0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails】extract_options!メソッドの挙動と使われるところ

Last updated at Posted at 2024-10-23

はじめに

Railsのコードリーディングをしていたところextract_options!というメソッドの存在を知りました。

この記事ではextract_options!の挙動について、Railsのコードで使われている箇所とともにまとめます。

extract_options!メソッドとは?

extract_options!メソッドは、配列の最後の要素がハッシュであればそれを取り出し、残りの配列を返すメソッドです。

メソッド名の末尾の!が示すように元の配列を破壊的に変更する点に注意が必要です。

サンプルコードで確認します。

sample-app> params = [:name, :age, { city: 'Tokyo', country: 'Japan' }]
=> [:name, :age, {:city=>"Tokyo", :country=>"Japan"}]
sample-app> options = params.extract_options!
=> {:city=>"Tokyo", :country=>"Japan"}
sample-app> params
=> [:name, :age]
sample-app> options
=> {:city=>"Tokyo", :country=>"Japan"}

上記の例では、extract_options!の呼び出しによってparams配列自体からハッシュ要素が削除され、[:name, :age]のみが残っています。

また、最後の要素がハッシュでなければ空のハッシュを返します。

sample-app> params = [:name, :age, :city, :country]
=> [:name, :age, :city, :country]
sample-app> options = params.extract_options!
=> {}
sample-app> params
=> [:name, :age, :city, :country]
sample-app> options
=> {}

extract_options!のコードを見てみる

extract_options!メソッドは Rails 内で Ruby の Array クラスを拡張して実装されています。


activesupport/lib/active_support/core_ext/array/extract_options.rb
# frozen_string_literal: true

class Hash
  # By default, only instances of Hash itself are extractable.
  # Subclasses of Hash may implement this method and return
  # true to declare themselves as extractable. If a Hash
  # is extractable, Array#extract_options! pops it from
  # the Array when it is the last element of the Array.
  def extractable_options?
    instance_of?(Hash)
  end
end

class Array
  # Extracts options from a set of arguments. Removes and returns the last
  # element in the array if it's a hash, otherwise returns a blank hash.
  #
  #   def options(*args)
  #     args.extract_options!
  #   end
  #
  #   options(1, 2)        # => {}
  #   options(1, 2, a: :b) # => {:a=>:b}
  def extract_options!
    if last.is_a?(Hash) && last.extractable_options?
      pop
    else
      {}
    end
  end
end

配列の末尾の要素がハッシュであるかチェックし、ハッシュの場合はpopメソッドでハッシュだけを取り除いて返しています。

extractable_options?によって Hash のサブクラスではなく純粋な Hash インスタンスのみを対象としています。

Railsのソースコードでextract_options!が使われているところ

Railsのソースコードでextract_options!が使われている代表例としてvalidatesメソッドがあります。


activemodel/lib/active_model/validations/validates.rb
  def validates(*attributes)
    defaults = attributes.extract_options!.dup  # ここで使われている
    validations = defaults.slice!(*_validates_default_keys)

    raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
    raise ArgumentError, "You need to supply at least one validation" if validations.empty?

    defaults[:attributes] = attributes

    validations.each do |key, options|
      key = "#{key.to_s.camelize}Validator"

      begin
        validator = key.include?("::") ? key.constantize : const_get(key)
      rescue NameError
        raise ArgumentError, "Unknown validator: '#{key}'"
      end

      next unless options

      validates_with(validator, defaults.merge(_parse_validates_options(options)))
    end
  end

たとえば以下のコードがあるとします。

app/models/book.rb
class Book < ApplicationRecord
  validates :title, presence: true
end

このとき、validatesメソッドの流れは次のようになります。

activemodel/lib/active_model/validations/validates.rb
  def validates(*attributes)  # attributes = [:title, { presence: true }]
    defaults = attributes.extract_options!.dup  # ここで { presence: true } を抽出
    # この時点で attributes と defaults の中身は次のようになる
    # attributes = [:title]
    # defaults = { presence: true }
    
    # 後続の処理に続く...

引数attributesからオプションの部分だけを引き抜いていますね。

このようにextract_options!は、可変長引数(args)を取るメソッドです。

validatesメソッドのように、オプションとしてハッシュを最後に渡すパターンで頻繁に使用されます。

おわりに

extract_options!の存在は初めて知りましたが、わりと便利そうな印象です。

オプションの指定としてハッシュを使っているメソッドは他にもあると思うので、そこでもextract_options!が使われていないか確認してみます。

また、extract_options!が配列を破壊的に変更する点を踏まえて使用する際には注意が必要です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?