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.
I like this technique. BTW. GeoKit rocks!
From Programming Ruby:
Thus we need a backslash:
TO_FORMAT = /^[_a-z0-9+.-]+@[_a-z0-9-]+\.[_a-z0-9.-]+$/i
I know the code is for demonstration purposes only but still, we don’t want bugs to propagate throughtout the Internet from this magnificent blog
Thanks for your great tips!
By the way: GeoKit has helped me a lot building Waschstrasse.info—thanks!
Marcin,
I actually went back to my original code and it was correct there. This means that it is either being stripped out by CodeRay or that there’s a combination of characters that make the slash not render.
Sorta makes you want to be cautious about the code you lift from a blog, eh? As always, one should know all of the code they deploy.
Thanks for the tip.
Bill
You really shouldn’t use your models in your migrations. It’s now possible to break the migration file by modifying the model. You could solve this by placing the model in your migration file, but I’m not sure if that’s the best solution.
Yeah, I’ve had situations where I’ve added a model, then later deleted it, and thus broken the migration when it runs from an early version. However, you can easily add the model into your migration to fix the migration.
I think this is a matter of style and my preference is still to use model constants in the migration. It just seems more DRY and adds accuracy to your migration.
Thanks for your feedback!