Rails: hook for validating model when it's appended to its parent resource?
I have a situation where a user submits a multi-model form. Through that form, I have to assign the user a random item from my database along to their account. As I loop through the data, I flag the item that I've assigned to them with a boolean in_use flag. The problem is, in a situation such as the following:
2.times do |n|
# grab random item which will be parent
parent = # some random code to grab an item not in use
parent.i开发者_StackOverflow中文版n_use = 1
parent.child.build
children << child
end
Three models involved here. The parent itself is within its own parent, hence the children << child statement. The problem here is an edge condition is that the code that grabs a random item not in use can grab the same parent twice as I don't know of a hook that will allow me to save parent.in_use after the child has been appended to its parent via children << child
. The loop will go again, the in_use flag hasn't been persisted to the database and it can select it again. Is there a way to persist it, then roll it back if validation fails in a situation like this?
Does this work?
2.times do |n|
# grab random item which will be parent
parent = # some random code to grab an item not in use
parent.child.build
parent.in_use
parent.save # <--- save the parent on the db
children << child
end
I'm not sure about the last line, since you didn't explain what the children
variable was. I'm also assuming that parent.in_use
is a method, not a property (otherwise you would have to write parent.in_use = true
or something similar)
One more thing - it seems you are using an external attribute (called in_use or similar) in order to store whether a parent has children. It would be probably simpler just to count the number of children. There are several ways to do this, but a good compromise is using an automatic counter cache.
class Parent << ActiveRecord::Base
has_many :children, :counter_cache => true #this will had a children_count attribute
The attribute is named just like the children table. So if you write has_many :issues
, the counter will be issues_Count
.
You will have to add a children_count attribute to the parent's table.
You then can do if parent.children_count > 0
instead of checking strange parent.in_use attributes.
More information on the counter cache on railscast#23
Your question is frustrating, because you say your issue stems from pulling in the same parent twice, but the code to pull the parent is omitted. Yet the code for an irrelevant confusing contextless 3 model relationship is left in.
Anyway, I'm going to say this how it seems from my perspective, and I think you'll agree: The solution you are asking for has you getting the same parent twice (now in two places in memory with different state between them) building relationships that shouldn't exist, somehow figuring this out in your validations, having a conditional in the loop to check for it and retry the whole process.
From my perspective, this sounds like a nightmare. As someone who has written horribly convoluted bug inclined code like that, I strongly recommend you just get two different parents in the first place. Don't let errors propagate across your app, contain them early, or better yet, prevent them from happening at all:
class YourUnnamedModel < ActiveRecord::Base
named_scope :unused , :conditions => { :in_use => false }
named_scope :random , :order => 'RANDOM()'
named_scope :limited , lambda { |n=1| return :limit => n }
def self.example
unused.random.limited(2).each do |parent|
puts "doing stuff with #{parent.inspect}"
end
nil
end
end
Caveat: For some stupid reason (https://rails.lighthouseapp.com/projects/8994/tickets/1274-patch-add-support-for-order-random-in-queries), it is possible that your 'RANDOM()' could be called something else.
精彩评论