Technology Musings

My Beef with Model Validators in Rails

JB

When I first started using Rails, it was a dream come true.  I estimate that I reduced the amount of code I was writing by 60-75%.  This is probably still true.  However, as I've gotten to know the platform better, there are some philosophical issues that I have with Rails.  Sometimes it is more the Rails community and its conventions rather than rails itself, but nonetheless, it continually manifests itself.

My current beef - validators!  Having written lots and lots of code that interacts with lots and lots of systems, I have to say that you should never, ever, ever put in model validators.  Ever.  Validation is a process issue, not a data one.  In theory it should be a data issue, but it never ever works out that way.

Here's what happens.  Let's say I have a telephone number validator for a model.  It makes sure the numbers are in the format (918) 245-2234.  If not, it gives a nice error message.  However, now let's connect that same model to an external system.  Let's now say that the author of the external system was not as picky about phone number formats.  What happens now?  Well, every record that gets migrated from that system that isn't formatted according to your desires will now get kicked out!  And it might not even show up during testing!  In addition, your pretty error messages mean practically nothing on system->system communication.  So, what you have is the system randomly breaking down.

This causes even more problems in long-term maintenance.  I've had many bugs pop up because someone added a validator that broke several processes.  It worked in all of their tests, but they weren't thinking about all of the ways the system was used.  For instance, we decided to lock down the characters used in our usernames a little more.  The programmer decided to implement it as a validator.  Well, the problem is that several users already had usernames that didn't match the validator.  So what happened?  Those records just stopped saving.  Not just when there was someone who was at the terminal, where they could see the error message, but every job started erroring out because of users who had faulty names.

Because of these issues, I *always* do validation in my controller, and *never* in the model.  It is true that there are probably workarounds for all of these issues, but the point is that they are *not* that exceptional!  They are everywhere!  What would be nice is to have validators which could be run optionally, in specific situations.  So, a controller could declare that it was using :data_entry validations, and then the model would only run those validators.  So, it would look something like this:

In the model:

validates_presence_of :email, :for => [:data_entry]

In the controller:

uses_validators :data_entry

One of the issues I have found with dynamic languages such as ruby is that errors arise when "magic" is sprinkled in unexpected places.  Ruby magic is awesome, but when I do something simple and it won't save, it is sometimes hard to even locate where the error is.  Explicitly declaring which validator set you are using allows programmers to more easily see what they are doing, and only apply magic code where it goes.

Someday I'll write a package like this --- when I get extra time :)