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

时间:2015-11-13 22:07:08

标签: ruby-on-rails ruby ruby-on-rails-4 associations after-save

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

例如

在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, how add a associated record after creating a primary record (Books, Auto Add BookCharacter)

我想知道我是否应该在Business模型中使用after_create回调,然后在Budgets控制器中存在逻辑(不完全确定如何执行此操作),或者我是否应该在business_controller.rb中添加逻辑“新”调用类似于:

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

3 个答案:

答案 0 :(得分:4)

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

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

这很好,因为它可以分离关注点,并且易于扩展,可测试,并且不像Accept_nested_attributes_for那样使用Rails魔术。例如,如果将来您决定并非所有企业都需要每个类别的预算,您可以轻松地将您想要的参数传递给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

这种方法可能会遇到一些问题:

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

答案 1 :(得分:1)

无论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外键正确无误。

department_id时看到你有Budget belongs_to Category。您应该将此category_id 定义为foreign_key:

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

答案 2 :(得分:0)

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

  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