Rails4: has_manyの保存自動化とタブでの表示分け— accepts_nested_attributes_for, Strong Parameters, Bootstrap


has_manyしている項目を fields_for で記述して、これをBootstrapのタブで分けて表示して、さらにその保存はassociation を介して自動化する(コントローラでhas_many項目の保存を意識しないでよいようにする)ということについてまとめて書きます。

今回のポイントは、 accepts_nested_attributes_for と、そのための strong parameters の書き方、fields_for を使ってタブ表示するためにobjectの値を取得する方法です。


1.前提:モデルの構造

人とその住所のアソシエーションを例とします。

personモデル

※今回の説明には以下は必要ないですが、とりあえず以下のようにしたとします。

person_name string 氏名
gender string 性別
birth_day string 誕生日
addressモデル
location string 場所の種類 「自宅」「会社」「出向先」など
zip string 郵便番号
prefecture string 都道府県
street text 住所
person_id integer belong_to する person

2.モデルの記述

person.rb

accepts_nested_attributes_for を使いますね。

  has_many :addresses, :dependent => :destroy
  accepts_nested_attributes_for :addresses

address.rb

  belong_to :person

3.コントローラの記述

person_controller.rb

# update は @person.update(person_params) だけでよい
def update
  if !@person.update(person_params)
    .... something to process ....
  end
  render ....
end

ポイントは person_params で使う Strong Parameter の記述です。
:addresses_attributes を使うのですね。

def person_params
  params.require(:person).permit( :id, :person_name, :gender, :birth_date, 
    :addresses_attributes => [:id, :location, :zip, :prefecture, :street])
end

4.ビューの記述

has_many を表示するのに fields_for を使いますね。
fields_for 自体が存在するaddressの数分ループもしてくれるのですごく便利です。

<%= form_for :person do |f| %>
  ..... person 用の要素の記述 .....
  <%= f.fields_for :addresses do |fa|%>
    <div class="form-group">
      <%= fa.text_field :place, :placeholder => "PLACE NAME", :class => "form-control" %>
      <%= fa.text_field :zip, :placeholder => "ZIP", :class => "form-control" %>
      <%= fa.select :prefecture, PREFECTURE_LIST,{:include_blank => true}, :class => "form-control" %>
      <%= fa.text_area :street, :row => 2, :placeholder => "STREET",  :class => "form-control" %>
    </div>
  <% end %>

5.ビューをタブ表示にする

上の形だと、住所が全てダラダラと表示されるのでBootstrapのタブを付け加えてみます。
タブのラベルには address.location を、 タブのidには address.id を使います。

form.html.erb—タブラベル部分

 ※最初のタブには class=”active” が必要

<% address_first_id = @pesson.addresses.first.id %>
<div class="nav nav-tabs">
  <ul class="nav nav-tabs">
    <% addresses.each do |address|%>
      <li<%= ' class="active"' if address.id == address_first_id %>><a href="#address_<%= address.id.to_s %>" data-toggle="tab"><%= address.location %></a></li>
    <% end %>
  </ul>
</div>

form.html.erb—タブの中身部分

 ※ここでのポイントは fa.object.id というようにobject記述でデータの内容を読み出せること。

<div id="addressesTabContent" class="tab-content">
  <%= f.fields_for :addresses do |fa|%>
    <div class="tab-pane <%= "active" if fa.object.id == address_first_id %> in" id="address_<%= fa.object.id.to_s %>">
      <div class="form-group">
        <!-- ここには address.location はいらない。ラベルに表示しているので -->
        <%= fa.text_field :zip, :placeholder => "ZIP", :class => "form-control" %>
        <%= fa.select :prefecture, PREFECTURE_LIST,{:include_blank => true}, :class => "form-control" %>
        <%= fa.text_area :street, :row => 2, :placeholder => "STREET",  :class => "form-control" %>
      </div>
    </div> <!-- /tab-pane -->
</div> <!-- /addressesTabContent -->

以上です。概念説明のメモとして簡略化してますので、個々に解決が必要な細かい問題はあるかもしれません。

補足:fields_forのchild_indexを使う方法

上記の例は address.id をタグのidとしていたが、単純に頭から0,1,2…と番号を振って、fields_for では、その child_index を使うという方法もある。fields_for の index は fa.options[:child_index] で取得できる。


参考にさせていただいたページ

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中