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.