RailsCasts Pro episodes are now free!

Learn more or hide this

Bradley Griffith's Profile

GitHub User: bradley

Site: bradley.computer

Comments by Bradley Griffith

Avatar

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:

ruby
class Post < ActiveRecord::Base
  attr_accessible :content, :summary, :title, :tag_list, :tags

  # association code here...

  validates_associated :tag_list
  before_save :save_tags

  # validation code here...

  # other methods shown in original code...

  def tag_list
    caller[0][/`([^']*)'/, 1] == 'block in validate' ? @tag_list : tags.map(&:name).join(", ")
  end

  def tag_list=(names)
    @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
      Tag.find_or_initialize_by_name(name: n.strip)
    end
  end

  private

    def save_tags
      self.tags = Tag.transaction do
        @tag_list.each(&:save)
      end
    end
end

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.

Avatar

Here are the necessary model methods for any DataMapper users:

ruby
has n, :taggings
has n, :tags, :through => :taggings

def self.tagged_with(name)
  Tag.all(name: name).articles
end

def tag_list
  tags.map(&:name).join(", ")
end

def self.tag_counts
  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')
end

def tag_list=(names)
  self.tags = names.split(',').map do |n|
    Tag.first_or_create(name: n.strip)
  end
end

Hope that helps.