Adding Rails G Migration To a Gem - Following Code To Re-Implement Functionality
This post was originally published on my previous blog jer-k.github.io
Published on 2018-02-25
This is a follow-up to my last post about Adding ActiveRecord Rake Tasks to a Gem that I promised to write.
In that post I had to figure out how to make the command rails g migration
accessible inside the gem, which ended
up taking me all afternoon, but surprised me in how little code was actually needed to achieve the result. I wanted to
write about the process I went through to figure out what was needed; I believe it is good exercise in understanding
how to follow code to and understanding what it takes to re-implement functionality.
A note before I begin: I use RubyMine for my IDE and all the tracing of the code I did in RubyMine, unless otherwise stated. I'm going to provide links to GitHub so you can see exactly where I saw the code I'm talking about. I also picked the most current commit as of writing for the links to ensure they continue pointing at the correct code.
When I first realized that rails g migration
wasn't available in the gem I thought to myself, well that makes a lot
of sense, I haven't included Rails. But I was almost fooled by the globally installed Rails for a moment!
That output is indicating that I have installed Rails globally and I can create a new Rails application in any directory
I want. The output that I was looking for is the output when running rails
inside of a Rails application.
Seeing this result, the easiest thing that came to mind was to figure out where the line The most common rails commands are
lives and start my work there. Going to the Rails repo on Github and searching, I saw that the second
result was a USAGE file in Railties and it was exactly what I was looking for. Now I needed to understand
how this USAGE
file gets invoked.
I immediately added spec.add_development_dependency 'railties', '~> 5'
to my gem and opened up directory containing
the USAGE
file. In the same directory is help_command.rb.
Seeing that HelpCommand
is a subclass of Rails::Command::Base I jumped to defintion of Base
because I'm
trying to generate files, not output help information.
Looking at Rails::Command::Base
one of the first things I noticed that it has require "rails/command/actions"
right above the class definition. I assumed that actions.rb likely had more information about the available actions
that could be invoked.
Looking into Rails::Command::Actions
I was pleasantly surprised to see a method definition for load_generators
and I knew I was on the right track.
I jumped to the definition of Rails.application but unfortunately that didn't inform me about what application
was supposed to be. I went back and decided that I needed to figure out where load_generators
was being defined.
RubyMine does a great job (sometimes too good when the method definition name is very generic) of finding all the possible
places that a method is defined. If it is only defined in a single location, it will jump to directly to that definition,
but if there are multiple, it will give you a list of all the locations. For load_generators
there were only three
definition locations, two happened to be in Rails::Command::Actions
and the other was in Rails::Engine.
Now I knew that the Rails.application
needed to be my own Rails::Engine
. I hadn't even written an Engine
before,
but thankfully the Rails team has done a great job adding documentation about writing your own inside the file! As I
started looking through the documentation I came across the portion about Generators
This looked exactly what I needed so quickly created my own and attempted to generate a migration.
Success, I was able to use rails g
and generate my migration! The only caveat was that I had to name the file gem_rails
because once I installed the gem into an actual Rails
application, things started to not play nicely together.
My main takeaways from this experience were the importance of writing code in a manner that allows another user to come into the code understand the possibilities. In my opinion, someone should either understand exactly what the code is supposed to do, say a succinct method for example, or they should be able to follow the code to different methods or classes through well-defined and named APIs. Oh, and don't forget about update to date comments and documentation in the code too! While it was easy to write those sentences, writing code that follows those principles isn't always so easy. The Rails team (and I mean all contributors!) should be applauded for making life so easy for the rest of us.