I am trying to implement a rails tagging model as outlined in Ryan Bate's railscast #167. http://railscasts.com/episodes/167-more-on-virtual-attributes
This is a great system to use. However, I cannot get the form to submit the tag_names to the controller. The definition for tag_names is :
def tag_names @tag_names || tags.map(&:name).join(' ') end
Unfortunately, @tag_names never gets assigned on form submission in my case. I cannot figure out why. SO it always defaults to tags.map(&:name).join(' '). This means that I can't create Articles because their tag_names are not there, and I also can't edit these tags on existing ones. Anyone can help?
In short, your class is missing a setter (or in Ruby lingo, an attribute writer). There are two ways in which you can define a setter and handle converting the string of space-separated tag names into Tag objects and persist them in the database.
Solution 1 (Ryan's solution)
In your class, define your setter using Ruby's attr_writer
method and convert the string of tag names (e.g. "tag1 tag2 tag3"
) to Tag objects and save them in the database in an after save callback. You will also need a getter that converts the array of Tag
object for the article into a string representation in which tags are separated by spaces:
class Article << ActiveRecord::Base # here we are delcaring the setter attr_writer :tag_names # here we are asking rails to run the assign_tags method after # we save the Article after_save :assign_tags def tag_names @tag_names || tags.map(&:name).join(' ') end private def assign_tags if @tag_names self.tags = @tag_names.split(/\s+/).map do |name| Tag.find_or_create_by_name(name) end end end end
Solution 2: Converting the string of tag names to Tag
objects in the setter
class Article << ActiveRecord::Base # notice that we are no longer using the after save callback # instead, using :autosave => true, we are asking Rails to save # the tags for this article when we save the article has_many :tags, :through => :taggings, :autosave => true # notice that we are no longer using attr_writer # and instead we are providing our own setter def tag_names=(names) self.tags.clear names.split(/\s+/).each do |name| self.tags.build(:name => name) end end def tag_names tags.map(&:name).join(' ') end end