Rails 4 在保存时创建关联对象
Rails 4 Create Associated Object on 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)
我想知道我是否应该在业务模型中使用 after_create 回调并让逻辑存在于预算控制器中(不确定如何执行此操作)或者我是否应该添加逻辑到'new' 调用中的 businesses_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
对于其余部分,我将创建自定义 classes,每个都有一个单一的职责,以系统地生成新业务。这是一个例子。请记住,这并不是要复制和粘贴,只是为了说明一个概念:
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
这很好,因为它分离了关注点并且易于扩展、可测试并且不像 accepts_nested_attributes_for 那样使用 Rails 魔法。例如,如果将来您决定并非所有企业都需要每个类别的预算,您可以轻松地将所需的预算作为参数传递给 BudgetGenerator。
您将在控制器中实例化 BusinessGenerator class:
class BusinessController < ActionController::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。也许考虑在生成器方法中放置一个事务。
不管 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
or 定义 foreign_key:
#app/models/budget.rb
class Budget < ActiveRecord::Base
belongs_to :category, foreign_key: "department_id"
end
我最终将逻辑添加到业务控制器中的创建方法以遍历所有类别并在保存后立即创建预算。请注意,我很懒,没有进行任何错误处理。 :
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
如何在保存新的主对象后自动创建多个关联对象?
例如
在 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)
我想知道我是否应该在业务模型中使用 after_create 回调并让逻辑存在于预算控制器中(不确定如何执行此操作)或者我是否应该添加逻辑到'new' 调用中的 businesses_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
对于其余部分,我将创建自定义 classes,每个都有一个单一的职责,以系统地生成新业务。这是一个例子。请记住,这并不是要复制和粘贴,只是为了说明一个概念:
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
这很好,因为它分离了关注点并且易于扩展、可测试并且不像 accepts_nested_attributes_for 那样使用 Rails 魔法。例如,如果将来您决定并非所有企业都需要每个类别的预算,您可以轻松地将所需的预算作为参数传递给 BudgetGenerator。
您将在控制器中实例化 BusinessGenerator class:
class BusinessController < ActionController::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。也许考虑在生成器方法中放置一个事务。
不管 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
or 定义 foreign_key:
#app/models/budget.rb
class Budget < ActiveRecord::Base
belongs_to :category, foreign_key: "department_id"
end
我最终将逻辑添加到业务控制器中的创建方法以遍历所有类别并在保存后立即创建预算。请注意,我很懒,没有进行任何错误处理。 :
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