Better sanity checking in Rails
When you’re building a web application, Rails does a load of the heavy lifting for you. Many of the core philosophies of Rails are aimed around only implementing functionality in the place it makes sense.
This turns out to be a great idea for readability, and with practice – and thoughtful naming – it isn’t too difficult to keep your code clean enough that you can see at a glance what it does. You can get to the point where your controller code just gives a high-level overview of what the code does, and leaves all the details to the models and other modules.
Lots of the tools in a Rails developer’s toolbox are commonly known, but there are a couple of useful ones that are newer and haven’t been picked up on as much.
As a simple example of how Rails tries to make you code in the right place, here’s a snippet of the kind of code you shouldn’t be writing:
class UsersController < ApplicationController
def update
# ...
@user.attributes = params[:user]
errors = {}
errors[:name] = "Name must be given" if user.name.to_s == ""
errors[:birth_year] = "Birth year must be given" if user.birth_year.to_i == 0
if errors.empty?
@user.save
redirect_to users_url
end
end
end
Instead, Rails lets you simplify the code like this:
==
class User < ActiveRecord::Base
validates_presence_of :name, :message => "must be given"
validates_presence_of :birth_year, :message => "must be given"
end
class UsersController < ApplicationController
def update
- …
@user.attributes = params[:user]
if @user.save
redirect_to users_url
end
end
end
==
This has a few benefits:
- Code is only written in the place it fits best, rather than the controller needing to know which fields are required, that information is stored in the model. This ties in well with the DRY principle that Rails promotes.
- It improves the integrity of your code, as you have a much tighter guarantee that a user can never be saved without a name, while the first example only applied the protection to one action in one controller.
- This approach makes the controller code more readable, instead of scanning through a load of validation code, you can read the action code (almost word for word) as “if the user object saves, redirect to the users list page.”
This level of separation is great news, because it means the business logic in your controllers can be much more descriptive and understandable, and not prescriptive and procedural.
Rails follows its own rules for the most part too. Rather than forcing you to write the code that checks to see whether the name attribute is set, instead it hides this away in a validates_presence_of method – which is implemented away from your specific model, deep in the Rails code.
The example above should be second nature to a Rails developer, validation is usually one of the first “cool” things you hear about when learning Rails. However there are plenty of other, lesser known ways that the readability of your code can be improved by offloading common checks and calculations to prewritten Rails methods.
Do try and pay attention
Here’s a line I’ve written many, many times in various forms throughout multiple Rails apps:
if post.published_by && post.published_by.admin?- Do something
end
This makes sense to Rubyists – if the post hasn’t been published yet, you can’t call any methods on its publisher. Trying to do that would give you probably the most common kind of error you’ll see in a Rails app:
NoMethodError: undefined method `admin?’ for nil:NilClassBut checking for the presence of something before you access it is repetitive, and if you’re chaining more than a couple of method calls together it’s more difficult to read. Rails gives you an alternative:
if post.published_by.try(:admin?)- Do something
end
The try method is very similar to the send method, with one difference – if you call try on a nil object, it returns another nil rather than raising a NoMethodError. You can even pass arguments and blocks to try:
This isn’t a perfect solution as it’s a bit awkward to read back, but I see it as an improvement on the repetition I had before.
Clear and present danger
Another common idiom, especially when dealing with input from HTML forms, is to check whether the user entered anything in a particular field, either a field in the request parameters or a model attribute being returned from your database. However as Rails generally saves an empty form field as the empty string (""), the standard ways of checking for the presence of an object just don’t work in these cases:
Lots of Rails developers seem to know about Object#blank? — the supercharged version of Object#nil? that allows you to write code like
However fewer people seem to have discovered the complementary method Object#present? which is the exact opposite of .blank?
I’m sure there are lots of these useful utility methods, and I just haven’t heard about them. Leave your favourites in the comments.
Tweet