Rails:如何为 Rails activerecord 模型中的属性创建默认值? [复制]

Posted

技术标签:

【中文标题】Rails:如何为 Rails activerecord 模型中的属性创建默认值? [复制]【英文标题】:Rails: How do I create a default value for attributes in Rails activerecord's model? [duplicate] 【发布时间】:2010-12-05 18:15:17 【问题描述】:

我想通过在 ActiveRecord 中定义属性来创建一个默认值。默认情况下,每次创建记录时,我都希望属性:status 有一个默认值。我试着这样做:

class Task < ActiveRecord::Base
  def status=(status)
    status = 'P'
    write_attribute(:status, status)
  end
end

但是在创建时我仍然从数据库中检索到这个错误:

ActiveRecord::StatementInvalid: mysql::Error: Column 'status' cannot be null

因此,我认为该值未应用于属性。

在 Rails 中执行此操作的优雅方式是什么?

非常感谢。

【问题讨论】:

***.com/questions/328525/… 提供更完整和最新的答案 【参考方案1】:

您完全无需编写任何代码就可以做到这一点:) 您只需为数据库中的列设置默认值。您可以在迁移中执行此操作。例如:

create_table :projects do |t|
  t.string :status, :null => false, :default => 'P'
  ...
  t.timestamps
end

希望对您有所帮助。

【讨论】:

此解决方案需要数据库转储以保留其中的信息。 注意,MySQL 不允许在 TEXT/BLOB 列上使用默认值。否则这是理想的解决方案 哇,指南中没有提到一次:default! guides.rubyonrails.org/migrations.html 不幸的是,我已经运行了我的迁移,因此正在寻找一种在模型中获得默认值的方法。 如果您希望默认值是配置值,可能会在迁移后随时修改。 如果您在调用“new”方法后需要它作为默认值,在保存或尝试保存它之前,这很好。此外,您始终可以运行新的迁移来更改列以添加默认值。【参考方案2】:

您可以为迁移中的列设置默认选项

....
add_column :status, :string, :default => "P"
....

您可以使用回调,before_save

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status ||= 'P' # note self.status = 'P' if self.status.nil? might be safer (per @frontendbeauty)
  end
end

【讨论】:

通常我们会写 self.status ||= 'P'。此外,如果正在验证该字段,请考虑使用 before_validation 回调。 如果您尝试为视图设置默认值(即创建新记录时),这对您没有帮助。更好的选择是 after_initialize。 请注意,如果您使用 before_create 并且函数的最后一行类似于self.hates_unicorns ||= false,则返回 false 并且模型不会保存。 :) 小心||=,以防您尝试设置布尔字段的默认值。 self.status = 'P' if self.status.nil? 更安全。 @jackquack:如果您希望true 成为默认值,self.booleanfield ||= true始终将其设置为true,不仅在未设置时,而且如果它已经设置为false(因为falsenil 都是假的)。 IE。 x ||= true 等价于 x = true。您可以看到这可能会造成问题。它只发生在布尔值上,因为 Ruby 中没有其他数据类型具有虚假值。【参考方案3】:

解决方案取决于几件事。

默认值是否取决于创建时可用的其他信息? 你能以最小的后果擦除数据库吗?

如果你对第一个问题的回答是肯定的,那么你想使用 Jim 的解决方案

如果你对第二个问题的回答是肯定的,那么你想使用丹尼尔的解决方案

如果您对这两个问题的回答均为“否”,则最好添加并运行新的迁移。

class AddDefaultMigration < ActiveRecord::Migration
  def self.up
     change_column :tasks, :status, :string, :default => default_value, :null => false
  end
end

:string 可以替换为 ActiveRecord::Migration 识别的任何类型。

CPU 很便宜,因此在 Jim 的解决方案中重新定义 Task 不会引起很多问题。尤其是在生产环境中。这种迁移是正确的做法,因为它会被加载并且调用频率要低得多。

【讨论】:

我刚刚使用了迁移技术,我惊讶地发现默认值已被重新应用到我现有的所有数据中。此 Rails v3.0.1 sqlite 处于开发模式。 大多数数据库引擎不会那样做。您可以有一个默认值,但仍然有空值。我不确定是 Rails 还是 sqlite 在应用非空约束时假定所有空行都应该具有默认值。但我知道,如果您对包含空值的列应用非空约束,其他数据库引擎会阻塞。【参考方案4】:

我现在找到了更好的方法:

def status=(value) 
  self[:status] = 'P' 
end 

在 Ruby 中,方法调用允许没有括号,因此我应该将局部变量命名为其他名称,否则 Ruby 会将其识别为方法调用。

【讨论】:

您的问题应该更改以适应这个公认的答案。在您的问题中,您想默认属性的初始值。在您写的答案中,您实际上是在处理该属性的输入值。 这不会设置默认值,如果在任何时候设置了“状态”值,它会将值设置为“P”。此外,您应该真正使用“write_attribute :status, 'P'”【参考方案5】:

因为我刚刚遇到这个问题,而且 Rails 3.0 的选项有点不同,所以我将提供另一个答案。

在 Rails 3.0 中,你想做这样的事情:

class MyModel < ActiveRecord::Base
  after_initialize :default_values

  private
    def default_values
      self.name ||= "default value"
    end
end

【讨论】:

请注意; 'after_initialize' 表示在 Ruby 初始化之后。因此,每次从数据库加载记录并用于在内存中创建新模型对象时都会运行它,因此如果您只想在第一次添加新记录时设置默认值,请不要使用此回调。如果您想这样做,请使用 before_create 而不是 before_save; before_create 在创建新的数据库记录之前和第一次初始化之后运行。每次对数据库记录进行任何类型的更新时都会调用 before_save。 使用 before_create 而不是 before_save 的问题是 before_save 先执行。因此,当您想做除设置默认值之外的其他操作时,例如在创建和更新时从其他属性计算值,这将导致问题,因为可能未设置默认值。最好使用 ||= 运算符并使用 before_save 如何检查它是否为persisted?,如果不是则仅设置它? 我还没有看到无法将 after_initialize 中使用的代码轻松移动到 before_validation、before_save 等的案例。很有可能您团队中的一位开发人员最终会执行 MyModel 之类的东西。 all.each 可能用于某种批量处理,因此运行此初始化逻辑 MyModel.count 次数。 @Altonymous:好点。我想知道是否也有助于用new_record? 条件包装“默认”块? (link)【参考方案6】:

我会考虑使用 here 找到的 attr_defaults。你最疯狂的梦想会成真。

【讨论】:

虽然值得注意的是,这本质上是@BeepDog 答案的包装:github.com/bsm/attribute-defaults/blob/master/lib/…【参考方案7】:

当我需要默认值时,通常在新操作的视图呈现之前用于新记录。以下方法将仅为新记录设置默认值,以便在呈现表单时它们可用。 before_savebefore_create 为时已晚如果您希望默认值显示在输入字段中,它们将不起作用。

after_initialize do
  if self.new_record?
    # values will be available for new record forms.
    self.status = 'P'
    self.featured = true
  end
end

【讨论】:

谢谢。那就是我一直在寻找的那个,我的输入字段将填充默认值。 干得好,我通常也是这样做的。除了你应该只设置它们为零的值。否则,当它们被传递到 create 方法时,您将覆盖这些值,例如 a = Model.create(status:'A', featured:false) asgeo1 是正确的。最好在设置之前检查它是否为 nil。使用self.status ||= 'P'self.status = 'P' if self.status.nil?【参考方案8】:

对于 Rails 开箱即用支持的列类型 - 就像这个问题中的字符串 - 最好的方法是按照 Daniel Kristensen 的指示在数据库本身中设置列默认值。 Rails 将自省数据库并相应地初始化对象。另外,这使您的数据库不会有人在您的 Rails 应用程序之外添加一行而忘记初始化该列。

Rails 不支持开箱即用的列类型 - 例如ENUM 列 - Rails 将无法内省列默认值。对于这些情况,您确实想使用 after_initialize(每次从数据库加载对象以及每次使用 .new 创建对象时都会调用它),before_create(因为它发生在验证)或 before_save(因为它也会在更新时发生,这通常不是您想要的)。

相反,您想在 before_validation 上设置属性:创建,如下所示:

before_validation :set_status_because_rails_cannot, on: :create

def set_status_because_rails_cannot
  self.status ||= 'P'
end

【讨论】:

【参考方案9】:

在我看来,当需要默认值时,有两个问题需要解决。

    初始化新对象时需要该值。使用 after_initialize 是不合适的,因为如前所述,它将在调用 #find 期间被调用,这会导致性能下降。 保存时需要保持默认值

这是我的解决方案:

# the reader providers a default if nil
# but this wont work when saved
def status
  read_attribute(:status) || "P"
end

# so, define a before_validation callback
before_validation :set_defaults
protected
def set_defaults
  # if a non-default status has been assigned, it will remain
  # if no value has been assigned, the reader will return the default and assign it
  # this keeps the default logic DRY
  status = status
end

我很想知道为什么人们会想到这种方法。

【讨论】:

设置跳过验证会不会不执行?【参考方案10】:

只是加强Jim's answer

使用presence 可以做到

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status = status.presence || 'P'
  end
end

【讨论】:

以上是关于Rails:如何为 Rails activerecord 模型中的属性创建默认值? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

Rails 如何为多态关联填充“model_type”字段?

如何为 Rails 中的 url 助手设置默认主机?

如何为具有范围批量分配的 Rails 3.1 应用程序播种

Rails:如何为 AWS Elasticbeanstalk 应用程序获取 puma 3.11?

Rails:如何为 redirect_to 设置 json 格式

如何为多记录 Rails 表单中的复选框设置唯一 ID?