【问题标题】:Rails after_initialize only on "new"Rails after_initialize 仅在“新”上
【发布时间】:2012-03-07 22:42:55
【问题描述】:

我有以下 2 个模型

class Sport < ActiveRecord::Base
  has_many :charts, order: "sortWeight ASC"
  has_one :product, :as => :productable
  accepts_nested_attributes_for :product, :allow_destroy => true
end

class Product < ActiveRecord::Base
  belongs_to :category
  belongs_to :productable, :polymorphic => true
end

没有产品就不可能有运动,所以在我的sports_controller.rb 中我有:

def new
  @sport = Sport.new
  @sport.product = Product.new
...
end

我尝试将产品的创建转移到运动模型,使用after_initialize

after_initialize :create_product

def create_product
 self.product = Product.new
end

我很快了解到,只要实例化模型(即通过 find 调用),就会调用 after_initialize。所以这不是我想要的行为。

我应该如何建模所有sport 都有product 的要求?

谢谢

【问题讨论】:

    标签: ruby-on-rails model nested-attributes


    【解决方案1】:

    如您所说,将逻辑放入控制器可能是最佳答案,但您可以通过执行以下操作使 after_initialize 工作:

    after_initialize :add_product
    
    def add_product
      self.product ||= Product.new
    end
    

    这样,它只在不存在产品时设置产品。与控制器中的逻辑相比,它可能不值得开销和/或不够清晰。

    编辑:根据 Ryan 的回答,就性能而言,以下可能会更好:

    after_initialize :add_product
    
    def add_product
      self.product ||= Product.new if self.new_record?
    end
    

    【讨论】:

    • 我使用这个解决方案的问题是,在我的情况下,产品字段可以为空(所以我真的需要一个 after_initialize 只在创建时).. 如果有人有想法,那就太好了,谢谢!
    • @yorch 在创建之前/之后签出。如果您的逻辑太复杂,您可能不想将其隐藏在 before/after 挂钩中,因为这可能太神奇
    • 然而,这种方法并没有优化,一旦你开始选择大量的运动/产品,你会发现你的查询是非常未优化的,因为对于你拥有的每一个关系,你都在做一个选择语句来查看如果产品存在
    • @YaBoyQuy 您应该在查询中使用includes - 请参阅api.rubyonrails.org/classes/ActiveRecord/Associations/… 的 Eager Loading 部分
    • @Eric L,当我第一次阅读这个问题时,我认为使用 Product 创建一个 Sport 对象是模型存在的先决条件以及与之相关的业务规则。如果确实如此,IMO 这个逻辑必须存在于域层而不是视图/应用层(包括控制器)上。如果不是所有运动都必须至少有一个产品,那么您建议的类方法是一个不错的选择
    【解决方案2】:

    after_initialize :add_product, if: :new_record? 肯定是这里最干净的方式。

    将条件排除在 add_product 函数之外

    【讨论】:

    • after_initialize :add_product, on: :create 工作吗?
    • 看起来不像:我明白了:ArgumentError: Unknown key: :on. Valid keys are: :if, :unless, :prepend
    【解决方案3】:

    如果您执行self.product ||= Product.new,它仍然会在您执行find 时搜索产品,因为它需要检查它是否为 nil。因此,它不会进行任何急切加载。为了仅在创建新记录时执行此操作,您只需在设置产品之前检查它是否是新记录。

    after_initialize :add_product
    
    def add_product
      self.product ||= Product.new if self.new_record?
    end
    

    我做了一些基本的基准测试,检查if self.new_record? 似乎并没有以任何明显的方式影响性能。

    【讨论】:

    • 调用 new_record 会影响性能的出色工作? !
    • 也可以通过写after_initialize :add_product, :if =&gt; :new_record?new_record? 移出add_product。在某些情况下,组织会更好。
    • 怎么知道他们可以放 :if 呢?您可以链接到文档而不是文档吗?
    【解决方案4】:

    不用after_initialize,用after_create怎么样?

    after_create :create_product
    
    def create_product
      self.product = Product.new
      save
    end
    

    这看起来会解决您的问题吗?

    【讨论】:

    • 这是用于控制器中的new 方法....尚未将任何内容保存到数据库中,因此不会调用after_create
    【解决方案5】:

    看起来你们很亲密。您应该能够完全取消 after_initialize 调用,但首先我相信如果您的 Sport 模型与 :product 具有“has_one”关系,如您所指出的,那么您的 Product 模型也应该“belong_to”运动。将此添加到您的产品模型中

    belongs_to: :sport
    

    下一步,您现在应该可以像这样实例化 Sport 模型了

    @sport = @product.sport.create( ... )
    

    这是基于来自 Ruby on Rails Guides 的 Association Basics 的信息,如果我不完全正确,您可以阅读一下

    【讨论】:

    • 多态关联使这一点有点偏离。产品属于可生产的(组成词)。这是因为有些产品是运动的,有些是电影(我从运动开始)。这是我对如何在 Rails 中处理继承的理解(除了我不想要的单表继承)。不过感谢您的想法!
    • 啊,我明白了,我没有意识到模型的多态性,反过来又意识到我也不知道如何处理这个问题。我也会继续自己研究。如果我有什么想法,我会告诉你的。
    • 这个railscast 似乎包含您正在寻找的答案。看起来您使用与我在上面尝试的类似方式来创建新运动。希望这会有所帮助。
    • 是的,看起来答案是将逻辑放入控制器中。这也是一些人在 IRC 频道上所说的
    【解决方案6】:

    你应该重写初始化方法,比如

    class Sport < ActiveRecord::Base
    
      # ...
    
      def initialize(attributes = {})
        super
        self.build_product
        self.attributes = attributes
      end
    
      # ...
    
    end
    

    从数据库加载记录时,永远不会调用初始化方法。 请注意,在上面的代码中,属性是在构建产品之后分配的。 在这样的设置中,属性分配会影响创建的产品实例。

    【讨论】:

    • 您不应该这样做,原因如下:stackoverflow.com/questions/4376992/…
    • 不,没有理由不覆盖初始化。相反,如果您查看 rails 源代码,您会明白初始化是要被覆盖的。您需要做的就是在重写的初始化中调用 super,但这是所有 ruby​​ 开发人员都应该遵守的规则,rails 开发人员默默地希望您这样做。
    • 我会警惕这样做,因为这可能会导致混乱,在未来的版本中中断,并可能导致某些第三方 gem 出现问题。除非它是非常正式的建议。我完全支持一般的 hacks/workarounds,但构造函数是一个特别脆弱的功能,after_initialize 提供了一个简单的替代方案。
    • 我同意@VictorNazarov,after_initialize 回调通常是一个坏主意,因为它有两个缺点 - 它为所有未来的初始化创建开销,并破坏自定义 SELECTs,而覆盖初始化程序都没有。
    猜你喜欢
    • 2011-06-21
    • 1970-01-01
    • 1970-01-01
    • 2012-04-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多