nested_form (rails)

#はじめに
railsのgem:nested_formについて使用方法と実際に作成した簡単なサンプルを記事にしました。
以下のGitHubにソースあります。
https://github.com/shotaimai66/nested_form
- masterブランチが、JSを使って、動的に追加された要素の数を制限した機能と、番号を付与する機能を追加したブランチです。
- basicブランチが、今回作るサンプルの内容です。
#想定するアプリ
- アプリ内容
    - ユーザーのCRUDアプリ
    - ユーザーは複数のスマホを持っていて、スマホは複数のアプリを持っている
- リソース
    - User
    - Cellphone
    - App
- アソシエーション
    - User　　     （has_many :cellphones)
    - Cellphone (belongs_to :user, has_many :apps)
    - App       (belongs_to :cellphone)
- ライブラリ等
    - JQuery
    - [nested_form](https://github.com/ryanb/nested_form)
    - Bootstrap4
- 環境
    - AWScloud9
    - Rails 5.2.2
    - ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux]


#rails new
```ruby:コマンド
rails new nested_form
```
#model（データベース）作成
```irb:コマンド
rails g scaffold User name:string       #Userモデル(scaffoldコマンド)
rails g model Cellphone user:references name:string     #Cellphoneモデル
rails g model App cellphone:references name:string           #Appモデル
```

Userモデルをscaffoldコマンドで作成し、コントローラー、ビュー、ルーティングを設定してもらう。
CellphoneモデルとAppモデルをコマンドにreferencesオプションをつけて、モデルの関係を明示する。
#モデル同士の紐付け（アソシエーション）
先ほどのreferencesオプションで紐付けができているか確認する。
かくモデルファイルと、マイグレーションファイルを確認して以下のように修正する。

```ruby:app/models/user.rb
class User < ApplicationRecord
  has_many :cellphones, :dependent => :destroy
  accepts_nested_attributes_for :cellphones, allow_destroy: true
end

```
```ruby:app/models/cellphone.rb
class Cellphone < ApplicationRecord
  belongs_to :user
  has_many :apps, :dependent => :destroy
  accepts_nested_attributes_for :apps, allow_destroy: true
end
```
```ruby:app/models/app.rb
class App < ApplicationRecord
  belongs_to :cellphone
end
```
- `accepts_nested_attributes_for`はリソースをネストさせて、フォームを作成およびネストしたパラメーターを作る為のものです。
- `:dependent => :destroy`と`allow_destroy: true`は親リソースが削除された時に、子リソースの扱いをどうするか明記したもの。今回は、親リソースが削除されると子リソースも削除されるようにしたい為、このように明記します。


```ruby:db/migrate/20190110083809_create_users.rb
class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name

      t.timestamps
    end
  end
end
```
```ruby:db/migrate/20190110084133_create_cellphones.rb
class CreateCellphones < ActiveRecord::Migration[5.2]
  def change
    create_table :cellphones do |t|
      t.references :user, foreign_key: true
      t.string :name

      t.timestamps
    end
  end
end

```
```ruby:db/migrate/20190110084257_create_apps.rb
class CreateApps < ActiveRecord::Migration[5.2]
  def change
    create_table :apps do |t|
      t.references :cellphone, foreign_key: true
      t.string :name

      t.timestamps
    end
  end
end

```
- `t.references :cellphone, foreign_key: true`これで、リソースに参照カラム（user＿id,cellphone_id）の定義と外部キー制約がつきます。

#Gem(nested_form)
gem nested_formのインストール

```irb:Gemfile
gem "nested_form"
```
###application.js修正

```ruby:app/assets/javascripts/application.js
//= require jquery_nested_form   #追記
```

#bundle install

```ruby:コマンド
bundle install
```


#rails db:migrate

```ruby:コマンド
rails db:migrate
```


#ビュー、コントローラーの修正

```ruby:app/controllers/users_controller.rb
# GET /users/new
  def new
    @user = User.new
    @cellphone = @user.cellphones.build
    @cellphone.apps.build
  end

```

```erb:app/views/users/_form.html.erb
<%= nested_form_for(@user) do |f| %>
<!--errors-->
  <% if user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>
      <ul>
        <% user.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
<!--end_errors-->


  <div class="field card" style="width: 500px;">
    <!--user-->
    <h3>user</h3>
    <%= f.text_field :name, {placeholder: "ユーザーの名前"} %>
    <div class="cellphone card" style="width: 400px;">
      <h3>cellphone</h3>
      
      <!--cellphone-->
      <%= f.fields_for :cellphones do |f| %>
        <%= f.text_field :name, {placeholder: "スマホの名前"} %>
        <%= f.link_to_remove "削除" %>
        <div class="test"></div>
          <div class="app card" style="width: 300px;">
            <h3>app</h3>
            
            <!--app-->
            <%= f.fields_for :apps do |f| %>
              <%= f.text_field :name, {placeholder: "アプリの名前"} %>
              <%= f.link_to_remove "削除" %>
            <% end %>
            <%= f.link_to_add "アプリ追加", :apps %>
            <!--end_app-->
            
          </div>
      <% end %>
      <%= f.link_to_add "スマホ追加", :cellphones %>
      <!--end_cellphone-->
      
    </div>
    <!--end_user-->
  </div>

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

```erb:app/views/users/show.html.erb
<p id="notice"><%= notice %></p>

<div class="field card" style="width: 500px;">
    <h3>user</h3>
    <%= @user.name %>
    <div class="cellphone card" style="width: 400px;">
      <h3>cellphone</h3>
      <% @user.cellphones.each do |cellphone| %>
        <%= cellphone.name %>
          <div class="app card" style="width: 300px;">
            <h3>app</h3>
            <% cellphone.apps.each do |app| %>
              <%= app.name %>
            <% end %>
          </div>
      <% end %>
    </div>
</div>

<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

```


###動作確認
![スクリーンショット 2019-01-12 16.03.59.png](https://qiita-image-store.s3.amazonaws.com/0/270321/b2a8cfc8-5f71-9eb0-ee1a-99732fddf1c0.png)


あれ、動かない！！
なんでや！
gem nested_formはjqueryに依存しているので、JQueryを使えるようにしてあげましょう！

#Gemの修正

```ruby:Gemfile
gem 'jquery-rails'

gem 'bootstrap', '~> 4.1.1'     #見た目をよくする為にBootstrapも一緒に入れときます。
```
Bootstrap４導入方法：[Railsアプリで Bootstrap 4 を利用する](https://qiita.com/NaokiIshimura/items/c8db09daefff5c11dadf)

```ruby:コマンド
bundle install
```

###application.js追記

```ruby:app/assets/javascripts/application.js
//= require jquery3
//= require popper
//= require bootstrap-sprockets
```



#見やすいように修正
```scss:app/assets/stylesheets/users.scss
@import "bootstrap";

input {
    margin-top: 20px;
    margin-bottom: 20px;
    margin-left: 20px;
    width: 170px;
}

.card {
    margin-left: 20px;
    margin-bottom: 20px;
}

label {
    display: block;
}

.cellphone {
    background-color: #0000000f;
}


.cellphone > .fields {
    border-bottom: 2px dotted black;
}
```

#完成　イエーイ！
![basic.gif](https://qiita-image-store.s3.amazonaws.com/0/270321/6db08f53-24fd-9744-d2e2-c0458e7c4cde.gif)


#JSでもう少しよくする
[nested_form](https://github.com/ryanb/nested_form)に動的に追加した後に、何かしらの処理ができる方法が載っている。

今回は、以下の３つの機能を実装してみた。
1. スマホをフォームを追加すると、その子要素のアプリフォームも同時に追加する。
2. スマホフォームを動的に追加、削除した時に、要素の数を表示させる。
3. 動的に追加できる数を５つに制限する。

###javascriptの追加（今回は、ビューに直接コーディング）
_form.html.erbの一番下に追記

```erb:app/views/users/_form.html.erb

以上省略＃
<script>
  // スマホフォーム追加でアプリフォーム同時追加
  $(document).on('nested:fieldAdded:cellphones', function(event){
  var field = event.field; 
  var addField = field.find('.add_nested_fields');
  addField.click();
})


$(function() {
  // 動的に追加されたスマホフォームにインデックスを表示
  function setFieldNum() {
      $('.cellphone').children('div.fields:visible').each(function(index) {
        $(this).find('.test').text($('.cellphone').children('div.fields:visible').index(this) + 1)
  });
  }
  //　動的に表示するフォームの数を制限
  $(document).on('nested:fieldAdded', function(e) {
    setFieldNum();
    var link = $(e.currentTarget.activeElement);
    if (!link.data('limit')) {
      return;
    }
    if (link.siblings('.fields:visible').length >= link.data('limit')) {
      link.hide();
    }
  });
 
  $(document).on('nested:fieldRemoved', function(e) {
    setFieldNum();
    var link = $(e.target).siblings('a.add_nested_fields');
    if (!link.data('limit')) {
      return;
    }
    if (link.siblings('.fields:visible').length < link.data('limit')) {
      link.show();
    }
  });
})
</script>
```
link_to_addメソッドに　"data: { limit: 5 }"　を追記

```erb:app/views/users/_form.html.erb

            <%= f.link_to_add "アプリ追加", :apps, data: { limit: 5 }%>
            <!--end_app-->
            
          </div>
      <% end %>
      <%= f.link_to_add "スマホ追加", :cellphones, data: { limit: 5 } %>
      <!--end_cellphone-->
```
![advance.2019-01-12 16_08_46.gif](https://qiita-image-store.s3.amazonaws.com/0/270321/154b6230-3a30-99f3-f473-89c3a95484d5.gif)



#終わり
長くなってしまいましたが、これにて終了。