Subtle Rails Backend Gotchas

  • Presence validation in the model without a schema constraint.
    Presence is one of the most common ActiveRecord validations. In our Rails models, the syntax looks like this:

    validates_presence_of :attribute
    

    or this:

    validates :attribute, presence: true
    

    It simply checks that the attribute is not nil or blank (whitespace). Having presence validated is great, because it frees us from having to nil check any methods that consume our validated attributes.

    But what if a future contributor removes that presence validation? Any business logic we’ve written that depends on our validated attributes might now explode. We can avert a potential disaster by adding null: false to the migration, on any attribute that has presence validated:

    class CrazyCatLady < ActiveRecord::Base
      has_many  :cats,
                inverse_of: :crazy_cat_lady
    end
    
    class Cat < ActiveRecord::Base
      belongs_to  :crazy_cat_lady,
                  inverse_of: :cats
    
      validates_presence_of :crazy_cat_lady
    end
    

    Null: false will cause a database error to be raised if nil is saved on that attribute. This simple step protects the integrity of our data by enforcing our intention on two independent levels of the Rails stack.

  • Booleans without default values.
    A boolean should virtually always have a default value. There are (very) occasional situations where our business logic will depend upon a boolean representing three states: unset, set false, and set true. In the vast majority of applications, however, you won’t need to be sensitive to the unset case. To implement the typical situation of a false default value, we would add default: false in our migrations:

    class CreateWidgets < ActiveRecord::Migration
      def change
        create_table :widgets do |t|
          t.string :name, null: false
          t.string :description
          t.boolean :flanged, default: false
    
          t.timestamps
        end
      end
    end
    
  • Incompletely tested migrations.
    Here’s is an easy one to forget. You’ve just written a migration, and you’re about to rake db:migrate to effect the change to the database. But wait! How do you know that your migration will play nice if someone has to roll back (i.e., revert) your changes?

    Roll it back, then migrate it right back up!

    rake db:migrate
    rake db:rollback
    rake db:migrate
    

    If we don’t see any errors in the console, we then check our schema.rb and make sure everything looks right. Taking this extra step ensures that any migration we write will behave itself if it needs to be rolled back.</ul>

comments powered by Disqus