DRYing up your validations using DB reflection

Out of the box, ActiveRecord will silently truncate attribute values which exceed their column width.

require 'active_record'

class Greeting < ActiveRecord::Base
  establish_connection(adapter:  'mysql', 
                       database: 'scratch', 
                       password: ENV['MYSQL_PASSWORD'])

  connection.create_table(:greetings) do |t|
    t.string :text, limit: 10
  end
end

g = Greeting.create(text: "Greetings and salutations, esteemed world!")
g.reload
puts g.text # => "Greetings "

This is potentially surprising to users. To improve usability, we can set a validation:

require 'active_record'

class Greeting < ActiveRecord::Base
  validates :text, length: { maximum: 10 }
end

g = Greeting.create(text: "Greetings and salutations, esteemed world!")
g.valid? # => false
g.errors.full_messages # => ["Text is too long (maximum is 10 characters)"]

Now we have a new problem: duplication of the length cap. The number “10” now appears in both the database migrations, and the model validations.

We could extract it out into a constant, although referencing models can be problematic in migrations. But that still doesn’t account for the (many) cases where we don’t specify an explicit field limit in the migration, and instead rely on the built-in ActiveRecord defaults for field limits.

Here’s a solution that uses database reflection to validate a field is within its DB column size limit:

validates :text,
          :length => {
              :maximum => columns_hash['text'].limit
          },

ActiveRecord provides the columns_hash to get at column metadata gathered from the database backend. In the code above we query it for the limit attribute, and use that as the max field length. By pulling the limit from the DB, we avoid duplication of knowledge.

26 comments

  1. Since I first started using Rails, I thought it was odd that it did not use DB introspection to automatically provide these kinds of obvious validations. 

    1. It has been picked up by¬†MichaŇā ŇĀomnicki and Ronen Barzel:¬†https://github.com/lomba/schema_validations

  2. Doesn’t this introduce an extra database hit to get the schema? And isn’t it considered an ActiveRecord best practice to use validations for limiting length instead of enforcing in the database?

      1. No, ActiveRecord pulls in the whole schema and caches it during start-up.
      2. ActiveRecord always puts a limit in the DB even if you don’t explicitly specify it. For instance, by default AR puts a 255-char limit on :string types. So the limit is already there; this is just a way to give the client some decent feedback instead of silently truncating it.
  3. I was looking for ways to solve this problem! From your info I coded a method for the model that returns the data. Seems like it should be part of AR. For example:
        Model.column_name.schema_data
    such as:
        Company.contact_name.limit
    returns the limit on the contact_name field. All of the column_hash values can be accessed this way. See: http://stackoverflow.com/questions/11004253/to-be-dry-can-i-access-a-string-column-width-limit-in-activerecord 

Leave a Reply

Your email address will not be published. Required fields are marked *