Rails after_initialize 仅在“新”上

问题描述 投票:0回答:8

我有以下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

我很快了解到每当实例化模型时(即通过

after_initialize
调用)就会调用
find
。所以这不是我想要的行为。

我应该如何建模所有

sport
都有
product
的要求?

谢谢

ruby-on-rails model nested-attributes
8个回答
71
投票

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

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

70
投票

当然

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

将条件保留在 add_product 函数之外


33
投票

如果您这样做

self.product ||= Product.new
,每次您执行
find
时,它仍然会搜索产品,因为它需要检查它是否为零。因此,它不会执行任何预先加载。为了仅在创建新记录时执行此操作,您只需在设置产品之前检查它是否是新记录即可。

after_initialize :add_product

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

我做了一些基本的基准测试并检查

if self.new_record?
似乎没有以任何明显的方式影响性能。


2
投票

除了使用

after_initialize
,不如使用
after_create
怎么样?

after_create :create_product

def create_product
  self.product = Product.new
  save
end

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


1
投票

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

belongs_to: :sport

下一步,您现在应该能够像这样实例化一个运动模型

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

这是基于 Ruby on Rails 指南中的 Association Basics 的信息,如果我不完全正确,你可以通读一下


1
投票
after_initialize :add_product, unless: :persisted?

0
投票

你应该像这样覆盖初始化方法

class Sport < ActiveRecord::Base

  # ...

  def initialize(attributes = {})
    super
    self.build_product
    self.attributes = attributes
  end

  # ...

end

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


0
投票

before_create
非常适合这种特殊情况。

你看,如果你想在即将创建的记录上启动一些代码块,你应该使用

before_create
钩子,因为它只会被调用一次 - 当记录创建时。更新时和保存时都没有。仅一次 - 创建记录时。愿上帝保佑你。

my_model = Model.create(**record_params) # this will trigger before_create hook
my_model.update(**some_params) # this will not trigger before_create hook 
© www.soinside.com 2019 - 2024. All rights reserved.