RailsCasts Pro episodes are now free!
Learn more or hide this
GitHub User: bradley
After a lot of investigating I found a way to run validations on tags using something very close to Ryan's code.
I'll just put the code that I changed:
class Post < ActiveRecord::Base
attr_accessible :content, :summary, :title, :tag_list, :tags
# association code here...
# validation code here...
# other methods shown in original code...
caller[/`([^']*)'/, 1] == 'block in validate' ? @tag_list : tags.map(&:name).join(", ")
@tag_list = names.split(",").map do |n|
#self.tags.find_or_initialize_by_name(name: n.strip) #uncomment this if you want invalid tags to show in tag list
self.tags = Tag.transaction do
A little explanation: I found that adding Tag objects to @post.tags automatically validates, and I believe attempts to save, the Tag object. So if any Tag object is invalid, it's validation will run, and fail, when the array of Tag objects is set to the value of self.tags in the tag_list= method.
(The method used by the update method in the PostController to update the model, update_attributes, automatically saves the entry if it's valid by the way.)
So, we need a way then to validate the Tag objects when the post object is being validated. So what what we can do is store the array of Tag objects generated in the tag_list= method to an instance variable called, wait for it, '@tag_list'. By adding :tag_list to our list of attr_accessible items, we can then use validates_associated to run validations on @tag_list when the post object is validated.
Because we named the array @tag_list, errors will be returned as errors on the tag_list attribute that we access for our post form. Which is good.
If the post object successfully saves -- meaning everything, including the Tag objects, validated -- we use a before_save hook to save the array of Tag objects stored in the tag_list and update self.tags.
The only thing left to do was to update the tag_list method so that it returns the string of tags except when the method is being called by a validation method, in which case it returns the @tag_list array.
Here are the necessary model methods for any DataMapper users:
has n, :taggings
has n, :tags, :through => :taggings
DataMapper.repository.adapter.select('select tags.*, t.count from tags inner join (select taggings.tag_id, count(taggings.tag_id) as count from taggings group by taggings.tag_id) t where tags.id = t.tag_id')
self.tags = names.split(',').map do |n|
Hope that helps.