【问题标题】:How to set default values in Rails?如何在 Rails 中设置默认值?
【发布时间】:2010-11-14 06:10:48
【问题描述】:

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

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

如果这是可以接受的,或者有更好的方法,有没有人提出意见?

【问题讨论】:

  • 这些对象是什么;它们是如何消费/使用的?它们是在渲染视图时使用还是用于控制器逻辑?
  • 如果您在谈论 ActiveRecord 对象,我必须告诉您,“默认值”问题没有合理的解决方案。只有疯狂的 hack,Rails 的作者似乎不认为这个功能值得(令人惊讶的是只有 Rails 社区......)
  • 由于接受的和大多数答案都集中在 ActiveRecords 上,我们假设最初的问题是关于 AcitveRecords。因此stackoverflow.com/questions/328525/… 的可能重复

标签: ruby-on-rails ruby


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

    如果您正在处理模型,您可以使用 Rails 5+ 中的 Attriutes API http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute

    只需添加一个具有适当列名的迁移,然后在模型中设置它:

    class StoreListing < ActiveRecord::Base
      attribute :country, :string, default: 'PT'
    end
    

    【讨论】:

      【解决方案3】:

      “正确”在 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() 时,它总是将“Doe”属性滴到Person.new(),除非您用其他东西覆盖它。

      【讨论】:

      • 试试看。它适用于使用.new 类方法调用的新模型对象。那篇博文中关于 ActiveRecord 直接调用.allocate 的讨论是关于从数据库中加载现有数据的模型对象。 (而且 IMO,ActiveRecord 以这种方式工作是一个糟糕的主意。但这不是重点。)
      • 如果您退后一步再次阅读原始发布者的问题,Nikita,然后按顺序阅读我的 cmets,它可能对您更有意义。如果不是……那么,问题已经得到解答。祝你有美好的一天。
      • 更适合向下迁移:change_column_default :people, :last_name, nilstackoverflow.com/a/1746246/483520
      • 请注意,对于较新的 rails 版本,您需要在默认参数之前添加一个额外的类型参数。在此页面上搜索 :type。
      • @JoelBrewer 我今天遇到了这个问题 - 对于 Rails 4,您还需要指定列类型:change_column :people, :last_name, :string, default: "Doe"
      【解决方案4】:

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

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

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

      第二个最好的方法是将默认值放入模型中,但这仅适用于最初为 nil 的属性。你可能会遇到我在处理boolean 列时遇到的问题:

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

      您需要new_record?,因此默认值不会覆盖从数据库加载的值。

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

      【讨论】:

      • 次要注意——你想做两件事:.... 1)不要在_initialize之后调用你的方法。你想要after_initiation :your_method_name.... 2 使用self.max_users ||= 10
      • 对于布尔值,只需这样做:prop = true if prop.nil?
      • after_initialize do 而不是def after_initialize
      • self.max_users 是安全的。
      【解决方案5】:

      您可以使用 rails_default_value gem。例如:

      class Foo < ActiveRecord::Base
        # ...
        default :bar => 'some default value'
        # ...
      end
      

      https://github.com/keithrowell/rails_default_value

      【讨论】:

        【解决方案6】:

        比建议的答案可能更好/更清洁的潜在方法是覆盖访问器,如下所示:

        def status
          self['name_of_var'] || 'desired_default_value'
        end
        

        参见the ActiveRecord::Base documentationmore from StackOverflow on using self 中的“覆盖默认访问器”。

        【讨论】:

          【解决方案7】:

          我需要设置一个默认值,就像它在 DB 中被指定为默认列值一样。所以它的行为是这样的

          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
          

          对我来说很好用。 (轨道 3.2.x)

          【讨论】:

            【解决方案8】:

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

            after_initialize 回调会为查找器找到并实例化的每个对象触发,并且在实例化新对象后也会触发 after_initialize (see ActiveRecord Callbacks)。

            所以,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_valuebar 属性呈现表单。

            充其量这是hacky...

            编辑 - 使用“新记录?”检查是否从新调用实例化

            不要检查属性值,而是使用带有 rails 的 new_record? 内置方法。所以,上面的例子应该是这样的:

            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 上设置默认值,但它也将默认值设置为每个找到并实例化的对象的属性(即从分贝)。您可以在分配默认值之前检查对象属性以查找值,但这不是一个非常优雅或强大的解决方案。
            • 为什么在这里推荐after_initialize回调?关于回调的 Rails 文档有在 before_create 中设置默认值而不进行额外条件检查的示例
            • @Dfr - 我一定错过了,你能给我一个链接来查看,我会更新答案...
            • 是的,这里是api.rubyonrails.org/classes/ActiveRecord/Callbacks.html 第一个例子,类Subscription
            • @Dfr - 我们使用 after_initialize 而不是 before_create 回调的原因是我们想为 user 设置一个默认值(例如在视图中使用)创建新对象时。 before_create 回调在 用户 已被提供一个新对象、提供他们的输入并将对象提交给控制器后被调用。然后控制器检查任何before_create 回调。这似乎违反直觉,但它是一个命名法 - before_create 指的是 create 操作。实例化一个新对象不会create该对象。
            【解决方案9】:

            如果您在谈论 ActiveRecord 对象,我使用“attribute-defaults”gem。

            文档和下载:https://github.com/bsm/attribute-defaults

            【讨论】:

              【解决方案10】:

              根据 SFEley 的回答,这里有一个更新/修复的 Rails 较新版本:

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

              【讨论】:

              • 请注意这不适用于 Rails 4.2.x(未在更高版本上测试)。由于change_column 可以非常“结构化”,因此无法推断出反向操作。如果您不确定,只需在之后运行db:migratedb:rollback 进行测试。接受的答案是相同的结果,但至少是假设的!
              • 这对我来说是正确的答案。它适用于 rails 5.1,但您需要像上面 @GoBusto 所述的 updown 进行操作
              【解决方案11】:

              如果您指的是 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

              【讨论】:

              • 在保存对象之前在数据库中使用默认值不是问题吗?如果我想构造一个填充了默认值的新对象,我需要在初始化程序中设置它们。
              • 布尔值不能默认为 1 或 0——它们必须设置为 true 或 false(参见 Silasj 的回答)。
              • @JamonHolmgren 谢谢你的评论,我更正了答案:)
              【解决方案12】:

              您也可以在迁移中尝试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 docs

              【讨论】:

              • 接受的答案更完整,但我喜欢这个干净且有记录的答案...谢谢!
              【解决方案13】:

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

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

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

              【讨论】:

                【解决方案14】:

                我回答了一个类似的问题here.. 一个干净的方法是使用 Rails attr_accessor_with_default

                class SOF
                  attr_accessor_with_default :is_awesome,true
                end
                
                sof = SOF.new
                sof.is_awesome
                
                => true
                

                更新

                attr_accessor_with_default 在 Rails 3.2 中已被弃用。你可以用纯 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 从 rails > 3.1.0 起已弃用
                • 在您的示例中,即使@is_awesome == false,is_awesome 也将始终为真。
                【解决方案15】:

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

                如果您在谈论 ActiveRecord 对象,请查看覆盖 after_initialize。

                这些博文(不是我的)很有用:

                Default values Default constructors not called

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

                【讨论】:

                  【解决方案16】:

                  您可以覆盖 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
                  

                  【讨论】:

                  • 这是更改核心 Rails 功能的糟糕地方。还有其他更稳定的不太可能破坏其他东西的方法,其他答案中提到了这样做的方法。 Monkey patching 应该只作为其他方式无法完成的事情的最后手段。
                  【解决方案17】:

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

                  有很多方法可以处理它,这个plugin 看起来很有趣。

                  【讨论】:

                  • 我认为你不应该依赖数据库来处理默认值和约束,所有这些东西都应该在模型层进行整理。该插件仅适用于 ActiveRecord 模型,它不是为对象设置默认值的通用方法。
                  • 我认为这取决于您尝试使用的默认值类型,这不是我需要经常做的事情,但我会说约束实际上要好得多在数据库和模型中都设置为 off - 以防止您的数据在数据库中变得无效。
                  猜你喜欢
                  • 2010-09-24
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-05-14
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多