Using model constants for project sanity
On one of our larger client projects (approx. 160 models and growing…) we have a specific model that we refer to quite a bit throughout our code. This model contains less than 10 records, but each of them sits on top of an insanely large and complex set of data. Each record refers to a each of their regions that our client does business in.
For example… we have, Australia, United Kingdom, Canada, United States, and so forth. Each of these regional divisions has their own company code, which are barely distinguishable from the next. They make sense to our client, but when we’re not interacting with those codes on a regular basis, we have to look constantly look them up again to make sure we’re dealing with the right record.
I wanted to share something that we did to make this easier for our team to work around these codes, which we should have thought of long ago.
Let’s take the following mode,
Division. We only have about 10 records in our database, but have conditional code throughout the site that are dependent upon which divisions specific actions are being triggered within. Each division has various business logic that we have to maintain.
Prior to our change, we’d come across a lot of code like:
# For all divisions except Canada, invoices are sent via email # In Canada, invoices are sent via XML to a 3rd-party service def process_invoices_for(division) if division.code == 'XIUHR12' # trigger method to send invoices to 3rd party service # ... else # batch up invoices and send via email # ... end end
An alternative that we’d also find ourselves using was.
if division.name == 'Canada'
Hell, I think I’ve even seen
if division.id == 2 somewhere in the code before. To be fair to ourselves, we did inherit this project a few years ago. ;-)
Throughout the code base, you’ll find business rules like this. Our developers all agreed that this was far from friendly and/or efficient and worst of all, it was extremely error-prone. There have been a few incidents where we read the code wrong and/or got them confused with one another. We were lacking a convention that we could all begin to rely on and use.
So, we decided to implement the following change.
You might already use constants in your Ruby on Rails application. It’s not uncommon to add a few into
config/environment.rb and call it a day, but you might also consider scoping them within your models. (makes it much easier for you to maintain them as well)
In our scenario, we decided to add the following constants to our
class Division < ActiveRecord::Base AFRICA = self.find_by_code('XYU238') ASIA = self.find_by_code('XIUHR73') AUSTRALIA = self.find_by_code('XIUHR152') CANADA = self.find_by_code('XIUHR12') USA = self.find_by_code('XIUHR389') # etc.. end
What this will do is load up ech of these constants with the corresponding object. It’s basically the equivallent of us doing:
if division == Division.find_by_code('XIUHR389')
But, with this approach, we can stop worrying about their codes and use the division names that we’re talking about with our clients. Our client usually approaches us with, “In Australia, we need to do X,Y,Z differently than we do in the other divisions due to new government regulations.”
if division == Division::CANADA # ... end case division when Division::AFRICA # when Division::AUSTRALIA # ... end
We are finding this to be much easier to read and maintain. When we’re dealing with a lot of complex business logic in the application, little changes like this can make a big difference.
If you have any alternative solutions, we’d love to hear them. Until then, we’ve been quite pleased with this approach. Perhaps you’ll find some value in it as well.