Rails6 のちょい足しな新機能を試す36（ActionController::Parameters#each_value 編）

# はじめに

Rails 6 に追加されそうな新機能を試す第36段。 今回は、 `ActionController::Parameters#each_value` 編です。
Rails 6 では、 `ActionController::Parameters` に `each_value` が追加されました。
`Hash#each_value` と(少し違いますが)同様のメソッドです。 Hash と同じ様なメソッドがあった方がいいよねってことで追加されたみたいです。

Ruby 2.6.3, Rails 6.0.0.rc1 で確認しました。Rails 6.0.0.rc1 は `gem install rails --prerelease` でインストールできます。

```shell
$ rails --version
Rails 6.0.0.rc1
```

今回、機能を試すための良い例を思いつくことができませんでした。

あまり良い例ではありませんが、無理矢理、今回の機能を試すために、英語の試験結果を登録する機能を作ることにしました。

- 試験は Reading, Writing, Speaking, Listening の4つの項目に分かれています。
- 各項目は、100点満点です。
- 4つの項目の平均点が70点以上なら合格、それ以外は不合格にします。
- 受験者の氏名を入力できます。
- データベースには、氏名と4つの項目の点数の他に、合否判定の項目を登録します。


# rails プロジェクトを作る

```shell
$ rails new rails6_0_0rc1
$ cd rails6_0_0rc1
```

# scaffold を実行する

試験結果を管理するための CRUD を作ります。合否判定のカラムが `passed` です。

```shell
$ bin/rails g scaffold EnglishScore name reading:integer writing:integer speaking:integer listening:integer passed:boolean
```

# 登録フォームを変更する

登録フォームから passed のチェックボックスを削除します。

```ruby:app/views/english_scores/_form.html.erb
<%= form_with(model: english_score, local: true) do |form| %>
  <% if english_score.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(english_score.errors.count, "error") %> prohibited this english_score from being saved:</h2>

      <ul>
        <% english_score.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>

  <div class="field">
    <%= form.label :reading %>
    <%= form.number_field :reading %>
  </div>

  <div class="field">
    <%= form.label :writing %>
    <%= form.number_field :writing %>
  </div>

  <div class="field">
    <%= form.label :speaking %>
    <%= form.number_field :speaking %>
  </div>

  <div class="field">
    <%= form.label :listening %>
    <%= form.number_field :listening %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>
```

# controller を変更する

`EnglishScoresController` の `english_score_params` メソッドの `passed` 項目を削除します。

```ruby:app/controllers/english_scores_controller.rb
class EnglishScoresController < ApplicationController
    ...
    # Never trust parameters from the scary internet, only allow the white list through.
    def english_score_params
      params.require(:english_score).permit(:name, :reading, :writing, :speaking, :listening)
    end
end
```

# controller を変更する(2)

ここで無理矢理、 `each_value` を試すため、合格したか、どうかを判定するプライベートメソッド `passed` を追加します。

```ruby:app/controllers/english_scores_controller.rb
    def passed
      # The following line raises LocalJumpError (no block given)
      # because each_value method requires block, does not
      # returns Enumerator object when block is not given.
      # { passed: english_score_params.each_value.map(&:to_i).sum / 4.0 >= 70.0 }

      total = 0
      english_score_params.each_value do |score|
        total += score.to_i
      end
      { passed: total / 4.0 >= 70.0 }
    end
```

`ApplicationController::Parameters#each_value` は `Hash#each_value` と違いブロックを渡す必要があります。
ブロックを渡さない場合は、 `LocalJumpError (no block given)` が発生します。

# controller を変更する(3)

`EnglishScoresController` の `create` と `update` メソッドを修正します。
合否の判定結果を `EnglishScore` モデルに渡すようにします。

```ruby:app/controllers/english_scores_controller.rb
class EnglishScoresController < ApplicationController
  ...
  def create
    @english_score = EnglishScore.new(english_score_params.merge(passed))
    ...
  end

  def update
    ...
      if @english_score.update(english_score_params.merge(passed))
    ...
  end
  ...
end
```

# その他

今回、無理矢理、 `ApplicationController::Parameters#each_value` を試すためにコントローラで合否判定をしましたが、
本来の実装であれば、 `EnglishScore` モデルで合否判定すべきで、コントローラでやるべきではないと考えています。
また、 name に数字で始まる名前を入力したら平均点が高くなるというツッコミとかも無しでお願いします。

# 試したソース

試したソースは以下にあります。
https://github.com/suketa/rails6_0_0rc1/tree/try036_params_each_values

# 参考情報

* [What is new in Rails 6.0](https://bogdanvlviv.com/posts/ruby/rails/what-is-new-in-rails-6_0.html)
* [Added ActionController::Parameters.each_value methods](https://github.com/rails/rails/pull/33979)