Reputation: 141859
I am having trouble with validations on a has_many relationship where the children exist, but the parent doesn't. However, when creating/saving the parent object, I want to ensure that specific children (with certain attributes) have already been saved.
There is a Parent
object that has_many
Child
objects. The Child
objects are persisted into the database first, and thus don't have any reference to the parent. The association structure is:
Parent
- has_many :children
Child
- someProperty: string
- belongs_to: parent
For example, there are three child objects:
#1 {someProperty: "bookmark", parent: nil}
#2 {someProperty: "history", parent: nil }
#2 {someProperty: "window", parent: nil }
A parent is valid only if it contains child objects with someProperty history
and window
.
I am setting up the parent inside the controller as:
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!
When the children are assigned to the parent with <<
, they are not saved immediately as the parent's id does not exist. And for the parent to be saved, it must have at least those 2 children. How could I solve this problem? Any input is welcome.
Upvotes: 4
Views: 7276
Reputation: 15596
Not sure why you need to do such a thing, but anyway, how about doing this?
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
Controller:
...
p = Parent.new(params[:data])
p.save!
...
As you can see, I moved all the logic to model at the first place. Then, there is a two-step process for saving children. First, we assign children to the parent and then we validate if they meet the required criteria (insert your logic there).
Sorry for being short. I'll answer any further questions if necessary.
Upvotes: 5
Reputation: 3696
First thing, if you want the children to be saved without the parent id then there is no point in doing this
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
the whole purpose of
p.children << some_child
is to attach the parent id to the child object which you are not doing here because the parent doesn't exist yet.
The other thing is if you just want to make sure that the parent has a child object and if you are creating child and parent together then you can use transaction block around the parent and child creation which will make sure that the parent has child, like
transaction do
p = create_parent
p.children << child1
p.children << child2
end
So, within the transaction, if at any stage code fails then it will rollback the whole db transaction , i.e you will either have one parent with 2 children or nothing, if that's the end state you are looking for.
EDIT: Since you can't create a parent unless it has 2 children, in that case, instead of
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
do
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
Does that make sense
Upvotes: 1