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