growing up your acts_as_taggable

The acts_as_taggable plugin, although built in to Rails, is basically deprecated. And the gem version is old and requires a separate join table for every taggable model, which is silly.

update

Please see the updated has_many_polymorphs documentation, which has instructions for using the new, built-in tagging generator.

install

Uninstall whatever acts_as_taggable version you have, to prevent fighting among the plugins. Then, install has_many_polymorphs:

script/plugin install -x svn://rubyforge.org/var/svn/fauna/has_many_polymorphs/trunk

models

We will assume you are using the default acts_as_taggable models. You should have a Tag model, with a name field, as well as a Taggings join model. However, these models are part of the acts_as_taggable plugin source, whereas their migrations have to be part of your app (which is confusing). Instead, with has_many_polymorphs, we have to make the models part of the app, too:

class Tag < ActiveRecord::Base
  has_many_polymorphs :taggables,
    :from => [:books, :magazines],
    :through => :taggings,
    :dependent => :destroy
end

class Tagging < ActiveRecord::Base
  belongs_to :tag
  belongs_to :taggable, :polymorphic => true

  def before_destroy
    # disallow orphaned tags
    tag.destroy_without_callbacks if tag.taggings.count < 2
  end
end

See the line [:books, :magazines]? This line replaces all the acts_as_taggable macro calls strewn through your models. Simply list in the array the models that you want to be able to tag. (You can even tag Tags, see below.)

migrations

We need to make sure your existing tag schema fits this. It should be something like:

class AddTagSupport < ActiveRecord::Migration
  def self.up
    create_table :tags do |t|
      t.column :name, :string, :null => false
    end
    add_index :tags, :name, :unique => true

    create_table :taggings do |t|
      t.column :tag_id, :integer, :null => false
      t.column :taggable_id, :integer, :null => false
      t.column :taggable_type, :string, :null => false
    end
    add_index :taggings, [:tag_id, :taggable_id, :taggable_type], :unique => true
  end

  def self.down
    drop_table :tags
    drop_table :taggings
  end
end

If your schema isn’t like this already, you have two choices. You can add more options to the association macros in your models, to show them how to relate to your schema, or you can write a migration to convert your schema into canonical form. The first option is possibly quicker to implement, but the second one will be easier to maintain.

api

Hey, you’re done! Well, not really. The API is different, which is the biggest sticking point. We’ll write some convenience methods to mimic the old way. You should put the methods in RAILS_ROOT/lib/tag_extensions.rb or similar.

class ActiveRecord::Base
  def tag_with tags
    tags.split(" ").each do |tag|
      Tag.find_or_create_by_name(tag).taggables << self
    end
  end

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

Pretty straightforward, which makes it easy to modify. For example, to make creating a model from params more transparent, we could add:

alias :tags= :tag_with

Or to enforce lowercase tags, just do:

def tag_with tags
  tags.downcase.split(" ").each do |tag|
    Tag.find_or_create_by_name(tag).taggables << self
   end
end

If you want to allow tags with spaces in them, we can accept an array instead of a string:

def tag_with *tags
  tags.flatten.each do |tag|
    Tag.find_or_create_by_name(tag).taggables << self
  end
end

To delete tags (don’t forget to downcase the tag_string if you need to):

def tag_delete tag_string
  split = tag_string.split(" ")
  tags.delete tags.select{|t| split.include? t.name}
end

To get all models for a tag (and all in a single SQL query, thanks to the plugin):

Tag.find_by_name("artichoke").taggables

To get only a specific model for a tag:

Tag.find_by_name("artichoke").books

Easy and powerful. And if you need to find which tags are most popular, see here.

Make sure that you require the file containing your API methods in environment.rb, since Rails won’t load it automatically:

require 'tag_extensions'

performance note

It is more efficient if we add :skip_duplicates => false to the has_many_polymorphs :taggables call. Then the taggables for each Tag won’t get loaded at all during the <<, because there is no reason to check them.

If we do this, though, and use database constraints to enforce uniqueness, we need to manually rescue assignment errors in our tag_with method:

def tag_with tags
  tags.split(" ").each do |tag|
    begin
      Tag.find_or_create_by_name(tag).taggables << self
    rescue ActiveRecord::StatementInvalid => e
      raise unless e.to_s[/Duplicate entry/]
    end
  end
end

self-referential tagging

What if you want to be able to tag tags? This is more useful than might first appear. It lets you create an ad-hoc non-exclusive hierarchy of categories without extra models. This is much more maintainable than one based on hard-coded models for each level. But you need some way to distinguish “the tags tagged by a tag” and “the tags a tag is tagged by”—a directed graph. So we will rename the parent relationship:

class Tag < ActiveRecord::Base
  has_many_polymorphs :taggables,
    :from => [:books, :magazines, :tags],
    :through => :taggings,
    :dependent => :destroy,
    :as => :tagger
end

class Tagging < ActiveRecord::Base
  belongs_to :tagger,
             :class_name => "Tag",
             :foreign_key => "tagger_id"
  belongs_to :taggable,
             :polymorphic => true
end

Modify the migration accordingly. Now, you can use some_tag.taggables to get the targets of some_tag, and some_tag.taggers to get the tags for which some_tag is itself a target.

Note that your tag_delete method might have to be more longwinded due to this long-outstanding Rails bug. (Rails 1.2.3 might be ok now; it’s unclear.)

wrapping up

You should find, once you complete the move, that tag lookup is faster, that the API is more intuitive and in line with regular ActiveRecord, that database compatibility is improved, and that the subsystem is more extensible. Quite a benefit!

Since has_many_polymorphs is basically a huge superset of the acts_as_taggable features, there is not much that isn’t supported. Many people use has_many_polymorphs in production for tagging already. Join them!

72 responses

  1. Chris, I thought about that, but I think most people need to eventually customize the way _taggable works. So I would rather not lock them down—even if only in appearances.

    And I dislike publishing arbitrary APIs, like _taggable does. That’s not learning; that’s memorization.

  2. First off, this looks like a great plugin—no more need to enumerate every reverse has_many / belongs_to relationships between tags and their various taggable models (e.g. tag.posts, tag.comments, etc…).

    In the self-referential tagging paragraph, however, shouldn’t it be: :from => [:books, :magazines, :tags] instead of: :from => [:books, :magazines]

    Otherwise, how does the some_tag in some_tag.taggers know what to look up? From what I understand, adding :tags to the :from array makes the Tag class a polymorph so that you can tag some_tag with another_tag whereby the taggings table stores:

    1. some_tag as a taggable (e.g. taggable_id = some_tag.id & taggable_type = some_tag.class) and, 2. another_tag as a tagger (e.g. tagger_id = another_tag.id)

    Is this completely wrong? If it is, could you clarify and expand that part of the post for this noobie? Thank you.

  3. You’re exactly right; my oversight.

    While we’re on the topic, when you have the self-reference defined as above, you get the following methods on Tag:

    Tag#taggers
    parent Tags of an instance

    Tag#taggables
    all polymorphic children of an instance (Books, Magazines, and Tags)

    Tag#tags
    only child Tags of an instance

    Without the :as => :tagger key, there is no way to distinguish child Tags from parent Tags, and _polymorphs will throw an error if you include :tags in the :from => [] array.

    Tag#tags is not specially named (child_tags, etc.) because it needs to parallel Tag#books and Tag#magazines.

    People always find this to be the most confusing part, but I’m not sure there’s much I can do about it. Self-reference is kinda confusing by nature.

  4. If you were to add a user_id to the taggings table above to enable sorting by user, e.g.:

    
    t.column :user_id, :integer, :null => false
    
    class Tagging < ActiveRecord::Base
      belongs_to :tagger
      belongs_to :user
      belongs_to :taggable, :polymorphic => true
    end
    

    how would you go about filtering a tag’s taggables (or a subset of taggables such as books or magazines) using a specific user_id? Can we add something akin to :conditions => to the has_many_polymorphs :taggables declaration in the Tag model to make this work?

    If there is a workable method to achieve the preceding, can we also use this method to filter one association with one criterion, say Tag#books with taggings.user_id, and another association with a different criterion, say Tag#magazines, with taggings.created_on? Scratching head?!?

  5. You can do that on the individual collections:

    >> tag.books.find(:all, :conditions => 'taggings.user_id = 2')

    You can’t yet use a custom finder against the polymorphic collection because of the way the proxy object works. I plan to fix that in a couple of weeks.

    For now you can find against the join model and map:

    >> tag.taggings.find(:all, :conditions => 'user_id = 2').map(&:taggable)

    but it’s not efficient for large collections because the map makes it O(n) instead of O(1).

  6. Thank you. I’m still learning the ins and outs of this very handy plugin and I was hoping there might be a magic incantation that I’d yet to discover. ;-)

  7. Evan, rather than adding to ActiveRecord::Base, could you not create a mixin and reference it in every class you wish to tag?

  8. Do you mean that you would reference the mixin in every child class of the relationship, or only in the parent class?

  9. This forum thread discusses what an approach like that might entail. It’s kind of a toss-up, but to me it makes a more maintainable app if the details of the relationship are specified in a centralized place.

  10. Thanks for the link Evan – it explained what I was trying to do. My app is in a state that I probably won’t need to use the convenience methods as you’ve described but code my own in the Tag model. Thanks again.

  11. A few things..

    1. The has_many_polymorphs page says version 22 was released Jan 2006 instead of 2007

    2. The code in the self-referential tagging section should say :as => :tagger, not :as => tagger

    3. With the self-referential setup, Rails doesn’t know to eager-load the Tag model:

    >> Place.find(1).tag_list
    NameError: undefined local variable or method
    `taggers' for #<Place:0x2782e64>
    >> Tag
    => Tag
    >> Place.find(1).tag_list
    => "apartment, house"
    

    4. Where would you recommend putting the API code. I currently have it in environment.rb, but this feels wrong.

    5. How would you go about removing tags. Is there a better way than what I came up with.

    def remove_tags tags
      tags.downcase.split(',').each do |tag|
        t = Tag.find_by_name(tag.strip)
        if t and taggings.find_by_tagger_id(t)
         taggings.find_by_tagger_id(t).destroy
          t.destroy if t.taggings.size == 0
        end
      end
    tag_list
    end
    
  12. 1 and 2: Fixed, thanks.

    3. Missing methods is a common problem; it’s an inversion of control issue. Polymorphs dynamically adds methods to the child classes at runtime. But it can’t kick in until the Tag model makes the has_many_polymorphs macro call, and Rails doesn’t like to load a model until the last possible minute, which in this case is too late. One fix is to reference the Tag constant, all on its own, in application.rb. This ensures that the polymorphic methods will get injected on every request.

    Read this for more details and an alternate solution, especially when working in the console.

    4. I recommend making a file in /lib called activerecord_base_tag_extensions.rb and requiring that in environment.rb. If you don’t mind being a little dirty you could just stick the code after the Tag class declaration in app/models/tag.rb. (If adding the methods onto Base seems too invasive, you could put the methods in a module and manually include that module in each of your taggable classes.)

    5. How about:

    class ActiveRecord::Base
      def delete_tags tag_string
        split = tag_string.downcase.split(" ")
        tags.delete tags.select{|t| split.include? t.name}
      end
    end
    

    And then in the Tagging model:

    class Tagging
      def before_destroy
        tag.destroy_without_callbacks if
            tag.taggings.count < 2
      end
    end
    

    Note that ActiveRecord already has an internal method called remove_tags() which can cause trouble.

  13. Hi

    I have the same issue as no. 5 in the last comment. In this I mean that I have still have the entry in the taggings table with taggable_id == 0.

    Then I tried the above fix but I can’t seam to get the after_save to react on the tags.delete.

    Any suggestions?

  14. Ok. We need to add :dependent => :destroy to the has_many_polymorphs call and use before_destroy.

    The situation is a bit confused. .delete on a has_many :through skips join record save callbacks. Its default behavior is to nullify the foreign key (in this case in the join record), even though this doesn’t make sense with a has_many :through (because a join record is useless without both keys). With MySQL, if you nullify a :null => false integer field, it assigns zero instead. But if we add :dependent => :destroy to the has_many_polymorphs call (which will only affect the join record, and not the association target), then the before_destroy callback will get hit, and subsequently the join record will get destroyed.

    I’m gonna update the examples above.

  15. Changing the tags.delete to tags.destroy also seems to work. The delete method issues a direct SQL query, whereas the destroy method ensures that the before/after_save methods are invoked.

    While debugging, I discovered this interesting warning your code triggers:

    >> Tagging.find(2).tag.taggings_count
    DEPRECATION WARNING: taggings_count
    See http://www.rubyonrails.org/deprecation for details.
    (called from taggings_count at
    active_record/deprecated_associations.rb:7)
    => 12
    

    Unfortunately there is no information on the provided link yet.

    Finally, how would I prevent caching as such:

    >> p = Place.find(1)
    >> p.tags
    => ""
    >> p.add_tags 'food'
    >> p.tags
    => ""
    >> Place.find(1).tags
    => "food"
    >> p.tags
    => ""
    

  16. Upon further inspection, destroy does not produce the desired results. Using delete with :dependent => :destroy works.

  17. I guess in Rails 1.2 we’re supposed to use taggings.count instead.

    Regarding the caching, you need to be able to reach the .reload method on the .tags collection somehow. This will be difficult if you’ve overridden .tags with your own method. But otherwise, just adding a tags.reload in the tag_with or add_tags method will clear it up. Same goes for delete_tags. I didn’t include them above because in most cases it doesn’t matter if the instance goes slightly stale within the action, so there is no reason to force another database hit.

    You could implement your own mini-cache within the class to keep tag_list synchronized if this is critical to you.

  18. Using the code now posted, I’m getting an infinite recursion:

    >> video = Video.create()
    => #<Video:0xb71b03fc...>
    >> video.tag_with('test')
    => ["test"]
    >> video.reload
    => #<Video:0xb71b03fc...>
    >> video.tags
    => [#<Tag:0xb719b218
      @attributes={"name"=>"test", "id"=>"4"}>]
    >> video.tags.delete video.tags[0]
    ActiveRecord::StatementInvalid:
      SystemStackError: stack level too
      deep: SELECT * FROM taggings WHERE
      (taggings.tag_id = 4)
    
    (big stacktrace)
    

    If I’m not wrong, I think the before_destroy in the tagging is calling the tag.destroy, which is then calling the tagging.destroy (and so on), or am I just not seeing something?

  19. I thought about that possibility, but it worked in my console so I thought ActiveRecord was smart enough to avoid it. Does .destroy_without_callbacks work for you instead?

  20. Given a list of tags, I wrote two functions to return all associated Places:

    def search_and(tag_string) # find_tagged_with_all
      @places = Array.new
    
      tag_string.split(',').collect { |tag|
          Tag.find_by_name(tag.strip).places
      }.each_with_index { |p,i|
        if i == 0
          @places = p
        else
          @places = (@places & p).uniq
        end
      }
    end
    
    def search_or(tag_string) # find_tagged_with_any
      @places = tag_string.split(',').collect { |tag|
          Tag.find_by_name(tag.strip).places
      }.flatten.uniq
    end
    

    These are elegant, but far from optimal. I’m not sure where to start looking in terms of speeding these functions up. Should I run straight SQL queries instead, or is there a cleaner way?

    Also, to get counts of all tags, I have:

    def tags_count
      Hash[*Tag.find(:all).map {|t|
        [t.name, t.taggables.count]
      }.flatten]
    end
    

  21. Aman, I was just talking with David Bluestein about these same issues.

    For getting counts of tags, make sure you see this.

    You are right that the combinatorics are bad for finding subsets. Caching can mitigate a lot of this. I had basically the same solution as you, but shorter:

    def find_for_all s; find_for_ '&', s; end
    def find_for_any s; find_for_ '|', s; end
    
    def find_for_(op, tag_string)
      tag_string.split(" ").inject(nil) do |i, name|
        set = Tag.find_by_name(name).taggables
        i ? i.send(op, set) : set
      end
    end
    

    Once some upcoming changes to the plugin are in place, I should be able to give you O(1) methods instead of O(n).

    Note that both & and | are set operations, so you don’t need uniq.

  22. Thanks Evan, looking forward to the has_many_polymorphs update.

    With the self-referential tagging setup, the tagging model doesn’t work as expected.

    class Tag < ActiveRecord::Base
      has_many_polymorphs :taggables,
        :from => [:places, :tags],
        :through => :taggings,
        :dependent => :destroy,
        :as => :tagger
    end
    
    class Tagging < ActiveRecord::Base
      belongs_to :tagger
      belongs_to :taggable, :polymorphic => true
    end
    
    ['amex', 'visa', 'discover', 'mastercard'].each { |t|
      Tag.find_by_name(t).tag_with 'payment type'
    }
    
    Tag.find_by_name('payment type').tags.collect {|t|
      t.name
    }
    => ["visa", "mastercard", "discover", "amex"]
    

    Everything so far works wonderfully. Now, if I try to use the Tagging model to find tagged tags:

    >> Tagging.find(:all,
         :conditions => 'taggable_type = "Tag"').each {|t|
      puts t.tagger.name + ' ' + t.taggable.name
    }
    NameError: uninitialized constant Tagging::Tagger
    

  23. You need :class_name => "Tag" and :foreign_key => "tagger_id" keys in the belongs_to :tagger macro in tagging.rb. My fault.

  24. Hey Evan,

    I like what you’ve got going here, but I’m just too thick to ‘get it’.

    First off, I’m trying to get tag_with and tag_list to work but I can’t seem to do it. All I get is “undefined method `tag_list'” I put the code after the class Tag declaration as well as in a separate file in /lib/ but I still get the error.

    Second, I’m trying to figure out the self-referential Tagging bit but I don’t know how to “Modify the migration accordingly”.

    Lil help? Thanks!

  25. Thanks for the help in IRC Evan.

    For others’ benefit:

    I got the Self Referential migration figured out (replace the tag_id column with tagger_id)

    Once that was in place, tag_list needs to be changed to:

    def tag_list
      taggers.map(&:name).join(' ')
    end
    

    And don’t forget to reference ‘Tag’ when playing in the console!

  26. Evan—I’m new to this, sorry. Where in my app do I put the tag_with and tag_list methods?

    In the tags model?

  27. Ran an svn up today and the updates seem to have broken something:

    Loading development environment.
    /active_support/dependencies.rb:267:in `load_missing_constant':NameError: uninitialized constant ActiveRecord::Associations::HasManyPolymorphs::ClassMethods
    >> Tag
    NoMethodError: undefined method `has_many_polymorphs' for Tag:Class
    

  28. I can’t seem to reproduce your issue. What version of Rails are you on? Anything else unusual about your environment? If you incrementally roll back, can you find a prior revision number that works?

  29. Found the problem:

    aman$ ls vendor/plugins/acts_as_ferret/lib/
    acts_as_ferret.rb
    class_methods.rb
    instance_methods.rb
    more_like_this.rb
    multi_index.rb
    

    Another plugin with files of the same name.

    Is this an ActiveSupport::Dependencies bug?

  30. Hey, good spelunking. I wouldn’t really say it’s Rails a bug necessarily. require tracks loaded dependencies based on the string by which they were requested, so class_methods.rb only gets loaded once if it is not identified by a more explicit path. Since acts_as_ferret starts with a, its folders came first when ActiveSupport::Dependencies built up the load paths (of which there are a lot).

    See if the latest svn works for you.

  31. I was wondering what the “before_destroy” method for the Tagging class was doing exactly.

    And also why it was ”< 2” not ”< 1”

  32. Brian, the before_destroy prevents you from having Tag records that aren’t associated with any taggable.

    Each time a Tagging relationship is about to be deleted, the callback is run, and checks if it is the only tagging for that Tag. (Since the tagging we are deleting still exists at this point, we have to check that there are not two, rather than not one.) If it finds it is the only relationship left, it deletes the Tag before allowing itself to get deleted.

    The _without_callbacks prevents the infinite loop Giao mentions above.

    >> Recipe.create(:title => "Soup")
    >> _.tag_with "hot spicy"
    >> Recipe.create(:title => "Buffalo wings")
    >> _.tag_with "hot spicy"
    >> Tag.find_all.map(&:name)
      => ["hot", "spicy"]
    >> Recipe.find(1).tag_delete "spicy"
    >> Tag.find_all.map(&:name)
      => ["hot", "spicy"]
    >> Recipe.find(2).tag_delete "spicy"
    >> Tag.find_all.map(&:name)
      => ["hot"]
    
  33. With the following code:

    p = Product.new(:name => "foo")
    p.tag_names = "foo bar"
    assert_equal ["foo","bar"],
      p.tags.collect {|t| t.name}
    assert_equal "foo bar", p.tag_names
    p.save!

    I get this error:

    ActiveRecord::Associations::PolymorphicError: You
    can't associate unsaved records.
    

    I’ve defined tag_names= and tag_names methods instead of tag_with and tag_list. If I do p.save before I call tag_names=, then it works. So is that just the way it works, you have to have a saved taggable before you can tag it? That’s kind of a limitation if you are making a form that allows a user to create a taggable object and tag it at the same time, but I suppose I could just save the object and then update it with the tags if I have to.

  34. This is a limitation inherited from has_many :through:

    >> Recipe.new.ingredients << Ingredient.new
    
    ActiveRecord::HasManyThroughCantAssociateNewRecords:
    Cannot associate new records through
    'Recipe#ingredients_recipes' on '#'. Both records
    must have an id in order to create the has_many
    :through record associating them.
    

    I recommend just saving the record first before associating. It is technically possible to add build() support to the plugin, which is what is at issue here, but it’s a lot of work for little benefit.

    Is this functionality critical to you?

  35. Don’t know if I correctly understood this polymorphic thing.

    It will not be possible to use a foreign key for taggable_id, since it will be receiving ids from all my taggable models, is this correct?

    If so, are there some other thing that can be done to guarantee or maximize referential integrity?

  36. When I use the following to save an article and its tags:

    @article.save
    @article.tag_with(params[:tags])
    

    What I do to validate tag input? I tested validate methods inside tag model, but they seem not work.

  37. James, you can use Rail’s validations and callbacks, as well as :dependent => :destroy, to guarantee that you can’t get invalid records in the Tagging column. How exactly it needs to work will be dependent on your particular app, though. You might be able to use a stored procedure to implement a fancy conditional constraint in your database itself, but I doubt it’s worth the effort.

    Fmc, what exactly doesn’t work about it?

  38. I keep getting:

    undefined method `alias_method_chain' for
    #<Class:ActiveRecord::Base> (NoMethodError)
    

    What version of Rails is this plugin/gem compatible with?

    I’m also using ferret so not sure if that is causing a conflict as it did with Aman Gupta.

    Our app is running Rails 1.1.6 and I have tried both the gem and the plugin of has_many_polymorphs (version 27).

    Cheers

  39. Version 24 was the last version that supported Rails 1.1.6 (there is a changelog in the plugin’s README.txt which mentions this).

    What’s keeping you from upgrading? Just curious.

  40. Cheers Evan…I should have checked the changelog…

    Basically we have developed a monster of an application at work and the test suite is enormous…Rake on our CI server takes around 45 minutes…

    We have a lot of assert_tag statements in our functional tests and deadlines looming…Basically the overhead involved in correcting our tests once switching to 1.2.x is enormous…

    Once we hit these deadlines and start decoupling our app into manageable chunks we’ll definitely look to upgrade…

    I’ll give version 24 a go…

  41. As of version 27.1 of the plugin, you don’t have to worry about referencing or requiring the parent class (Tag) before you use the injected methods. The plugin should auto-discover and preload it.

  42. I’m always getting the undefined local variable or method `tags' error when I use tag_list without calling Tag someway.

    For example:

    def edit
      @article = Article.find(params[:id])
    end
    

    And in the view:

    @article.tag_list
    

    The exception is raised. But if I just put Tag in the controller, it works:

    def edit
      Tag
      @article = Article.find(params[:id])
    end
    

    Is this common behavior?

  43. Fernando, there was a bug related to turning on the has_many_polymorphs_cache_classes option in config/environments/development.rb. This is now deprecated in 27.2; please use Rails’ regular config.cache_classes (not in the after_initialize block) if you don’t want model reloading.

    Hopefully this resolves the issues. Rails’ dependency code is byzantine.

    If the behavior persists for you, please post on the forum with details about your Rails environment, etc., and we’ll work it out.

  44. Just an update…

    We’ve now almost completed the upgrade to Rails 1.2.3.

    Thanks to cruisecontrol.rb and has_many_polymorphs amongst a few other factors we’ve convinced the powers that be that the upgrade was worth the effort.

    Cheers.

  45. I couldn’t get the migration to run, with this error:

    Mysql::Error: Table 'writr_development.taggings' doesn't exist:

    But it turned out the problem had something to do with Hobo—I removed the tag and tagging model files temporarily and the migration ran.

  46. Hi Evan—

    I installed and got the nice message:

    There is an experimental tagging system generator
    in place.
    ./script/generator tagging TaggableModel1
    TaggableModel2 [..]
    

    However, I tried it and got this error:

    script/generator tagging Card
    tcsh: script/generator: Command not found.
    

    and switching ‘generator’ to ‘generate’ got:

    ./script/generate tagging Card
    Couldn't find 'card' generator
    
  47. Hey all… I’m gonna try to work on the tagging system tonight, and finish the generator. Watch for a blog post shortly.

    In other news, it’s probably better to post problems on the forum.

  48. I get the following error when I use the technique described above for tagging:

    Unknown key(s): source_type
    
  49. Like Noobie, I want to have tagging to belong to an user. I modified the models accordingly but now I have to modify tag_with to be called with one more argument to something like:

    def tag_with(tags, user)
      tags.split(" ").each do |tag|
        t = Tag.find_or_create_by_name(tag)
        # this has to do also user_id = user.id somehow
        t.taggables << self
      end
    end
    

    But I don’t know how to do it. Thanks for any suggestions.

  50. So I have an input field on my model for tags. If I put in “outdoors water” and click save, the tags save just fine.

    If I then edit it and add “outdoors water summer”, the ‘summer’ tag is added. But if I edit again and remove a tag: “outdoors summer”… the “water” tag still remains.

    Do you have a “best practice” to deal with this issue?

  51. Is there any reason why taking the taggable code from the generator and placing it into vendor/plugins should not work? I’m requiring tagging_extensions in init.rb and the models are found okay, but I’m still getting the message SomeModel is not a taggable model.

    Thanks!

  52. Nice work. Seems like a lot of extra functions I need to write just to get tagging working with basic features that either the acts_as_taggable gem or plugin already have, though. Like related tags, for example.

  53. LLG, I found a post on RubyForge which solves your user problem:

    def tag_with(tags, user)
      tags.split(" ").each do |t|
        tag = Tag.find_or_create_by_name(t)
        Tagging.create(:tag => tag,
          :taggable => self, :user_id => user.id)
      end
    end
    
  54. It seems that I am not able to create tag that are numbers. Any hints?

    >> a.tag_with('qqq www eee 111')
    ActiveRecord::RecordNotFound:
    Couldn't find Tag with ID=111
    

    Notice that the last select statement is using tags.`id` instead of tags.`name`:

      Tag Load (0.000362)
      SELECT * FROM tags WHERE (tags.`id` = 111)
    
  55. Lei, if you’re using the generator, it’s because of this line in tag_cast_to_string():

    when /^\d+$/, Fixnum then Tag.find(item).name
    

    Just remove it, and you should be fine.

    It’s better to post questions on the forum these days. Someone is more likely to help you sooner.

  56. I’m trying to add tagging to my little camping app, but I think I’m having trouble with the gem verson of has_many_polymorphs.

    The end of the trace looks like

    !! trouble loading goodies: [NameError] uninitialized
    constant Link
    

    Link is a model name which I want to add tagging to. Maybe it has something to do with the different naming conventions in Camping? Please let me know if there’s anything I can do to make this work.

  57. Assign all your models to toplevel constants, and it should be able to figure it out:

    App::Models.constants.each do |c|
       eval "::#{c} = App::Models::#{c}"
    end