This is a documentation on how to add class and instance methods to models that exist in ruby gems. Often, there is a need to add methods to models that are created in ruby gems.
In a recent project that I am working on, I found a particular need for adding images to a tagging gem. The purpose of the gem is for taxonomy and the final UI draft has different images allocated to each of the category (or should I say tag). We decided to have the rails backend handle the image and tag association. Hence, the ideal way to handle this would be to modify the models in the tagging gem to hold its image as well upon creation.
The reason I use the latter instead of the former is because the former does not support the new ActiveRecord 6 when I was working on the project. It returns erroneous results and throws error due to deprecated ActiveModel method in its normal usage for example.
The alternative I found is
Gutentag. It support Rails 6 and the contributors are actively resolving issues, keeping its issues count at 0 at the time of writing. I found it reliable and it does its main job well, which is to provide the tagging module.
I now want to add image to each tag using
has_on_attached method, and also a custom instance method that will return the tag’s name and image url.
The way I am doing it is to create a module that defines the relevant methods, and have the
Gutentag::Tag model include this custom module. I will include it during the initialization phase. This will require some workarounds because of we are accessing the
ActiveRecord railitie sduring the initialization phase where these railities are not loaded yet.
The Extension Module
I modified it slightly with the use of ActiveSupport::Concern to do it the Rails 6 way. This helps to resolve module dependencies gracefully.
In this extension, I attached an image to the module using
has_one_attached class method, which will ultimately be applied to the
I also defined the instance method
json_attributes which will return only the name and the image url in the resultant tag when called. It is used in the api response when frontend clients are retrieving the list of tags for example.
The code will be added to the original
Gutentag initializer file under
The extension file is imported in line 3.
Line 5 is one of the provided original
Gutentag configuration option. This is specific to my project and is trivial in relation to the topic of this article.
Line 10 is the main line of code to execute. It will add the module to the
Gutentag::Tag model which is defined inside the source code of the
Gutentag gem. However, as you can see, it is wrapped in a number of codes. Not doing so will result in errors.
Here is why.
As we are going to involve the
ActiveSupport railities, which are not initilized during the default rails initialization phase, we need to ensure we run the code after they have been loaded.
Rails has 5 initialization events. The first initialization event to fire off after all railities are loaded is
to_prepare, hence we define the code after that happens inside its block.
Since we are interacting with a
ActiveRecord model, during the initialization phase, it is possible that the table has not been created. In other words, the
Gutentag tables migration has not been executed, resulting in errors about the table not existing. An
if conditional check is done to prevent this error. I am not handling the
else condition as under normal circumstances, after the proper migration has been executed, this will not happen.
A non-existing table is not the only thing we have to guard when dealing with Railities during the initialization process. A non-existing database is also a probable scenario that may occur. An example is during the
rails db:create step. Hence, we rescue the
ActiveRecord::NoDatabaseError error to silence the error. As this is often the only scenario that will happen, I will not handle the exception in the
Now we can use it in our application. For instance, I can seed some default tags with images attached to them as shown:
Then in my api response for listing the tags, I can use the
json_attributes method as such: