Rails:如何为Rails activerrecord 's模型中的属性创建默认值?

我想通过在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中做这件事的优雅方式是什么?

多谢。

227668 次浏览

你不需要写任何代码就可以做到这一点:)你只需要为数据库中的列设置默认值。您可以在迁移中这样做。例如:

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

希望这能有所帮助。

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

....
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 better for boolean fields (per @frontendbeauty)
end
end

解决方案取决于几件事。

默认值是否依赖于创建时可用的其他信息? 你能以最小的代价清除数据库吗?< / p >

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

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

如果两个问题的答案都是否定的,那么您最好添加并运行一个新的迁移。

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不会引起太多问题。特别是在生产环境中。这种迁移是正确的方式,因为它被加载并且调用得更少。

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

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

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

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

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

class MyModel < ActiveRecord::Base
after_initialize :default_values


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

我会考虑使用attr_defaults found 在这里。你最疯狂的梦想将会成真。

当我需要默认值时,通常在新动作的视图呈现之前为新记录。以下方法将仅为新记录设置默认值,以便在呈现表单时可用。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

对于Rails开箱即用的列类型——比如这个问题中的字符串——最好的方法是在数据库中设置默认列,就像Daniel Kristensen指出的那样。Rails将对DB进行内省并相应地初始化对象。另外,这使您的DB安全,因为有人在您的Rails应用程序之外添加了一行,而忘记初始化该列。

对于Rails不支持开箱即用的列类型——例如ENUM列——Rails将不能自省列默认值。对于这些情况,想要使用after_initialize(每次从DB加载对象时调用它,以及每次使用.new创建对象时调用它)、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

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

  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

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

只是加强吉姆的回答

使用存在就可以了

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