method

with_options

v8.1.1 - Show latest stable - Class: Object
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 or Hash-like object 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

When the block argument is omitted, the decorated Object instance is returned:

module MyStyledHelpers
  def styled
    with_options style: "color: red;"
  end
end

styled.link_to "I'm red", "/"
# => <a href="/" style="color: red;">I'm red</a>

styled.button_tag "I'm red too!"
# => <button style="color: red;">I'm red too!</button>

3Notes

Nested with_options

Pilaf · Feb 23, 20096 thanks

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

This can be useful for conditional validation

himn1 · Oct 7, 2010

Beware nested with_options clobbers!

kwerle · May 18, 2012

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.