How to bulk validate association in Rails
I have the following scenario:
One of my models, let's call it 'Post', has multiple associated models, Images.
One, and only one, of those images can be the key Image to its Post (that is represented as a boolean flag on the Image model and enforced through a validation on the Image model which uses the Post as its scope).
Now of course when I want to update the primary Image flag, it happens that an Image model's key flag is set to true and the validation fails because there's still another Image with the key flag set to true.
I know, that thin开发者_如何学Cg screams to be transformed into an association on the Post model, which links to the key Image, but is there a way to validate associations in bulk in Rails?
What would be your take, would you make the key Image a separate association on the Post model or would you use the boolean flag?
there is a simple solution but it needs some trust:
- Remove the validation "is there only one primary image?"
- Make sure there will be only one primary image by adding a filter
The big plus is that you don't have to check anything in your controller or post model. Just take an image, set is_primary
to true and save it.
So the setup could look like:
class Post < ActiveRecord::Base
has_many :images
# some sugar, @mypost.primary_image gets the primary image
has_one :primary_image,
:class_name => "Image",
:conditions => {:is_primary => true }
end
class Image < ActiveRecord::Base
belongs_to :post
# Image.primary scopes on primary images only
scope :primary, where(:is_primary => true)
# we need to clear the old primary if:
# this is a new record and should be primary image
# this is an existing record and is_primary has been changed to true
before_save :clear_primary,
:if => Proc.new{|r| (r.new_record? && r.is_primary) || (r.is_primary_changed? && r.is_primary) }
def clear_primary
# remove old primary image
Image.update_all({:is_primary => false}, :post_id => self.post_id)
end
end
Edit:
This will work in any case - why?
- before_save is only invoked if all validations succeed
- the whole save is wrapped in a transaction, this means if clear_primary or the saving of the image itself fails, everyhing will be rolled back to it's original state.
Well you can do this within your Post model:
# Post.rb
has_many :images, :conditions => ['primary = ?', false]
has_one :primary_image, :conditions => ['primary = ?', true]
When you want to change the primary image, do something like this:
# Post.rb
def new_primary_image(image_id)
primary_image.primary = false
Image.find(image_id).primary = true
end
精彩评论