Using Constants in Rails Models
One of my pet peeves in code is the absence of constants and thus the prevalence of “magic numbers” in their place. Constants add a touch of readability and semantic meaning and magnify the maintainability of the code.
As a simple example, here’s a partial model featuring constants to aid in clear validation specifications:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Email < ActiveRecord::Base MAX_FROM_LENGTH = 30 MAX_TO_LENGTH = 100 FROM_LENGTH_RANGE = 1..MAX_FROM_LENGTH TO_LENGTH_RANGE = 1..MAX_TO_LENGTH TO_FORMAT = /^[_a-z0-9+.-]+@[_a-z0-9-]+\.[_a-z0-9.-]+$/i NOTE_LENGTH_RANGE = 1..DB_TEXT_MAX_LENGTH # ---- Attributes ---- # Attributes protected from mass-assignment (as through forms) attr_protected :sent_at # ---- Validations ---- validates_length_of :from, :within => FROM_LENGTH_RANGE validates_length_of :to, :within => TO_LENGTH_RANGE validates_format_of :to, :with => TO_FORMAT validates_length_of :note, :within => NOTE_LENGTH_RANGE |
As you can see the model defines constants to enforce length maximums and in turn the ranges. You could argue that the range constants are less useful since you have to refer back to the constants section to see them, but that’s a matter of taste. I like having them.
With constants like these properly placed in your model, you can take advantage of them in your migration. Here’s a partial example:
1 2 3 4 5 6 7 8 9 10 |
class CreateEmails < ActiveRecord::Migration def self.up create_table :emails do |t| t.column :emailable_id, :integer, :null => false t.column :to, :string, :null => false, :limit => Email::MAX_TO_LENGTH t.column :from, :string, :null => false, :limit => Email::MAX_FROM_LENGTH t.column :note, :text, :null => false t.column :sent_at, :datetime end end |
The advantage here is that your model and your migration are now in synch for field maximums. If that’s not enough, then consider what you can now do in your test code:
1 2 3 |
def test_invalid_with_long_to assert_value_for_attribute_invalid(Email.new, :to, filled_str("X", Email::MAX_TO_LENGTH + 1)) end |
So now, if you change your mind and decide you need to lengthen your fields, you need only change your model constants. Your migration could be rerun or you could add an additional migration which applies the new field length. And lastly, your test code doesn’t have to change.
I think this is simple, yet very much worth doing.