Add taggable support to my personal blog

Today I added taggable support to my blog. I do have blog posts tagged, but it's a simple comma-separated list in the database. I want to have a more robust system that allows me to query and filter by tags.

I used the ruby gem acts-as-taggable-on to add taggable support to my blog.

First thing was to install the gem by adding it to the Gemfile and running bundle.

gem "acts-as-taggable-on", "~> 10.0"

Generate and run database migrations

$ bundle exec rake acts_as_taggable_on_engine:install:migrations
$ bundle exec rails db:migrate

Don't forget to fail to read the instructions for when you are using MySQL, get some errors, then roll back and do it correctly.

$ bundle exec rails db:rollback
$ bundle exec rake acts_as_taggable_on_engine:tag_names:collate_bin

There is another issue with MySQL. Seems to be an old issues resurfacing...

Mysql2::Error: Cannot drop index 'index_taggings_on_tag_id': needed in a foreign key constraint

The issue here is that you have to create an index for a foreign key constraint. It's required by MYSQL. We try to delete the index without dropping the foreign key first.

Thanks to ndrix for the solution:

# https://github.com/mbleigh/acts-as-taggable-on/issues/978
# remove_index ActsAsTaggableOn.taggings_table, :tag_id if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id)
if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id)
  remove_foreign_key :taggings, :tags
  remove_index ActsAsTaggableOn.taggings_table, :tag_id
end

Run the migration again, and success! No issues. Time to hook up tags.

Add acts_as_taggable_on :tags to the Blog model. Update the admin blog form. And update the admin blog controller to accept tag_list in the params.

Ok now all the fun stuff. I have existing tags in the database as a comma-separated list. I need to migrate these tags to the new taggable system. I am going to use Maintenance Tasks to do this because it's more fun than just hopping into the console and running some queries.

Here's a very simple task that will migrate the tags from the old tags column to the new taggable system. I have configured soft deletes so I also include deleted records in the collection.

module Maintenance
  class MigrateBlogTagsTask < MaintenanceTasks::Task
    def collection
      Blog.with_deleted.all
    end

    def process(element)
      return if element[:tags].nil?
      return unless element.tag_list.empty?

      element.tag_list = element[:tags]
      element.save
    end
  end
end

Don't forget to create some tests for the maintenance task. Here's a spec that covers the happy path and a couple of edge cases:

module Maintenance
  RSpec.describe MigrateBlogTagsTask do
    describe "#process" do
      subject(:process) { described_class.process(element) }

      let(:blog) { create(:blog) }

      let(:element) {
        blog[:tags] = %w[tag1 tag2]
        blog
      }

      it "updates the tag list" do
        process
        expect(element.reload.tag_list).to eq(%w[tag1 tag2])
      end

      context "when the old tags field is nil" do
        let(:element) {
          blog[:tags] = nil
          blog
        }

        it "does not update the tags" do
          process
          expect(element.reload.tag_list).to eq([])
        end
      end

      context "when the blog post already has a tag list" do
        let(:element) {
          blog[:tags] = %w[tag1 tag2]
          create(:blog_with_tags, tag_list: %w[tag3 tag4])
        }

        it "does not update the record" do
          process
          expect(element.reload.updated_at).to eq(element.updated_at)
          expect(element.reload.tag_list).to eq(%w[tag3 tag4])
        end
      end
    end
  end
end

Run the maintenance task to migrate the tags.

$ bundle exec maintenance_tasks perform Maintenance::MigrateBlogTagsTask

I do have some existing tests for my Blog model and requests. I need to update these tests to use the new taggable system. It's mostly some minor changes to the tests to use the new tag_list attribute. Pretty easy to update. I made the necessary updates, and my tests passed:

$ bundle exec rspec

Finished in 2.52 seconds (files took 1.48 seconds to load)
92 examples, 0 failures, 8 pending

And I think that's all for this session. I have better tags.

Next steps would be to:

  • Create a Stimulus Controller to create auto suggestion for tags when editing or creating a blog entry
  • Roll out to Project model
  • Clean up and remove the old fields from my tables and old model Concerns

 

Join the discussion on:

Linkedin Mastodon BSKY

Did you like this post? Let me know by sending me a message. Is there a topic you would like me to cover? Let me know about that too. I look forward to hearing from you!

Let's Connect!