Railsで非同期処理 セレクトメニューの値による項目の表示制御


# まえがき
　Railsでアプリを作った際に、プルダウンの値によってフォームを追加したり非表示したりする必要が出てきた。これは非同期処理といって、ちょっと難解なものらしかった。実装するとたしかにややこしいものだったので、やりかたを書いていくことにする。


# 完成イメージ

![タイトルなし.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/104211/737fcf03-0c5e-898c-d816-e453417cf697.gif)

　画面遷移せずにフォームが出たり消えたりする。

# 動作フロー

　動作は以下のようになる。

1. 値が変更されたらjsファイルの関数が発火
2. Ajax通信が行われる（指定したコントローラーへデータ送信）
3. Ajax通信のレスポンスデータをもとにviewファイル書き換え

　少しずつ説明していく。


## 0. 下準備：jQueryの導入
　やっていく前にjQueryをRailsに導入する。いろいろあったが、私は以下のようにした。

```js:app/javascript/packs/application.js
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

// --　いろいろ

require("jquery")

require ('home')

// --　いろいろ
// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)

```

```js:app/javascript/home.js
// jsファイルとして新規作成
```


`home.js`　に処理をいろいろ書いていく。

## 1. 値が変更されたらjsファイルの関数が発火

```erb:app/views/reviews/_form.html.erb
<!-- 略 --> 
<div class="col-8 my-4 px-3">
    <label for="label-report" class="form-label">変更後のステータス</label>
    <p><%= f.select :status, ["進行中","中止","完了"] ,{class: "form-control"}%></p>             
    <% if plan.status == "進行中" %>
    <p id = "review_plans_<%= plan.id %>_status_disp" class ="visible">  
    <% else %>
    <p id = "review_plans_<%= plan.id %>_status_disp" class ="imvisible"> 
    <% end %>
    変更後の締め切り:<%= f.date_field :deadline ,{class: "form-control"} %>
    </p>
    <% end %>
</div>
<!-- 略 --> 

```

　ここの「変更後のステータス」のプルダウンの値が変化したことを検出したい。home.jsに以下のように書く。

```js:app/javascript/home.js
var select_field = $('select[id^="review"]');
  $(select_field).change(function(){
    let state = this.value;
    let state_id = this.id;
    console.log("changed");
  });
```

　ここの`$('select[id^="review"]')` はselect要素でidがreviewで始まるものを指定している。


## 2. Ajax通信が行われる（指定したコントローラーへデータ送信）
　RailsでAjax通信を行うには方法がいくつかある。今回はjQueryの`$.ajax()`を使う方法を書く。

### home.jsの編集
　home.jsに書いていく。

```js:app/javascript/home.js
var select_field = $('select[id^="review"]');
  $(select_field).change(function(){

    let state = this.value;
    let state_id = this.id;
    $.ajax({
      type: 'GET', 
      url: '/reviews/change_state', 
      data:  { 'state' : state, 'id' : state_id }, 
      dataType: 'json' 
    })
    .done(function(data){ // dataにはレスポンスされたデータが入る

    })
  });
```

$.ajax()はjQueryでのajax通信を行う関数である。パラメーターは以下のようになる。

type: HTTP通信の種類
url: リクエストを送信する先のURL
data: サーバーに送信するデータ
dataType: データのタイプ


http://semooh.jp/jquery/api/ajax/jQuery.ajax/options/

### ルーティングの設定

リクエストの送信先は`/reviews/change_state`　としているが、まだルーティングの設定をしていない。これではrailsがどこへ送ればいいか迷ってしまう。routeを編集する。

```config/routes.rb
Rails.application.routes.draw do
# 略 ...

  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
# 略 ...
  resources :reviews do
# 略 ...
    collection do
      get 'change_state'
    end
  end
end

```

 resourcesを使って、reviewのリソースベースなルーティングをつくったのちに、collectionを使う。これでidを伴わないパスを認識して、reviewsのchange_stateアクションへ送ってくれる。

### コントローラーの設定

コントローラーはこのように編集する。

```app/controllers/reviews_controller.rb
# 略

  def change_state
    @messages = params
    respond_to do |format| # リクエスト形式によって処理を切り分ける
      format.html { redirect_to :root } # html形式の場合
      format.json { render json: @messages } # json形式の場合
    end
  end

# 略
```

`respond_to`を使って、リクエストされた方法によって処理を場合分けする。単純にURLを送信された場合はrootへリダイレクトされるようにしておく。リンクはアプリ上で貼っていないので、URL直打ちを想定する。

ajax通信でJSON形式でリクエストされた場合はJSONに変換した@message を返すようにする。

この@messages (params)には何が入っているか。binding.pryを挟んで処理中に覗いてみる。

```
params
=> <ActionController::Parameters {"state"=>"中止", "id"=>"review_plans_9_status", "controller"=>"reviews", "action"=>"change_state"} permitted: false>
```

stateとidが付与されている。これは$.ajax()で指定したデータである。`render json`すると、以下のようになる。

```
render json:params
=> "{\"state\":\"中止\",\"id\":\"review_plans_9_status\",\"controller\":\"reviews\",\"action\":\"change_state\"}"
```

## 3. Ajax通信のレスポンスデータをもとにviewファイル書き換え

コントローラーの戻り値は$.ajax()の`.done(function(data){ ` の`data`に格納される。

```js:app/javascript/home.js
var select_field = $('select[id^="review"]');
  $(select_field).change(function(){

    let state = this.value;
    let state_id = this.id;
    $.ajax({
      type: 'GET', 
      url: '/reviews/change_state', 
      data:  { 'state' : state, 'id' : state_id }, 
      dataType: 'json' 
    })
    .done(function(data){ 
      const p1 = document.getElementById(`${data.id}_disp`);
      p1.classList.remove('visible','imvisible');
      if (data.state == "進行中"){
        p1.classList.add('visible');
      }else{
        p1.classList.add('imvisible');
      }
    })
  });
```

`document.getElementById` でdata.idがある要素を取得している。それをp1に代入。そのp1のclasslistから一旦表示非表示を制御しているクラスを除去する。

その後でstateに応じてvisible/imvisible　クラスを付与している。このクラスはcssに書いている。

viewファイルとcssは以下のようになっている。

```erb:app/views/reviews/_form.html.erb
<!-- 略 -->
<div class="col-8 my-4 px-3">
    <label for="label-report" class="form-label">変更後のステータス</label>
    <p><%= f.select :status, ["進行中","中止","完了"] ,{class: "form-control"}%></p>             
    <% if plan.status == "進行中" %>
      <p id = "review_plans_<%= plan.id %>_status_disp" class ="visible">  
    <% else %>
      <p id = "review_plans_<%= plan.id %>_status_disp" class ="imvisible"> 
    <% end %>
        変更後の締め切り:<%= f.date_field :deadline ,{class: "form-control"} %>
      </p>
    <% end %>
</div>
<!-- 略 -->
```

```css:app/assets/stylesheets/application.scss
//略
 .visible {
  display:inline;  
 }

 .imvisible {
  display:none; 
 }
//略

```


完成。



## おわり

なんとか記事が書けた。これからもつまづいたところがあったら文章に残しておく。


### めっちゃ参考になったリンク

https://pikawaka.com/rails/ajax-jquery