如何在Rails activerecord模型中为属性创建默认值?

Jos*_*ogi 190 activerecord ruby-on-rails

我想通过在ActiveRecord中定义属性来为属性创建默认值.默认情况下,每次创建记录时,我都想拥有属性的默认值:status.我试着这样做:

class Task < ActiveRecord::Base
  def status=(status)
    status = 'P'
    write_attribute(:status, status)
  end
end
Run Code Online (Sandbox Code Playgroud)

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

ActiveRecord::StatementInvalid: Mysql::Error: Column 'status' cannot be null
Run Code Online (Sandbox Code Playgroud)

因此我假设该值未应用于该属性.

在Rails中这样做的优雅方法是什么?

非常感谢.

Jim*_*Jim 287

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

....
add_column :status, :string, :default => "P"
....
Run Code Online (Sandbox Code Playgroud)

要么

你可以使用回调, 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
Run Code Online (Sandbox Code Playgroud)

  • 如果您尝试设置布尔字段的默认值,请注意`|| =`.`self.status ='P'如果self.status.nil?`更安全. (69认同)
  • 如果您尝试为视图设置默认值(即创建新记录时),这对您没有帮助.更好的选择是after_initialize. (42认同)
  • 通常我们会写self.status || ='P'.此外,如果要验证字段,请考虑使用before_validation回调. (16认同)
  • 请注意,如果使用before_create并且函数的最后一行类似于`self.hates_unicorns || = false`,则返回false并且模型将不会保存.:) (14认同)
  • @jackquack:如果你希望`true`成为默认值,`self.booleanfield || = true`将*always*设置为`true`,不仅在它未设置时,而且如果它已经设置为` false`(因为`false`和`nil`都是假的).即`x || = true`相当于`x = true`.您可以看到这可能会造成问题.它只发生在布尔值,因为Ruby中没有其他数据类型具有假值. (6认同)
  • 它应该是`self.status` (2认同)

Bee*_*Dog 192

因为我刚才遇到过这个问题,并且Rails 3.0的选项有点不同,我将为这个问题提供另一个答案.

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

class MyModel < ActiveRecord::Base
  after_initialize :default_values

  private
    def default_values
      self.name ||= "default value"
    end
end
Run Code Online (Sandbox Code Playgroud)

  • 谨慎一点; 'after_initialize'表示在Ruby初始化之后.因此,每次从数据库加载记录并用于在内存中创建新的模型对象时都会运行它,因此如果您想要的只是在第一次添加新记录时设置默认值,请不要使用此回调.如果你想这样做,请使用before_create而不是before_save; before_create在创建新的db记录之前和第一次初始化之后运行.每次对db记录进行任何类型的更新时都会调用before_save. (113认同)
  • 使用before_create而不是before_save的问题是before_save首先执行.因此,当您想要执行除设置默认值之外的其他操作时,比如在创建和更新时从其他属性计算值,它将导致问题,因为可能未设置默认值.最好使用|| =运算符并使用before_save (8认同)
  • 我还没有看到after_initialize中使用的代码无法轻易移动到before_validation,before_save等的情况.很可能最终你的团队中的一个开发人员会执行像MyModel.all.each这样的东西,可能是为了批量处理某种,因而运行这个初始化逻辑MyModel.count的次数. (3认同)
  • @Altonymous:好点.我想知道是否还有助于用'new_record?'条件包装'defaulting'块?([链接](http://apidock.com/rails/ActiveRecord/Base/new_record%3F)) (3认同)
  • 如何检查它是否"持久存在?"并且仅在设置它时如何? (2认同)

Tim*_*ord 83

当我需要默认值时,它通常用于渲染新操作视图之前的新记录.以下方法将仅为新记录设置默认值,以便在呈现表单时可用.before_save并且before_create 为时已晚,如果您希望在输入字段中显示默认值,则无法使用.

after_initialize do
  if self.new_record?
    # values will be available for new record forms.
    self.status = 'P'
    self.featured = true
  end
end
Run Code Online (Sandbox Code Playgroud)

  • 干得好,这也是我一般所做的.除非您只应设置值为零.否则,当它们传递给create方法时,你将覆盖这些值,例如在`a = Model.create(status:'A',features:false)中. (10认同)
  • asgeo1 是正确的。最好在设置之前检查它是否为零。使用 `self.status ||='P'` 或 `self.status = 'P' 如果 self.status.nil?` (2认同)

小智 77

您可以在不编写任何代码的情况下执行此操作:)您只需在数据库中设置列的默认值即可.您可以在迁移中执行此操作.例如:

create_table :projects do |t|
  t.string :status, :null => false, :default => 'P'
  ...
  t.timestamps
end
Run Code Online (Sandbox Code Playgroud)

希望有所帮助.

  • 注意,MySQL不允许TEXT/BLOB列上的默认值.否则这是理想的解决方案 (7认同)
  • 此解决方案需要数据库转储以保留其中的信息. (5认同)

EmF*_*mFi 22

解决方案取决于一些事情.

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

如果您回答第一个问题,那么您想使用Jim的解决方案

如果您回答第二个问题,那么您想使用Daniel的解决方案

如果对这两个问题的回答都是"否",那么最好添加和运行新的迁移.

class AddDefaultMigration < ActiveRecord::Migration
  def self.up
     change_column :tasks, :status, :string, :default => default_value, :null => false
  end
end
Run Code Online (Sandbox Code Playgroud)

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

CPU很便宜,所以在Jim的解决方案中重新定义Task不会导致很多问题.特别是在生产环境中.这种迁移是正确的方式,因为它加载它并且不常调用.


Pep*_*ppy 12

我会考虑使用这里找到的attr_defaults .你最疯狂的梦想将成真.


swa*_*pab 10

只是加强吉姆的答案

使用存在可以做到

class Task < ActiveRecord::Base
  before_save :default_values
  def default_values
    self.status = status.presence || 'P'
  end
end
Run Code Online (Sandbox Code Playgroud)


aec*_*aec 5

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

对于列类型 Rails 不支持开箱即用 - 例如 ENUM 列 - Rails 将无法自省列默认值。对于这些情况下,你希望使用after_initialize(它被称为每一个对象从数据库加载以及对象使用。新创建的每个时间的时间),before_create(因为它验证后出现),或before_save(因为它也会在更新时发生,这通常不是您想要的)。

相反,您希望在 before_validation on: create 中设置属性,如下所示:

before_validation :set_status_because_rails_cannot, on: :create

def set_status_because_rails_cannot
  self.status ||= 'P'
end
Run Code Online (Sandbox Code Playgroud)


Pet*_* P. 5

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

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

这是我的解决方案:

# 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
Run Code Online (Sandbox Code Playgroud)

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