フォームオブジェクトで fields_for を使う:newの場合

続きはこちら → フォームオブジェクトで fields_for を使う:editの場合

環境

やりたいこと

RailsGuids日本語訳:Action View フォームヘルパーにあるとおり、オブジェクト同士が1対多や1対1の関係になっているときの fields_forヘルパーの使い方はいろいろ分かったのだけど、1対多や1対1の関係になっていない、複数のオブジェクトをまとめて作成したり更新したりするときの fields_forヘルパー の使い方がわからない。

そこで、互いに1対多や1対1の関係になっていないオブジェクトをフォームオブジェクトの要素にした上で、 fields_forヘルパー を使いたい。

想定

フォームオブジェクト BookInputForm を用意し、このオブジェクトには Book オブジェクトが複数含まれる。Bookオブジェクトは、title、author、price、isbnを属性としてもつ。

プロジェクトの用意

% rails new fields_for_demo --skip-bundle
% cd fields_for_demo
% vi Gemfile

以下をコメントアウト&追加

  • gem 'therubyracer', platforms: :ruby
  • gem "bootstrap-sass", "~> 3.3.0"
  • gem "bootstrap-datepicker-rails"
  • gem "bootstrap_form"
% bundle install

Bootstrapを有効にする

bootstrap-sassbootstrap_formを有効にする。

% mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scss
% vi app/assets/stylesheets/application.scss

中身を以下のようにする。

@import "bootstrap-sprockets";
@import "rails_bootstrap_forms";
@import "bootstrap";

Javascriptの方も変更する。

% vi app/assets/javascripts/application.js

中身を以下のようにする。

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap-sprockets
//= require_tree .

Bookオブジェクト作成

scaffoldで作る。

% rails g scaffold book title:string author:string price:integer isbn:string
% rake db:migrate

フォームオブジェクト作成

% mkdir -p app/forms
% touch app/forms/book_input_form.rb
% vi app/forms/book_input_form.rb

中身は以下のようにした。books属性にBookオブジェクトを複数個格納する。books_attributes を定義することでフォームオブジェクトでも fields_forを使うことができる(1対多のときの accepts_nested_attributes_for の代わり。ActiveModel::Modelでは、has_manyを指定できないため、こうする必要がある)。

class BookInputForm

  include ActiveModel::Model

  attr_accessor :books
 
  def books_attributes=(attributes)
    @books ||= []
    attributes.each do | i, book_params|
      @books.push(Book.new(book_params))
    end
  end

  def save
    @books.each do | book |
      book.save
    end
  end
end

コントローラー作成

別につくる必要なないのだけど今回は作成した。綺麗じゃないし無駄があると思う。

% touch app/controllers/input_forms_controller.rb
% vi app/controllers/input_forms_controller.rb

中身は以下のようにした。

class InputFormsController < ApplicationController

  def new
    @book_list = BookInputForm.new(books: [Book.new, Book.new]) # 2つの空の入力欄を用意するため
  end

  def create
    @book_list = BookInputForm.new(book_input_params)
    @book_list.save
    redirect_to books_path  
  end

  private

  def book_input_params  # Rails 4以降のStrong Parameter対応。
    params.require(:book_input_form).permit(books_attributes: [:title, :author, :price, :isbn])
  end
end

config/routes.rb に InputForms を追加しておく

Views

% mkdir app/views/input_forms
% touch app/views/input_forms/new.html.erb

中身は以下のとおり。これで、InputFormsController#new で用意した分のBookオブジェクト数だけ、入力欄が表示される。

<%= bootstrap_form_for(@book_list, :url => {:controller => :input_forms, :action => :create}) do | form_data |-%>
    <%= form_data.fields_for :books do | field_data | -%>
      <%= field_data.text_field :title %>
      <%= field_data.text_field :author %>
      <%= field_data.number_field :price %>
      <%= field_data.text_field :isbn %>
    <% end -%>
  <%= form_data.submit %>
<% end -%>

続きはこちら → フォームオブジェクトで fields_for を使う:editの場合