Create Tag functions to Rails without gem

Why I decided to make the Tag functions by myself

If you’re developing with Rails, I think you’ve heard of this famous gem acts-as-taggable-on. You might ask me, “why don’t you use this gem?”. This gem helps you to create the Tags so easily, even you don’t need to think about the database structure because this gem will generate the basic one.

But there’s a weak point, as the other gem also has. It is…

  • not easy to modify
  • takes time to know their own rules

It takes time in some cases to add your own change to the gem. Because you need to understand where to modify inside the gem and you need to know how big impact will be made by the change.

The tag functions with the names in different language

This time, I need the tag that has the names for different languages. Like, 1 tag has the 3 names in English, Japanese and Chinese.

I think it is also possible to make this tag with ‘acts-as-taggable-on’ gem, but it’s much easier and faster to create the tag by myself. It’s because, if I make it by myself, I know everything about it and I don’t have to implement too many functions that I don’t need, so that I can modify easily.

(Of course there’re good points to use the well-known gems but in my opinion it’s better to create it by ourselves if you just need the simple one. Simple is best.)

How I made the Tag

1. Create the Tag model

This create the Tag model with 3 names columns in English, Japanese and Chinese. (name_en, name_ja, name_zh_cn)

$ rails g model Tag name_en:string name_ja:string name_zh_cn:string
class CreateTags < ActiveRecord::Migration
  def change
    create_table :tags do |t|
      t.string :name_en
      t.string :name_ja
      t.string :name_zh_cn

      t.timestamps null: false
    end
    add_index :tags, :name_en, unique: true
    add_index :tags, :name_ja, unique: true
    add_index :tags, :name_zh_cn, unique: true
  end
end
# app/models/tag.rb
class Tag < ActiveRecord::Base
end

2. Create the Tagging model

This hold the relations between the Tag and the Post.

And make the unique index to tag_id and post_id to avoid the duplicate registration.

$ rails g model Tagging tag_id:integer post_id:integer
class CreateTaggings < ActiveRecord::Migration
  def change
    create_table :taggings do |t|
      t.integer :tag_id
      t.integer :post_id

      t.timestamps null: false
    end
    add_index :taggings, :tag_id
    add_index :taggings, [:tag_id, :post-id], unique: true
  end
end
# app/models/tagging.rb
class Tagging < ActiveRecord::Base
end

3. Add the relations to each models

This is the typical many-to-many relations.

# app/models/post.rb
class Post < ActiveRecord::Base
  has_many :tags, through: :taggings
  has_many :taggings, dependent: :destroy
# app/models/tag.rb
class Tag < ActiveRecord::Base
  has_many :taggings, dependent: :destroy
  has_many :posts, through: :taggings
# app/models/tagging.rb
class Tagging < ActiveRecord::Base
  belongs_to :post
  belongs_to :tag

And then, you can call like this.

> post = Post.find 1
> post.tags
=> returns the all related tags
>
> tag = Tag.find 1
> tag.posts
=> returns the all related posts
>
s> post.taggings.new(tag_id: 1)
=> #<Tagging:0x007ff9482e9e58 id: nil, tag_id: 1, post_id: 1, created_at: nil, updated_at: nil>

4. Useful method to get the I18n name

Add the name method to get the right name depends on the I18n.locale. (This will fall back to name_en if another name does not exist.)

# app/models/tag.rb
class Tag < ActiveRecord::Base
  def name
    name = eval("self.name_#{I18n.locale}")
    name.present? ? name : name_en
  end
> tag = Tag.find 1
> I18n.locale = :en
> tag.name
=> 'Tag Name'
> I18n.locale = :ja
> tag.name
=> 'タグの名前'

That’s it!

Simple is best!

The gems are great. They help us to create great apps. But too much functions and black boxes inside your app is not happy. (it’s not black box if you understand the all source codes. Actually it takes time so that we need the trustworthy gems.)

I’m not saying “Don’t use the gems!”, but it’s better to ask yourself whether it’s better to use this gem or not before use them. I believe this will make you (and you in the future) happier.

Thanks!

@takp