Creating tags that link to many articles

Hello everyone.

I’ve recently finished the RoR tutorial and decided to try to build an example blog application.

I’m trying to implement functionality that allows:

  1. A link that will just list all articles in a nice way.
  2. A link that will list all tags and the articles that they reference

I’m having trouble with 2) though. Everything I need for an Article is easy enough to generate with scaffolding but I’m having trouble figuring out how to implement tags.

My thought process around this was to have a Tags text box on the form. I would scan that string, extract the tags (space-seperated) sequences of characters that match the pattern [a-zA-Z0-9]. Then build the association between that article and a tag when needed.

My Article model has a has_many :tags association. The db migration for tags has a foreign_key referencing an article_id. I think that this is enough.

I’m getting weird behavior on the front-end though with the form. Specifically, the forms tag text box is being auto populated with a: #Tag::ActiveRecord_Associations_CollectionProxy:0x00007f4931244308

The error occurs with the strong parameters for the article. I think that this might be caused by the symbol name for tags colliding in certain aspects, the Article model has a tags attribute which is a text field, the forms label and text_area references the same symbol, i.e. :tags. I’m not too familiar with Ruby but I know symbols should be unique, so it’s probably suspect to have two “meanings” of :tags as i’ve listed.

Does anyone have any advice for me? Am I misunderstanding something pretty major here? Any help would be appreciated.

Welcome to Rails! Glad to see you’re digging right in and making something you want to use as a learning exercise.

Tagging has been a well-solved problem in Rails for many many years. Here’s a very partial list of gems that purport to solve this for you, many of which are based on acts_as_taggable, which has been around since Rails 2 was a thing. Category: Rails Tagging - The Ruby Toolbox

If you want to learn more about this topic by trying to implement it yourself, you might want to dig into one or more of the gems listed above, and see if they offer any clues as to how they go about solving this problem.

In very broad strokes, many tagging systems provide a polymorphic relationship between one or more base models (the objects in your application) and many tags. If you add one of those gems to your application, you will be able to inject tagging behavior into your individual models, rather than having to build a separate table to link each model to the common Tag class. If you were going to build this long-hand, you might do something like this:

# tag.rb
class Tag < ApplicationRecord
  has_many :taggings
  has_many :taggables, through: :taggings
end

# tagging.rb
class Tagging < ApplicationRecord
  belongs_to :taggable, polymorphic: true
  belongs_to :tag
end

# post.rb
class Post < ApplicationRecord
  attr_accessor :tag_names
  has_many :taggings, as: :taggable
  has_many :tags, through: :taggings

  def tag_names
    tags.pluck(:name).join(' ')
  end

  def tag_names=(val)
    tags = val.split(' ').uniq.map { |name| Tag.where(name: name.downcase).first_or_initialize }
    self.tags = tags
  end
end

You would need to create those models and tables, and I’m sure I have some ugly bugs in there because I typed it all off the top of my head, but that’s the basic bits that a tagging gem is going to give you.

In your view, you could loop over the post’s tags and render each one in a little graphical widget. Each of those tags could link to a page showing all the things tagged with that tag name, too.

In your post#edit form, you would create a text field called tag_names (also be sure to add that to your strong parameters definition in your controller) and by adding your space-delimited tags there, you could edit which tags are assigned to that post.

There are a lot of optimizations that you could add to this as you go, and reading some of those famous tagging gems’ source may give you some ideas about that. Certainly there are benefits to adding indexes on the tag name and compound indexes on the taggings table. But do that later, after you’ve gotten it to work at all, and you start to notice the need for speed.

And the moment that you need to add this to more than one of your application’s classes, you’ll want to extract those tag_names* methods from Post and put them in a module, which you could include in each of your “taggable” classes. Have a read in the Concerns documentation, as that’s a perfect fit for this part of the job. ActiveSupport::Concern

Walter

2 Likes

Thank you Walter. I appreciate the in depth response.

This was obviously a lot more complicated then I thought!

I will try to digest what you’ve wrote here and go from there.

If you don’t want to go all the way down the rabbit hole of making tags have a polymorphic relationship, you could probably get what you have so far to work by adding the attr_accessor and the two tag_names methods to your Article model. In your articles_controller, add :tag_names to the array of fields permitted in your strong parameters, and then add a field in your form named :tag_names. With those things (and assuming that you called your Tag identity column name, as I chose that in my example code), this has a strong chance of working right away.

Give it a shot, and then rip it all out and do it again in a more flexible manner!

Walter

1 Like

I had forgotten how old acts_as_taggable was. Here’s a reference to it in a book from 2005: https://media.pragprog.com/titles/fr_rr/Tagging.pdf

Rocking Rails version 1.1 in that edition!

Walter

1 Like

Hi Walter.

So I was able to get this to work after some meditation on your post.

I wasn’t sure if we needed a polymorphic association here. I think what I was missing was that extra layer of abstraction between tags and the article, i.e. taggables, that which can be tagged to something.

So here are my associations now:

Article → has_many: tags, has_many :taggables, through: tags || Tag → belongs_to: :article, belongs_to: taggable || Taggable → has_many :tags, has_many :articles, through: :tags

Of course I set up the tags table to reference the article_id and taggable_id.

The way I’m building the appropriate associations is through the tag: article.tags.create(taggable_id: taggable.id) after grabbing the taggables that i want to tag an article to.

Does that sound OK? Is there are more Rails way to do something like this?

P.S. Thanks again for your help I really appreciate it!

I may be misreading what you are naming things here. If you’re not using the polymorphic relation, you don’t need taggables. That’s just the name of the polymorphic adapter in my example. You would still need taggings, as that’s the join table between a tag and the things that it tags. Here’s how I would structure this without the taggable abstraction:

# article.rb
class Article < ApplicationModel
  has_many :taggings
  has_many :tags, through: :taggings
end

# tagging.rb
class Tagging < ApplicationModel
  belongs_to :article
  belongs_to :tag
end

# tag.rb
class Tag < ApplicationModel
  has_many :taggings
  has_many :articles, through: :taggings
end

If you look at the Tagging model, you’ll see that it has a direct relationship with an Article. Since that’s the only kind of thing you are going to tag, you don’t need the polymorphic “taggable” adapter part. You can read more about the polymorphic adapter here: Active Record Associations — Ruby on Rails Guides

I hope this makes sense to you. Please let me know if not.

Walter

1 Like

That makes sense.

That structure is pretty much what I ended up doing at the end.

Thanks for your help again!