Ruby on Rails – Using Tags as Model field

If you have a separate model for a Tag and manage them separately, it is pretty easy to attach those tags to another model via a field “tags_ids”. But, let’s say, you’ve got some SEO-settings for a object. It wouldn’t make much sense to store tags in a separate model for all your other pages.

To store tags as a property of any given object we will utilise PostgreSQL’s ability to have an array as data type. So, first of all, I don’t want to add all SEO-fields to every other object over and over again. Let’s create a separate SEO-model:

class CreateSeos < ActiveRecord::Migration[5.1]
  def change
    create_table :seos do |t|
      t.string :title
      t.text :description
      t.string :image

      t.timestamps
    end
  end
end

Then add the keyword column:

class AddKeywordsToSeos < ActiveRecord::Migration[5.1]
  def change
    add_column :seos, :keywords, :string, array: true, default: []
  end
end

Add references to appropriate other tables:

class AddReferencesToSeos < ActiveRecord::Migration[5.1]
  def change
    add_reference :seos, :post, foreign_key: true
    add_reference :seos, :product, foreign_key: true
  end
end

For front-end editing I’m using bootstrap-tagsinput (here is an example). Install it via yarn add bootstrap-tagsinput(or download files manually). Don’t forget to include JS aswell as CSS files.

On the front-end part it is important to use a select field instead of simple text_field to be able to select multiple tags. Here is my full html code for the seo block:

    =f.fields_for :seo do |seo_builder|
      .form-group
        %label.col-lg-2.control-label
          =t('admin.form.model.seo.title')
        .col-lg-10
          =seo_builder.text_field :title, class: 'form-control'
      .hr-line-dashed

      .form-group
        %label.col-lg-2.control-label
          =t('admin.form.model.seo.description')
        .col-lg-10
          =seo_builder.text_area :description, class: 'form-control', rows: 4

      .form-group
        %label.col-lg-2.control-label
          =t('admin.form.model.seo.keywords')
        .col-lg-10
          =seo_builder.select :keywords, object.seo.keywords.map {|i| [i, i]}, {}, {id: 'object_keywords', class: 'form-control custom-bootstrap-tagsinput', data: {role: "tagsinput"}, multiple: true}

For the backend part include seo to your corresponding models:

class Post < ApplicationRecord

  has_one :seo
  accepts_nested_attributes_for :seo, allow_destroy: true

end

In your controller, whitelist the attributes (important to use keywords: [] to indicate an array) and don’t forget to build┬áthe fields if they aren’t present yet.

seo_attributes: [:id, :title, :description, :image, keywords: []]
def new
  @post = Post.new
  if @post.seo.blank?
    @post.build_seo
  end
  @tags = @post.seo.keywords || ""
end

def edit
  if @post.seo.blank?
    @post.build_seo
  end
  @tags = @post.seo.keywords || ""
end

Now you can enjoy setting any variable tags and them being persistent in your database. Happy coding!