Another handy Rails method: #with_options
So let’s say you have the following (contrived) model:
class User
has_many :tasks, class_name: "UserTask", dependent: :destroy
has_many :addresses, dependent: :destroy
has_many :todos, as: :creator
has_one :subscription, dependent: :destroy
end
So when we delete a User
, we also delete their associated items. However, there’s a bug in our code – we’ve missed a dependent: :destroy
for the todos
associations, so we’re going to be left with zombie todos floating around our database, potentially raising exceptions if a user tries to access them.
DRY for Hashes
So it’d be nice to have some way to DRY up our declaration that we want associated models destroyed when we destroy a User
, and avoid more bugs from forgetting to add the necessary options. Enter #with_options
:
Including in the methods ActiveSupport adds to Object
, #with_options
takes a hash and a block. Within the block, you can make further method calls that take hashes, and #with_options
will merge these hashes with the original hash.
With that in mind, we can now refactor our User
class:
class User
with_options dependent: :destroy do |options|
options.has_many :tasks, class_name: "UserTask"
options.has_many :addresses
options.has_many :todos, as: :creator
options.has_one :subscription
end
end
Since #with_options
is defined on Object
, you can use it pretty much anywhere in your Rails codebase.
It blows up when I use a lambda!
There’s an unfortunate bug if you use #with_options
with a has_many
association that has an additional scope:
class User < ActiveRecord::Base
with_options dependent: :destroy do |options|
options.has_many :addresses, -> { where(active: true) }
# ...further associations...
end
end
Rails will throw an error because it tries to call Hash
methods on the lambda. Fortunately, the issue has been reported and was fixed in Rails v4.1.2. If you’re using an older version, simply pass an empty hash as your last option to has_many
and all will be well:
class User < ActiveRecord::Base
with_options dependent: :destroy do |options|
options.has_many :addresses, -> { where(active: true) }, {}
# ...further associations...
end
end
Photo by Daniel Petzold Photography on Flickr