Rails 4在保存时创建关联对象

在保存新主对象后,如何自动创建多个关联对象?

例如

在Rails 4中,我有三个对象: 企业预算类别

#app/models/business.rb class Business < ActiveRecord::Base #attrs id, name has_many :budgets end #app/models/budget.rb class Budget < ActiveRecord::Base #attrs id, business_id, department_id, value belongs_to :business belongs_to :category end #app/models/category.rb class Category < ActiveRecord::Base #attrs id, name has_many :budgets end 

当我创建新业务时,在保存新业务之后,我想为每个类别自动创建预算并给它$ 0的值。 这样,当我去展示或编辑新的业务时,它将已经具有相关的类别和预算,然后可以对其进行编辑。 因此,在创建新业务时,将创建多个新预算,每个类别对应一个,每个都具有值0。

我读了这篇文章: Rails 3,如何在创建主记录后添加关联记录(Books,Auto Add BookCharacter)

我想知道我是否应该在Business模型中使用after_create回调,然后在Budgets控制器中存在逻辑(不完全确定如何执行此操作),或者我是否应该在’new’中添加logic_controller.rb的逻辑用类似的东西打电话:

 @business = Business.new @categories = Category.all @categories.each do |category| category.budget.build(:value => "0", :business_id => @business.id) end 

根据我的经验,最好避免使用回调,除非它与给定模型的持久性有关。 在这种情况下,如果没有提供预算,则让预算设置自己的默认值就是使用回调。 这也消除了逻辑上的一些复杂性。

 class Budget before_validate :set_value ... private def set_value self.value ||= 0 end end 

对于其他人,我将创建自定义类,每个类都有一个单一的责任,以系统地生成新业务。 这是一个例子。 请记住,这不是要复制和粘贴,只是为了说明一个概念:

 class BusinessGenerator < Struct.new(:business_params) attr_reader :business def generate create_business create_budgets end private def create_business @business = Business.create!(business_params) end def create_budgets BudgetGenerator.new(@business).create end end class BudgetGenerator < Struct.new(:business) def generate categories.each do |c| business.budgets.create!(category: c) end end private def categories Category.all end end 

这很好,因为它分离了关注点,并且易于扩展,可测试,并且不使用Rails魔术,如accepts_nested_attributes_for。 例如,如果将来您决定并非所有企业都需要每个类别的预算,您可以轻松地将您想要的那些作为参数传递给BudgetGenerator。

您将在控制器中实例化BusinessGenerator类:

 class BusinessController < ActiveRecord::Base ... def create generator = BusinessGenerator.new(business_params) if generator.generate flash[:success] = "Yay" redirect_to generator.business else render :new end end ... end 

使用这种方法可能会遇到的一些问题包括:

  • 将validation错误返回到您的业务表单
  • 如果预算的创建失败,那么您就会陷入无预算的业务。 在创建预算之前,您不能等到保存业务,因为没有要关联的ID。 也许考虑在生成器方法中放置一个事务。

无论Brent Eicher的伟大建议如何,我从来没有经历过使用回调的任何不好的事情。 如果您不介意使用它们,则可以执行以下操作( 如果您每次将预算设置为0 ):

 #app/models/business.rb class Business < ActiveRecord::Base before_create :build_budgets private def build_budgets Category.all.each do |category| self.budgets.build(category: category, value: "0") end end end 

-

此外,您需要确保budget键是正确的。

Budget belongs_to Category时,我看到你有department_id 。 您应该创建此category_id 定义foreign_key:

 #app/models/budget.rb class Budget < ActiveRecord::Base belongs_to :category, foreign_key: "department_id" end 

我最终将逻辑添加到业务控制器中的create方法,以遍历所有类别并在保存后立即创建预算。 请注意,我很懒,没有进行任何error handling。 :

  def create @business = Business.new(params[:business]) @results = @business.save @categories = Categories.all @categories.each do |category| category.budgets.create(:amount => "0", :business_id => @business.id) end respond_to do |format| ... end end