【Rails】セレクトボックスの選択によって動的に変化するコンテンツの実装

## はじめに
セレクトボックスの選択肢を選ぶと非同期的にコンテンツが変更されるサイトが結構あると思います。
例えばAmazonで商品の色を選んだ時とか。
これをRailsで実装する方法を記していきます。

### 注意
Rails学習中の初学者による記事です。
内容に誤りを含む可能性・さらに良い手法がある可能性が多分にありますので、参考にする際はその点ご留意ください。
また、間違いのご指摘やアドバイスは大歓迎です。

### RubyとRailsのバージョン
下記のバージョンにて動作確認しています。
	- Ruby 2.5.1
	- Rails 5.2.1

## 処理の流れ
Ajaxで非同期的にコンテンツ内容を変更しますが、今回はRailsのViewで`remote: true`を設定していくことにします。
AjaxはjQuery.ajax()を使用して実装する方法もありますが、そちらは検索すればたくさん記事が出てくると思うので、調べてください。

`remote :true`を使用した際の流れは
	1. セレクトボックスの選択肢を選択
	2. セレクトボックスの変更をjQueryで読み取り、フォームをsubmitする
	3. フォームからJS形式のリクエストが送信され、ルーティングによりコントローラー/アクションの判定
	4. コントローラーのアクションが動作し、`app/views/コントローラー名/アクション名.js.erb`を呼び出す
	5. `アクション名.js.erb`内でhtmlをレンダリング
となります。
3〜5の部分がAjax（非同期通信）の処理です。[こちらの記事](https://qiita.com/__tambo__/items/45211df065e0c037d032)にAjax処理のステップが詳しく書いてありますので、参考にしてください。

## モデル
モデルはProduct, Variation, Imageの3つです。
シンプルに、1つのProductが複数のVariations（今回は色違いとします）を持ち、Variationはそれぞれ1枚のImageを持ちます。

```rb:
class Product < ActiveRecord::Base
	has_many :images
end

class Variation < ActiveRecord::Base
	belongs_to :product
	has_one :Image
end

class Image < ActiveRecord::Base
	belongs_to :variation
end
```

## コントローラー
コントローラーはProductsのみです。

```rb:products_controller.rb
class ProductsController < ApplicationController
  def show
		@product = Product.find(params[:id])
		@variation = @product.variations.first
  end

	def ajax
		@selected_color = params[:color]
		@product = Product.find(params[:id])
		@variation = @product.variations.find_by(color: @selected_color)
	end
end
```

showアクションでproductの詳細ページが表示されますが、最初に示されるvariationはとりあえず`product.variations.first`としておきます。
ajaxアクションはJS形式のリクエストを受け取り、選択されたcolorのvariationを呼び出します。

## ルーティング
routes.rbは下記の通りです。

```rb:routes.rb
Rails.application.routes.draw do
  get 'products/:id', to: 'products#show'
	get 'ajax', 	    to: 'products#ajax'
end
```

## View
Viewはproduct情報、variation画像、セレクトボックスから構成されています。variation画像とセレクトボックスは`_variant.html.erb`として、パーシャル化しています。

```rb:show.html.erb
<div class="product_info">
	<%= @product.name %>
	<%= @product.description %>
</div>
<div class="variation_contents">
	<%= render 'variation', { product: @product, variation: @variation, selected_color: @selected_color } %>
</div>
```

```rb:_variation.html.erb
<div class="variation_image">
	<%= image_tag variation.image.url %> #carrierwaveの場合
</div>
<span class="quick-drop">
	<%= form_tag ajax_path, method: :get, remote: true do %>
	  <%= hidden_field_tag :id, product.id %>
	  <%= select_tag :color, options_for_select(["Red", "Green", "Blue"], selected_color), class: "select-drop" %>
	<% end %>
</span>
```

このパーシャル部分をAjaxで更新することになります。

- remote: trueとすることでフォームのリクエストがJS形式で送られることになります。
- variationを特定するためにまずproductを特定しなければなりません。hidden_field_tagでproductのidを埋め込んでおきましょう。
- options_for_selectでオプションタグを生成します。なお、第二引数にデフォルト値を入れられるので、Ajax更新後も選択されたcolorを表示できるように、@selected_colorに格納したcolorをデフォルト値としています。[詳しくはRails Guideを参照してください](https://railsguides.jp/form_helpers.html)

## Ajaxの実装
JS形式で発行されたリクエストを受け取ったコントローラーは`アクション名.js.erb`を呼び出しますので、以下のファイルを作成します。

```rb:ajax.js.erb
$('.variation_contents').html('<%= escape_javascript(render 'variation', { product: @product, variation: @variation, selected_color: @selected_color } ) %>')
```

variation_contentクラスに対して、variationパーシャルをレンダリングするという処理になります。
この記述により、showテンプレート内部のvariation_contentが非同期的に置換されます。ただし、このままではフォームの自動送信が行われないため、colorを選択してもAjaxは起こりません。
セレクトボックスの変更を読み取り自動送信する動作を実装していきましょう。

## jQueryによる自動送信の実装

```js:custom.js
    jQuery(document).bind('ready ajaxComplete', function() {
      $('.select-drop').change(function() {
	  	  $(this).parent().submit();
	    });
    });
```

- select-dropクラスが変更された時、その親要素（今回はform）をsubmitします。
- `bind('ready ajaxComplete', function()`について、`jQuery(document).ready()`とするとページ読み込みを起点に動作することになり、Ajax後（つまりリロードされず、一部のコンテンツのみ更新される）には動作しないことになってしまいます。そこで、`ajaxComplete`も付加することでリダイレクト時、Ajax後どちらでも動作するようにしています。

## 完成
これで完成です。サイト上に表示されたセレクトボックスからcolorを選択すると、そのcolorを持つvariationのimageに切り替わるはずです。

## まとめ
セレクトボックスの変更によって動的にコンテンツが変わるサンプルコードを紹介しました。
今回は選択肢がcolorのみ、画像も各variationに1枚ずつというシンプルなものでしたが、紹介したコードを応用すればもうちょっと複雑な組み合わせも可能になると思います。
自分で実装する上でかなり苦戦した内容でしたので、同じように困っている人の助けになれば幸いです。

間違いの指摘、より良い方法等ありましたらご指摘いただけると嬉しいです！

## 参考文献
-  [https://qiita.com/__tambo__/items/45211df065e0c037d032](https://qiita.com/__tambo__/items/45211df065e0c037d032) 
- [【Rails】select_tagを使って表示内容を変える - 主婦がアプリをリリースするまでの軌跡](http://song-of-life.hatenablog.com/entry/2017/11/12/004027) 
- [https://qiita.com/fumikao/items/373caa60b77f27f2dbdd](https://qiita.com/fumikao/items/373caa60b77f27f2dbdd) 
- [jQuery: both .ready() and .ajaxComplete - Stack Overflow](https://stackoverflow.com/questions/19267072/jquery-both-ready-and-ajaxcomplete)
- https://www.kopjapan.com/blog/web/jqueryでajax完了後に実行開始したい処理があるときの対