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)

И мне интересно, должен ли я использовать обратный вызов 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 ответа

Решение

Я закончил тем, что добавил логику в метод create в контроллере Business для циклического прохождения всех категорий и создания бюджета сразу после сохранения. Обратите внимание, что я был ленив и не вставлял обработки ошибок.:

  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

По моему опыту, лучше избегать использования обратных вызовов, если это не связано с постоянством данной модели. В этом случае предоставление бюджету возможности установить собственное значение по умолчанию, если оно не указано, является хорошим вариантом использования обратного вызова. Это также устраняет некоторые сложности из вашей логики.

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, как acceptpts_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

Некоторые проблемы, которые могут возникнуть при таком подходе:

  • Возврат ошибок проверки в вашу бизнес-форму
  • Если создание бюджета не удается, вы застряли в бизнесе без бюджета. Вы не можете ждать, чтобы сохранить бизнес, пока не будут созданы бюджеты, потому что нет идентификатора, который нужно связать. Возможно, рассмотрите возможность размещения транзакции внутри метода генератора.

Невзирая на 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
Другие вопросы по тегам