Railsの「params」というメソッドがよく分からない


こんにちは、Ruby初心者で恐縮ですが、Railsを勉強していて、コントローラー内で使っている一見ハッシュのように見える`params[xxx]`が気になって仕方がないのです。

ハッシュじゃないのにあたかもハッシュのような書き方になっています。`ActionController::Parameters`のインスタンスであること以外何も分からなくてちょっと気持ち悪いです。

これってjsの言うところの「 **array like object**
」的なものなのでしょうか？実際どうやって作られているのでしょうか？できれば簡単なサンプルコードを頂ければ幸いです。




> ハッシュじゃないのにあたかもハッシュのような書き方になっています

Rails 4 では、 ActionController::Parameters の親クラスの親クラスがHashです。  
つまり、ハッシュと継承関係にあるのでハッシュと同じように振る舞います。

[code]

    # ActionController::Parameters
    class Parameters < ActiveSupport::HashWithIndifferentAccess
    
    # ActiveSupport::HashWithIndifferentAccess
    class HashWithIndifferentAccess < Hash
    
[/code]

> これってjsの言うところの「array like object」的なものなのでしょうか？

"array like object"という用語が初耳だったので、こちらのページを読んでみました。

<http://nfriedly.com/techblog/2009/06/advanced-javascript-objects-arrays-and-
array-like-objects/>

なるほど、「Arrayと同じメソッドをいくつか持っているが、完全な互換性はないオブジェクト」のことを指すようですね。

その定義からすると、ActionController::Parametersは"Array(Hash) like object"ではありません。  
ハッシュを継承しているので、 "ActionController::Parameters is a Hash"と言えます。  
つまりハッシュと完全な互換性があります。（後述するキーの扱いを除いて）

なお、Rails5では、使い方によっては安全のためのフラグがクリアされてしまうなど、パラメータへのアクセス以外のメソッド(Enumerable由来のものなど)が使えてしまうことの弊害があることから、
ActionController::parameters は HashWithIndifferentAccess
のサブクラスではなくなっています。([PR](https://github.com/rails/rails/pull/14384) /
[コミット(一部)](https://github.com/rails/rails/commit/14a3bd520dd4bbf1247fd3e0071b59c02c115ce0))

> 実際どうやって作られているのでしょうか？できれば簡単なサンプルコードを頂ければ幸いです。

クラス定義は上に載せたとおりです。詳しくはRailsのコードを読んでみてください。

  * <https://github.com/rails/strong_parameters/blob/master/lib/action_controller/parameters.rb>
  * <https://github.com/rails/rails/blob/master/activesupport/lib/active_support/hash_with_indifferent_access.rb>

インスタンスの作り方という点では、以下のように new + keyとvalue でインスタンスを作成できます。  
（とはいえ、自分でActionController::Parametersをnewすることは滅多にないですが）

[code]

    params = ActionController::Parameters.new(name: 'Alice', email: 'alice@example.com') 
    => {"name"=>"Alice", "email"=>"alice@example.com"}
    
[/code]

ハッシュと同じように扱えるので、keyとvalueを追加することもで来ます。

[code]

    params['tel'] = '123456'
    => "123456"
    
[/code]

ただし、HashWithIndifferentAccess（ActionController::Parametersの親クラス）はkeyとしてシンボルと文字列を区別しないようにハッシュを拡張しています。

[code]

    params[:name]
    "Alice"
    
    params['name'] 
    "Alice"
    
    params['tel']
    "123456"
    
    params[:tel]
    "123456"
    
[/code]

ちなみにRails本体でActionController::Parametersのインスタンスを作成しているコードは以下のようになっています。

[code]

    # https://github.com/rails/strong_parameters/blob/master/lib/action_controller/parameters.rb
    module ActionController
      # ...
      module StrongParameters
        # ...
        def params
          @_params ||= Parameters.new(request.parameters)
        end
    
[/code]

`def params` がcontrollerでよく登場する `params` の正体です。

こんな感じで疑問点は解消されたでしょうか？  
他に不明な点があればコメントください。

**P.S.**

余談ですがこういう疑問を持ったときはRubyMineのコードジャンプ機能を使うと便利です。  
有料のIDEですが、使いこなせばデバッグやコードリーディングの時間をかなり節約できるので、導入を検討してみると良いかもしれません。

  * [RubyMineのコードジャンプ機能は本当にすごい！！困ったときはCommand+Bを押すべし！](http://qiita.com/jnchito/items/bcb2bb5311a44113fdd9)
  * [僕がRubyMineを使い始めたたった１つの理由](http://qiita.com/LuckOfWise/items/c961a046d40a20436932)

僕もこの回答を書くにあたり、RubyMineを使ってクラスやメソッドの定義元を探していきました。




> ハッシュじゃないのにあたかもハッシュのような書き方になっています

Rails 4 では、 ActionController::Parameters の親クラスの親クラスがHashです。  
つまり、ハッシュと継承関係にあるのでハッシュと同じように振る舞います。

[code]

    # ActionController::Parameters
    class Parameters < ActiveSupport::HashWithIndifferentAccess
    
    # ActiveSupport::HashWithIndifferentAccess
    class HashWithIndifferentAccess < Hash
    
[/code]

> これってjsの言うところの「array like object」的なものなのでしょうか？

"array like object"という用語が初耳だったので、こちらのページを読んでみました。

<http://nfriedly.com/techblog/2009/06/advanced-javascript-objects-arrays-and-
array-like-objects/>

なるほど、「Arrayと同じメソッドをいくつか持っているが、完全な互換性はないオブジェクト」のことを指すようですね。

その定義からすると、ActionController::Parametersは"Array(Hash) like object"ではありません。  
ハッシュを継承しているので、 "ActionController::Parameters is a Hash"と言えます。  
つまりハッシュと完全な互換性があります。（後述するキーの扱いを除いて）

なお、Rails5では、使い方によっては安全のためのフラグがクリアされてしまうなど、パラメータへのアクセス以外のメソッド(Enumerable由来のものなど)が使えてしまうことの弊害があることから、
ActionController::parameters は HashWithIndifferentAccess
のサブクラスではなくなっています。([PR](https://github.com/rails/rails/pull/14384) /
[コミット(一部)](https://github.com/rails/rails/commit/14a3bd520dd4bbf1247fd3e0071b59c02c115ce0))

> 実際どうやって作られているのでしょうか？できれば簡単なサンプルコードを頂ければ幸いです。

クラス定義は上に載せたとおりです。詳しくはRailsのコードを読んでみてください。

  * <https://github.com/rails/strong_parameters/blob/master/lib/action_controller/parameters.rb>
  * <https://github.com/rails/rails/blob/master/activesupport/lib/active_support/hash_with_indifferent_access.rb>

インスタンスの作り方という点では、以下のように new + keyとvalue でインスタンスを作成できます。  
（とはいえ、自分でActionController::Parametersをnewすることは滅多にないですが）

[code]

    params = ActionController::Parameters.new(name: 'Alice', email: 'alice@example.com') 
    => {"name"=>"Alice", "email"=>"alice@example.com"}
    
[/code]

ハッシュと同じように扱えるので、keyとvalueを追加することもで来ます。

[code]

    params['tel'] = '123456'
    => "123456"
    
[/code]

ただし、HashWithIndifferentAccess（ActionController::Parametersの親クラス）はkeyとしてシンボルと文字列を区別しないようにハッシュを拡張しています。

[code]

    params[:name]
    "Alice"
    
    params['name'] 
    "Alice"
    
    params['tel']
    "123456"
    
    params[:tel]
    "123456"
    
[/code]

ちなみにRails本体でActionController::Parametersのインスタンスを作成しているコードは以下のようになっています。

[code]

    # https://github.com/rails/strong_parameters/blob/master/lib/action_controller/parameters.rb
    module ActionController
      # ...
      module StrongParameters
        # ...
        def params
          @_params ||= Parameters.new(request.parameters)
        end
    
[/code]

`def params` がcontrollerでよく登場する `params` の正体です。

こんな感じで疑問点は解消されたでしょうか？  
他に不明な点があればコメントください。

**P.S.**

余談ですがこういう疑問を持ったときはRubyMineのコードジャンプ機能を使うと便利です。  
有料のIDEですが、使いこなせばデバッグやコードリーディングの時間をかなり節約できるので、導入を検討してみると良いかもしれません。

  * [RubyMineのコードジャンプ機能は本当にすごい！！困ったときはCommand+Bを押すべし！](http://qiita.com/jnchito/items/bcb2bb5311a44113fdd9)
  * [僕がRubyMineを使い始めたたった１つの理由](http://qiita.com/LuckOfWise/items/c961a046d40a20436932)

僕もこの回答を書くにあたり、RubyMineを使ってクラスやメソッドの定義元を探していきました。

