ネストしたモデルの入力フォーム実装

以下(Sorce:)のURLを参考にRails3のサンプルアプリを実装してみました。

まずはscaffoldでカテゴリとサブカテゴリを作成します。

$ rails g scaffold category name:string code:string available:boolean
$ rails g scaffold sub_category category_id:integer name:string code:string available:boolean


jQueryを使いたいので、Gemfileに一行追加して

gem 'jquery-rails'

jquery-railsのインストールとマイグレーションを実行します。

$ bundle install
$ rails g install:jquery
$ rake db:migrate
モデルの関連付け

app/models/category.rb

class Category < ActiveRecord::Base
  has_many :sub_categories, :class_name => "SubCategory", :dependent => :destroy
  accepts_nested_attributes_for :sub_categories, :allow_destroy => true
end


app/models/sub_category.rb

class SubCategory < ActiveRecord::Base
  belongs_to :category, :class_name => "Category"
end
ヘルパーとJSの実装

app/helpers/application_helper.rb

module ApplicationHelper
  def link_to_remove_fields(name, f)
    f.hidden_field(:_destroy) + link_to_function(name, "remove_fields(this)")
  end

  def link_to_add_fields(name, f, association)
    new_object = f.object.class.reflect_on_association(association).klass.new
    fields = f.fields_for(association, new_object, :child_index => "new_#{association}") do |builder|
      render("fields_" + association.to_s.singularize, :f => builder)
    end
    link_to_function(name, raw("add_fields(this, \"#{association}\", \"#{escape_javascript(fields)}\")"))
  end
end

子要素を追加、削除するリンクをそれぞれ追加しています。
※ 注1: link_to_add_fieldsメソッドのrenderする部分テンプレートの名前を変更しました。
※ 注2: link_to_functionの第二引数、h()→raw()に修正しました。(Rails3なので)


public/javascripts/application.js

// application_jquery.js
function remove_fields(link) {
    $(link).prev("input[type=hidden]").val("1");
    $(link).closest(".fields").hide();
}

function add_fields(link, association, content) {
    var new_id = new Date().getTime();
    var regexp = new RegExp("new_" + association, "g")
    $(link).parent().before(content.replace(regexp, new_id));
}

参考サイトの実装をそのままコピペ

ビューの実装

app/views/categories/_form.html.erb

  <!-- ここから追加 -->
  <div class="field">
    <%= f.fields_for :sub_categories, @sub_categories do |sub_category| %>
      <%= render 'fields_sub_category', {:f => sub_category} %>
    <% end %>
  <%= link_to_add_fields "新しくサブカテゴリを追加する", f, :sub_categories %>
  </div>
  <!-- ここまで -->
  <div class="actions">
    <%= f.submit %>
  </div>


app/views/categories/_fields_sub_category.html.erb(sub_categories/_form.html.erbをコピーして修正)

<div class="fields">
  <h2>サブカテゴリ</h2>
  <%= link_to_remove_fields "削除", f %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :code %><br />
    <%= f.text_field :code %>
  </div>
  <div class="field">
    <%= f.label :available %><br />
    <%= f.check_box :available %>
  </div>
</div>

※ 注1: div.fieldsは忘れずに指定してください。JSでエラーになってしまいます。



Source:
#196 Nested Model Form Part 1 - RailsCasts
#197 Nested Model Form Part 2 - RailsCasts