Dynamically create methods and scopes in Rails

The active record models with the status columns generally indicate that there are different states that the model can be in.

And usually, when there are status columns within a model, it’s common to query for specific statuses and to check for the status of the model. For example, let’s say that we have an Invoice model that have three different statuses: pending, paid, overdue. Most people would write code for querying for these statuses and checking for the status of the model like this.

This works. Some developers, who are aware of creating scopes may create scopes for the different statuses instead. And also, they may add methods to the Invoice model to determine the status of the Invoices (because you know, refactoring).

The above is definitely better, and I (and a lot of people) would probably go, “Good Enough”, and get along with our lives. But our Invoice model can be made more succinct and more flexible if we utilize a little bit of underutilized Ruby features.

Let’s say that the Invoice model can have a lot more statuses, like 10 different statuses, like paid, pending, overdue, overpaid, underpaid, read, unread, clicked, missing, scammed (Note, some of these status names don’t make sense, I just wanted to quickly come up with 10). Well, that’s a lot of custom scopes and methods you have to write. Thankfully, there’s a little trick to creating scopes with a little bit of Ruby magic during runtime.

For the scopes, we can store the pre-defined statuses in a constant, loop over them, and create scopes during runtime. For the methods that check for the Invoice’s current status, Ruby has this thing called define_method that allows you to define methods during run time.

https://ruby-doc.org/core-2.2.0/Module.html#method-i-define_method

At first glance, one may go, “Why would I ever use this define_method thingy instead of actually writing the method myself?” Well, in the context of creating methods that check for predefined statuses on the fly, it can be pretty useful in that it can create all of these methods for us rather than us having to write the scopes by hand. To utilize these two techniques so that we don’t have to manually write 10 different scopes and methods by hand, we need to do the following.

  1. Create a constant that defines what statuses the Invoice model is allowed to have.
  2. Loop through the statuses, and for each status, create a scope and then define a custom method using Ruby’s define_method.

Below is how you do it.

Try the refactoring of the Invoice model above and you’ll see that the controller still works as it should. And the Invoice model in this form is much more concise and flexible since if we want to add more scopes and methods that pertain to the status, all we have to add is the new statuses in the Invoice::STATUS constant.