with_options
with_options(options, &block)
public
An elegant way to factor duplication out of options passed to a series of method calls. Each method called in the block, with the block variable as the receiver, will have its options merged with the default options hash provided. Each method called on the block variable must take an options hash as its final argument.
Without with_options, this code contains duplication:
class Account < ActiveRecord::Base has_many :customers, dependent: :destroy has_many :products, dependent: :destroy has_many :invoices, dependent: :destroy has_many :expenses, dependent: :destroy end
Using with_options, we can remove the duplication:
class Account < ActiveRecord::Base with_options dependent: :destroy do |assoc| assoc.has_many :customers assoc.has_many :products assoc.has_many :invoices assoc.has_many :expenses end end
It can also be used with an explicit receiver:
I18n.with_options locale: user.locale, scope: 'newsletter' do |i18n| subject i18n.t :subject body i18n.t :body, user_name: user.name end
When you don’t pass an explicit receiver, it executes the whole block in merging options context:
class Account < ActiveRecord::Base with_options dependent: :destroy do has_many :customers has_many :products has_many :invoices has_many :expenses end end
with_options can also be nested since the call is forwarded to its receiver.
NOTE: Each nesting level will merge inherited defaults in addition to their own.
class Post < ActiveRecord::Base with_options if: :persisted?, length: { minimum: 50 } do validates :content, if: -> { content.present? } end end
The code is equivalent to:
validates :content, length: { minimum: 50 }, if: -> { content.present? }
Hence the inherited default for `if` key is ignored.
NOTE: You cannot call class methods implicitly inside of with_options. You can access these methods using the class name instead:
class Phone < ActiveRecord::Base enum phone_number_type: [home: 0, office: 1, mobile: 2] with_options presence: true do validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys } end end
Nested with_options
You can nest with_options blocks, and you can even use the same name for the block parameter each time. E.g.:
class Product with_options :dependent => :destroy do |product| product.with_options :class_name => 'Media' do |product| product.has_many :images, :conditions => {:content_type => 'image'} product.has_many :videos, :conditions => {:content_type => 'video'} end product.has_many :comments end end
Beware nested with_options clobbers!
Careful:
with_options :foo => :bar do |something| something.with_options :foo => :baz do |inner| what_is(:foo) end end
:foo will be :baz. It will not be [:bar, :baz], for example.
This bit me when trying to do nested with_options for validation where both had :if => something.