How to Gain a Few More Hours of Sleep
Ruby on Rails has become my goto web framework. I’m a big fan of the conventions, the gems, and the plethora of tutorials and resources. But like anyone, I’ve had my fair share of scrambling through stack overflow and scratching my head at curious behavior. Here are a handful of simple gotchas that will hopefully save you several hours of frustration.
- Callback return values
- Manipulating Strong Parameters
- API usage: as_json, to_json
- Postgres and the Array type
Callback Return Values
This is a common, even sinister, stumbling block because it fails so quietly. Often you’ll be hard-pressed to discover why your model isn’t saving, even though it passes your validations (model.valid? == true) and your attributes look sane.
Check whether any of your callback methods are inadvertently returning false – a common error, for example, when setting an attribute’s value to false, and the method that does so is registered as a before_save callback.
Canceling callbacks
If a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns false, all the later callbacks are cancelled.
Better yet, if you’re attempting to set default values, do so in your schema when generating the migration.
Manipulating Strong Parameters
Your controller needs to pre-process parameters before passing them to the model; capitalizing a string, arithmetic operation, sanitization, etc. Rails 4 introducted strong parameters to whitelist them for mass-assignment; for example, when you pass the params hash to model.update_attributes(). In Rails 4, you’d whitelist parameters accordingly:
1 2 3 4 5 6 7 8 |
|
But modifying the params hash in the manner below will not work as expected.
1 2 3 4 5 |
|
Paraphrased from the docs, the permit() method “returns a new ActionController::Parameters instance” – so the modified hash will never be returned, and you’ll keep wondering why your changes aren’t passed on.
API Usage: as_json, to_json
My recent sideproject, stereopaw, uses a Rails API to pass data back-and-forth between a Backbone app. Models are rendered in json, but it’s frequently not ideal to pass every attribute in a model back to the user-facing app; attributes like created_at, or updated_at, are frequently unnecessary, and it’s never a good idea to pass something like encrypted_password. To compactly exclude attributes of json’d models, overload the as_json() method in the model. When to_json() is called (implicitly, with render :json => @model ), it invokes as_json() to “serialize” the model into a json representation, and attributes can be filtered with the :except option.
1 2 3 4 5 6 |
|
On the contrary, some models might have methods you’d like to include when rendering the model in json.
1 2 3 4 5 |
|
These are much more compact approaches to serializing json, as opposed to say, creating a new object and assigning attributes manually (eek).
Rails and the Postgres Array Data Type
Rails 4 and Postgres arrays aren’t quite the best of friends yet, and there are several caveats when using this data type that can leave you extremely frustrated.
Default Empty Array
The notation used to set a default empty array in migrations was a prior Rails issue, and may be problematic depending on your Rails version.
I’ve found that default: [] works for setting a default integer array type (defers to ActiveRecord to convert to postgres notation), while default ‘{}’ works for setting a default string array type – otherwise I’d incur a world of error messages.
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 |
|
Passing (Empty) Array Parameters
When expecting a parameter of an array data type, map the key to an empty array.
1 2 3 |
|
Sometimes you’ll need to pass an empty array, but Rails will exclude empty values, and the key won’t exist in the params hash. You’ll have to modify your parameter values accordingly, and a good bet is via “abbreviated assignment”.
1 2 3 4 5 |
|
ActiveRecord Dirty Array
Sooner or later you’ll encounter your model failing to save a modified array attribute. When it comes to array.push, array.pop, ActiveRecord doesn’t mark the array as “dirty,” and subsequent changes using these array manipulations will not propogate to the database. You’ll need to flag the specific attribute as dirty by calling attr_name_will_change! if you want to save the updated array type attribute.
1 2 3 4 5 6 7 8 9 10 |
|
Hope all this saves you a few headaches!