如何在Rails中设置默认值?


108

我正在尝试找到在Rails中为对象设置默认值的最佳方法。

我能想到的最好的new方法是在控制器的方法中设置默认值。

如果这是可以接受的,或者有更好的方法可以进行,那么有人可以提供任何输入吗?


1
这些对象是什么?他们如何消费/使用?它们是在渲染视图时使用还是用于控制器逻辑?
Gishu

3
如果您正在谈论ActiveRecord对象,那么我必须告诉您,“默认值”问题没有健全的解决方案。仅是疯狂的骇客,而且rails的作者似乎并不认为该功能值得(令人惊奇的是,只有rails社区才是..)
Mauricio 2010年

由于已接受且大多数答案都集中在ActiveRecords上,因此我们假定最初的问题是有关AcitveRecords的。因此,stackoverflow.com
questions / 328525 /…的

Answers:


98

“正确”在Ruby中是一个危险的词。通常有不止一种方法可以做任何事情。如果您知道总是要使用该表上该列的默认值,则在数据库迁移文件中设置它们是最简单的方法:

class SetDefault < ActiveRecord::Migration
  def self.up
    change_column :people, :last_name, :type, :default => "Doe"
  end

  def self.down
    # You can't currently remove default values in Rails
    raise ActiveRecord::IrreversibleMigration, "Can't remove the default"
  end
end

因为ActiveRecord会自动发现表和列的属性,所以这将导致在任何标准Rails应用程序中使用该模型的任何模型中都设置相同的默认值。

但是,如果只希望在特定情况下设置默认值(例如,这是一个继承模型并与其他表共享表),那么另一种优雅的方法是在创建模型对象时直接在Rails代码中进行设置:

class GenericPerson < Person
  def initialize(attributes=nil)
    attr_with_defaults = {:last_name => "Doe"}.merge(attributes)
    super(attr_with_defaults)
  end
end

然后,当您执行GenericPerson.new()时,Person.new()除非您用其他方法覆盖它,否则它将始终将“ Doe”属性细化到最大。


2
试试吧。它将在使用.newclass方法调用的新模型对象上工作。该博客文章有关ActiveRecord直接调用的讨论.allocate是关于从数据库中加载现有数据的模型对象。(这是ActiveRecord的工作一个可怕的想法是这样,IMO但是,跑题的。)
SFEley

2
如果您再次阅读原始海报的问题(Nikita),然后依次阅读我的评论,那么对您来说可能更有意义。如果没有,那么问题已经回答。祝你今天愉快。
SFEley 2011年

8
更适合向下迁移:change_column_default :people, :last_name, nil stackoverflow.com/a/1746246/483520
Nolan Amy

9
请注意,对于较新的rails版本,在默认参数之前需要一个附加的type参数。在此页面上搜索:type。
本·惠勒2014年

3
@JoelBrewer我今天遇到了这个问题-对于Rails 4,您还需要指定列类型:change_column :people, :last_name, :string, default: "Doe"
GoBusto 2015年

56

根据SFEley的答案,以下是针对较新的Rails版本的更新/修复的代码:

class SetDefault < ActiveRecord::Migration
  def change
    change_column :table_name, :column_name, :type, default: "Your value"
  end
end

2
请注意,这在Rails 4.2.x上不起作用(未在更高版本上进行测试)。由于change_column可能是相当“结构化”的,因此无法推论出反向操作。如果不确定,只需运行即可,db:migrate然后立即进行测试db:rollback。接受的答案是相同的结果,但至少是假设的!
gfd

1
这对我来说是正确的答案。它适用于rails 5.1,但是您需要像@GoBustoupdown
Fabrizio Bertoglio

22

首先,您不能过载,initialize(*args)因为在所有情况下都不会调用它。

最好的选择是将默认值放入迁移中:

add_column :accounts, :max_users, :integer, :default => 10

其次,最好将默认值放入模型中,但这仅适用于最初为零的属性。您可能会像我在处理boolean列时那样遇到麻烦:

def after_initialize
  if new_record?
    max_users ||= 10
  end
end

您需要new_record?这样,因此默认值不会覆盖从datbase加载的值。

您需要||=阻止Rails覆盖传递给initialize方法的参数。


12
小注释-您想做两件事:..... 1)不要在after_initialize之后调用您的方法。您想要after_initiation :your_method_name.... 2使用self.max_users ||= 10
Jesse Wolgamott'2

5
对于布尔变量,只需执行以下操作:prop = nil如果prop.nil?
弗朗西斯·波特

2
after_initialize do代替def after_initialize
fotanus

self.max_users是安全的。
Ken Ratanachai S. 18/12 / 28,2

15

您还可以尝试change_column_default进行迁移(在Rails 3.2.8中进行了测试):

class SetDefault < ActiveRecord::Migration
  def up
    # Set default value
    change_column_default :people, :last_name, "Smith"
  end

  def down
    # Remove default
    change_column_default :people, :last_name, nil
  end
end

change_column_default Rails API文档


1
接受的答案比较完整,但是我喜欢这个干净且有据可查的答案...谢谢!
gfd

7

如果您引用的是ActiveRecord对象,则有两种(或多种)方法可以做到这一点:

1.在数据库中使用:default参数

例如

class AddSsl < ActiveRecord::Migration
  def self.up
    add_column :accounts, :ssl_enabled, :boolean, :default => true
  end

  def self.down
    remove_column :accounts, :ssl_enabled
  end
end

此处提供更多信息:http : //api.rubyonrails.org/classes/ActiveRecord/Migration.html

2.使用回调

例如 before_validation_on_create

此处提供更多信息:http : //api.rubyonrails.org/classes/ActiveRecord/Callbacks.html#M002147


2
在数据库中使用默认值,直到保存对象之前都没有设置默认值,这不是问题吗?如果要使用填充的默认值构造一个新对象,则需要在初始化程序中进行设置。
拉菲

布尔值不能默认为1或0-必须将其设置为true或false(请参见Silasj的答案)。
Jamon Holmgren 2012年

@JamonHolmgren感谢您的发言,我更正了答案:)
Vlad Zloteanu 2012年

没问题@VladZloteanu
Jamon Holmgren

7

在Ruby on Rails v3.2.8中,使用after_initializeActiveRecord回调,您可以在模型中调用一个方法,该方法将为新对象分配默认值。

查找器发现并实例化的每个对象都会触发after_initialize回调,在实例化新对象之后也会触发after_initialize(请参见ActiveRecord回调)。

因此,IMO应该看起来像这样:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    # required to check an attribute for existence to weed out existing records
    self.bar = default_value unless self.attribute_whose_presence_has_been_validated
  end
end

Foo.bar = default_value除非实例包含attribute_whose_presence_has_been_validated先前保存/更新的实例,否则此实例将不可用。然后,default_value它将与您的视图结合使用,以使用default_valuefor bar属性呈现表单。

充其量这是hacky ...

编辑-使用“ new_record?” 检查是否从新呼叫实例化

不用检查属性值,而是将new_record?内置方法与rails一起使用。因此,以上示例应如下所示:

class Foo < ActiveRecord::Base
  after_initialize :assign_defaults_on_new_Foo, if: 'new_record?'
  ...
  attr_accessible :bar
  ...
  private
  def assign_defaults_on_new_Foo
    self.bar = default_value
  end
end

这更干净。啊,Rails的魔力-比我聪明。


这不能按我预期的那样工作-它应该只在new上设置默认值,但它还会将默认值设置为找到和实例化的每个对象的属性(即从db加载的记录)。您可以在分配默认值之前检查对象属性以查找值,但这不是一个很好的解决方案。
错误的2012年

1
为什么after_initialize在这里建议回调?轨道上的回调文档有例如设置默认值在before_create没有额外条件检查
DFR

@Dfr-我一定错过了,您能给我扔一个链接进行审阅,我将更新答案吗?
2013年

是的,这里是api.rubyonrails.org/classes/ActiveRecord/Callbacks.html第一个示例,类Subscription
Dfr

5
@Dfr-之所以使用after_initialize,而不是before_create回调,是因为我们要在用户创建新对象时为用户(在视图中使用)设置默认值。用户提供了一个新对象,提供了他们的输入并将该对象提交给控制器之后,将before_create调用该回调。然后,控制器检查是否有任何回调。似乎违反直觉,但这是一个命名法,指的是动作。实例化一个新对象不是该对象。before_createbefore_createcreatecreate
错误的2013年

5

至少对于Rails 3.2.6中的布尔字段,这将在您的迁移中起作用。

def change
  add_column :users, :eula_accepted, :boolean, default: false
end

1或设置0为默认值在这里不起作用,因为它是一个布尔字段。它必须是truefalse值。



2

生成迁移和使用change_column_default,简洁且可逆:

class SetDefaultAgeInPeople < ActiveRecord::Migration[5.2]
  def change
    change_column_default :people, :age, { from: nil, to: 0 }
  end
end

1

如果您只是为数据库支持的模型的某些属性设置默认值,我会考虑使用sql默认列值-您能否阐明正在使用的默认类型?

有很多方法可以处理它,这个插件看起来像是一个有趣的选项。


1
我认为您不应该依赖数据库来处理默认值和约束,所有这些东西都应该在模型层中进行整理。该插件仅适用于ActiveRecord模型,不是为对象设置默认值的通用方法。
卢卡斯·斯特斯卡

我认为这取决于您尝试使用的默认类型,这不是我经常需要执行的操作,但是我说实际上在数据库和数据库中设置约束要好得多模型-防止您的数据在数据库中失效。
paulthenerd

1

覆盖new / initialize的建议可能不完整。Rails将(经常)调用ActiveRecord对象的allocate,而调用allocate不会导致初始化。

如果您在谈论ActiveRecord对象,请看一下重写after_initialize。

这些博客文章(不是我的)非常有用:

默认值 未调用默认构造函数

[编辑:SFEley指出,Rails在实例化内存中的新对象时实际上会查看数据库中的默认值-我没有意识到。


1

我需要设置一个默认值,就像在数据库中将其指定为默认列值一样。所以它表现得像这样

a = Item.new
a.published_at # => my default value

a = Item.new(:published_at => nil)
a.published_at # => nil

因为after_initialize回调是在从参数设置属性之后调用的,所以没有办法知道该属性是否为nil,因为从未设置过该属性,或者因为有意将其设置为nil。因此,我不得不深入了解一下这个简单的解决方案。

class Item < ActiveRecord::Base
  def self.column_defaults
    super.merge('published_at' => Time.now)
  end
end

对我来说很棒。(Rails 3.2.x)



0

在这里回答了类似的问题..做到这一点的一种干净方法是使用Rails attr_accessor_with_default

class SOF
  attr_accessor_with_default :is_awesome,true
end

sof = SOF.new
sof.is_awesome

=> true

更新

在Rails 3.2中不建议使用attr_accessor_with_default。可以使用纯Ruby代替

class SOF
  attr_writer :is_awesome

  def is_awesome
    @is_awesome ||= true
  end
end

sof = SOF.new
sof.is_awesome

#=> true

attr_accessor_with_default已废弃铁轨> 3.1.0
flynfish

在您的示例中,即使@is_awesome == false,is_awesome始终为true。
里奇2012年



-3

您可以覆盖ActiveRecord模型的构造函数。

像这样:

def initialize(*args)
  super(*args)
  self.attribute_that_needs_default_value ||= default_value
  self.attribute_that_needs_another_default_value ||= another_default_value
  #ad nauseum
end

2
这是更改核心导轨功能的好地方。还有其他更稳定的方法不太可能破坏其他事物,其他答案中提到的解决方法。猴子修补仅是无法以其他任何方式完成的事情的最后手段。
2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.