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.
Great tip! Thank you.
You’re welcome!
Very nice tip!
Glad you liked it!
Nice 🙂
https://github.com/rubiety/validates_lengths_from_database uses this trick.
Nifty, thanks!
cool :), 10x
Thanks!
If only AR automatically did this for all (common) restrictions on columns.
Indeed.
Have a look at http://rubygems.org/gems/schema_validations, does exactly what you need.
Neat tip! I can see myself needing this.
Glad to help!
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.
It does seem weird.
Great tip! Too bad Rails doesn’t hold a strong opinion about using conventions…
🙂
Check out:
https://github.com/twinge/enforce_schema_rules
With one macro method it handles:
I use it on almost every project. Used to use schema_validations by the “Red Hill on Rails” folks but that fell into disrepair. Found this plugin and it has been working great.
That looks awesome! Thanks!
Years ago I wrote a schema_validations plugin which I used on a number of projects. I’ve since stopped supporting it for various reasons – for one thing, at the time the jiggery pokery required to get it to work was cumbersome. Looks like it would be MUCH easier now – but I still have an archived version:
https://github.com/harukizaemon/redhillonrails/tree/master/schema_validations
It has been picked up by Michał Łomnicki and Ronen Barzel: https://github.com/lomba/schema_validations
Oh, fantastic! Thanks for the heads up.
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?
Thanks Avdi. That’s good to know.
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