Проверка Rails для ассоциации has_many - PullRequest
4 голосов
/ 03 февраля 2010

У меня проблемы с проверкой отношений has_many, в которых есть дети, но родитель не существует. Однако при создании / сохранении родительского объекта я хочу убедиться, что определенные дочерние элементы (с определенными атрибутами) уже сохранены.

Существует Parent объект, который has_many Child объектов. Объекты Child сначала сохраняются в базе данных и, таким образом, не имеют никакой ссылки на родителя. Структура ассоциации:

Parent
  - has_many :children 

Child
  - someProperty: string
  - belongs_to: parent

Например, есть три дочерних объекта:

#1 {someProperty: "bookmark", parent: nil}
#2 {someProperty: "history", parent: nil }
#2 {someProperty: "window", parent: nil }

Родитель действителен только в том случае, если он содержит дочерние объекты с someProperty history и window.

Я устанавливаю родителя внутри контроллера как:

p = Parent.new(params[:data])
for type in %w[bookmark_id history_id window_id]
    if !params[type].blank?
        p.children << Child.find(params[type])
    end
end
// save the parent object p now
p.save!

Когда дети присваиваются родителю с <<, они не сохраняются сразу, так как идентификатор родителя не существует. И для того, чтобы родитель был спасен, у него должны быть как минимум те 2 ребенка. Как я мог решить эту проблему? Любые входные данные приветствуются.

Ответы [ 2 ]

5 голосов
/ 03 февраля 2010

Не знаю, зачем вам нужно делать такую ​​вещь, но в любом случае, как насчет этого?

class Parent < ActiveRecord::Base

  CHILDREN_TYPES = %w[bookmark_id history_id window_id]
  CHILDREN_TYPES.each{ |c| attr_accessor c }

  has_many :children

  before_validation :assign_children
  validate :ensure_has_proper_children

private

  def assign_children
    CHILDREN_TYPES.each do |t|
      children << Child.find(send(t)) unless send(t).blank?
    end
  end

  def ensure_has_proper_children
    # Test if the potential children meet the criteria and add errors to :base if they don't
  end
end

Контроллер:

...
p = Parent.new(params[:data])
p.save!
...

Как видите, я перенес всю логику в модель. Затем существует двухэтапный процесс спасения детей. Сначала мы присваиваем детей родителю, а затем проверяем, соответствуют ли они требуемым критериям (укажите вашу логику).

Извините, что был коротким. Я отвечу на любые дополнительные вопросы, если это необходимо.

1 голос
/ 03 февраля 2010

Во-первых, если вы хотите, чтобы дети сохранялись без родительского идентификатора, тогда нет смысла делать это

 p = Parent.new(params[:data])
 for type in %w[bookmark_id history_id window_id]
   if !params[type].blank?
     p.children << Child.find(params[type])
   end
 end

вся цель

 p.children << some_child

- прикрепить родительский идентификатор к дочернему объекту, который вы здесь не делаете, потому что родительский объект еще не существует.

Другое дело, если вы просто хотите убедиться, что у родительского объекта есть дочерний объект, и если вы создаете дочерний и родительский объект вместе, вы можете использовать блок транзакции вокруг создания родительского и дочернего объектов, который будет гарантировать, что родитель имеет ребенок, как

 transaction do
   p = create_parent
   p.children << child1
   p.children << child2
 end

Таким образом, в рамках транзакции, если на каком-либо этапе код завершится неудачей, он откатит всю транзакцию БД, т. Е. У вас будет один родитель с двумя дочерними элементами или ничего, если вы ищете конечное состояние.

РЕДАКТИРОВАТЬ: Поскольку вы не можете создать родителя, если у него нет 2 детей, в этом случае, вместо

p = Parent.new(params[:data])
 for type in %w[bookmark_id history_id window_id]
   if !params[type].blank?
     p.children << Child.find(params[type])
   end
 end

сделать

 children = []
 for type in %w[bookmark_id history_id window_id]
   if !params[type].blank?
     children << Child.find(params[type])
   end
 end

 if children.size >= 2
   p = Parent.create!(params[:data])
   children.each {|child| p.children << child}
 end

Имеет ли это смысл

...