読者です 読者をやめる 読者になる 読者になる

Rails Webook

自社のECを開発している会社で働いています。Rails情報やサービスを成長させる方法を書いていきます

Railsのform_for内のコードをすっきりさせるsimple_formの使い方

Rails View Rails gem

Railsのform_for内のコードをすっきりさせることができる、simple_formのインストール方法、使い方、カスタマイズ方法、バリデーションエラーの表示について説明します。

form_forで書いたコードです。
f:id:nipe880324:20140730145943p:plain:w320

これが、simple_formで書き直すとことにより、次のようにとてもすっきりさせることができます。
f:id:nipe880324:20140730145948p:plain:w320


動作確認

・Rails 4.1
・simple_form 3.0.2


0. simple_formを使う環境の作成

まず、simple_formを使うには、フォーム内がごちゃごちゃしていないと意味があまり無いので、その環境を作ります。

Railsプロジェクトの作成

Railsプロジェクトを作成します。

rails new simple_form_test
cd simple_form_test
ModelとDBの作成とScaffoldの実施

今回作成するプロジェクトのER図は次のようになっています。
CategoryとProductを多対多関係にするために、Categorizationというテーブルを作成しています。
f:id:nipe880324:20140728195741p:plain:w320

rails g model Category name:string
rails g model Publisher name:string
rails g scaffold Product name:string price:integer released_on:date rating:integer discontinued:boolean publisher:references
rails g model Categorization product:references category:references

Productのdiscontinuedカラム(販売中止かどうかのフラグ)をデフォルトではfalseにしたいので、defaultを設定します。

# db/migrate/yyyymmddhhmmss_create_products.rb
  t.boolean :discontinued, default: false, null: false

DBマイグレートします。

rake db:migrate
Modelファイルに関連の追加

ER図に沿ってリレーションを追加します。
Modelの生成時に、referencesを指定しているので、belongs_toは既に記載されていると思いますが、全てのModelを記載しておきます。

# app/models/categorization.rb
class Categorization < ActiveRecord::Base
  belongs_to :category
  belongs_to :product
end

# app/models/category.rb
class Category < ActiveRecord::Base
  has_many :categorizastions
  has_many :products, through: :categorizations
end

# app/models/product.rb
class Product < ActiveRecord::Base
  belongs_to :publisher
  has_many :categorizations
  has_many :categories, through: :categorizations
end

# app/models/publisher.rb
class Publisher < ActiveRecord::Base
  has_many :products
end
初期データの作成と投入

今回はCategoryとPublisherの登録画面を作成しないので、初期データをrake db:seed経由で登録しまておきます。

# db/seeds.rb
literature = Category.create! name: "文学"
history = Category.create! name: "歴史"
business = Category.create! name: "ビジネス"
science = Category.create! name: "科学"
comic = Category.create! name: "コミック"

suzuki = Publisher.create! name: "鈴木書店"
tanaka = Publisher.create! name: "田中出版"
shugo = Publisher.create! name: "集合社"
otona = Publisher.create! name: "大人館"

入力したデータを投入します。

rake db:seed
フォームの修正(Productの新規/編集画面)

Scaffoldでは適切でない入力項目もあるので、修正します。
下記にソースコードを全て記載していますが、修正した箇所は次の通りです。
・ratingを「テキストフィールド」から「ラジオボタン」に変更
・publisher_idを「テキストフィールド」から「セレクトボックス」に変更
・categoryを選択できるチェックボックスを追加

# app/views/products/_form.html.erb
<%= form_for(@product) do |f| %>
  <% if @product.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2>

      <ul>
      <% @product.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :price %><br>
    <%= f.text_field :price %>
  </div>
  <div class="field">
    <%= f.label :released_on %><br>
    <%= f.date_select :released_on %>
  </div>
  <div class="field">
    <%= f.label :rating %><br />
    <%= f.radio_button :rating, 1 %> 1
    <%= f.radio_button :rating, 2 %> 2
    <%= f.radio_button :rating, 3 %> 3
    <%= f.radio_button :rating, 4 %> 4
    <%= f.radio_button :rating, 5 %> 5
  </div>
  <div class="field">
    <%= f.label :discontinued %><br>
    <%= f.check_box :discontinued %>
  </div>
  <div class="field">
    <%= f.label :publisher_id %><br />
    <%= f.collection_select :publisher_id, Publisher.all, :id, :name, include_blank: true %>
  </div>
  <div class="field">
    <% Category.all.each do |category| %>
      <%= check_box_tag "product[category_ids][]", category.id, @product.category_ids.include?(category.id), id: dom_id(category) %>
      <%= label_tag dom_id(category), category.name %><br />
    <% end %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

カテゴリを更新できるようにコントローラーのStrongParameters内でcategory_ids: []を追加します。

# app/controllers/products_controller.rb
  ...
    # Never trust parameters from the scary internet, only allow the white list through.
    def product_params
      params.require(:product).permit(:name, :price, :released_on, :rating, :discontinued, :publisher_id, category_ids: [])
    end
Productの登録確認

$ server sでローカルサーバを起動し、Productが登録できることを確認して下さい。
[登録画面]
f:id:nipe880324:20140730142443p:plain:w480

[詳細画面]
※登録できたことがわかると思います。Publisherの表示がおかしく、カテゴリも表示されていませんが、show.html.erbを修正すれば適切に表示されます。
f:id:nipe880324:20140730142445p:plain:w480


長かったですが、これで準備は終わりです。
今のform_for内はとても長いですよね。
これを、simple_formを使うことによりとてもすっきりさせることができます。


1. simple_formのインストール方法

simple_formをインストールしていきます。

Simple formのインストール

Gemfileに追加します。

# Gemfile
gem 'simple_form'


バンドルを実行します。

bundle install


simple_formをRailsプロジェクトにインストールします。

rails g simple_form:install
      create  config/initializers/simple_form.rb
       exist  config/locales
      create  config/locales/simple_form.en.yml
      create  lib/templates/erb/scaffold/_form.html.erb
simple_formでform_forを書き直す

書き換えた後のソースコードを全て記載しています。
書き換える考え方としては、
form_forsimple_form_forにする。
・Input要素は、<%= f.input :xxx %>にする。
・関連があるInput要素は、<%= f.association :yyy %>に変えて下さい。

変更前と比べるととてもすっきりしたコードになっていると思います。

<%= simple_form_for(@product) do |f| %>
  <%= f.input :name %>
  <%= f.input :price %>
  <%= f.input :released_on %>
  <%= f.input :rating %>
  <%= f.input :discontinued %>
  <%= f.association :publisher %>
  <%= f.association :categories %>
  <%= f.button :submit %>
<% end %>


では、スタイリングファイルを作成し、スタイリングも追加します。

# app/assets/stylesheets/forms.css.scss
.simple_form {
  label {
    margin: 2px 10px;
    &.radio, &.checkbox {
      float: none;
      margin: 0;
      width: auto;
      text-align: left;
    }
    &.checkbox {
      display: block;
    }
    &.radio { margin-right: 10px; }
  }
  div.input { margin-bottom: 10px;}
  input.radio_buttons, input.check_boxes {
    margin-right: 5px;
  }
  .alert-error {
    color: #D00;
    margin-bottom: 10px;
    font-weight: bold;
  }
  .hint, .error {
    clear: left;
    font-size: 12px;
    color: #D00;
    display: block;
  }
  .hint {
    color: #555;
    font-style: italic;
  }
}


では、サーバを起動し、画面を確認しましょう。
コードは短くなったのに、画面表示はほとんど同じですね。
f:id:nipe880324:20140730144535p:plain:w480


[補足説明]
simple_formはDBカラムの型からHTMLの要素を決めています。
例えば、
・String型なら、テキストフィールド
・Integerなど数値型なら、数値フィールド
・Date型なら、日付フィールド
・Boolean型なら、チェックボックス
などです。
公式ドキュメントに対応は記載されています。



2. simple_formのフィールドのカスタマイズ方法

先ほどの画面では、ratingをラジオボタンにしたい、カテゴリをチェックボックスにしたいなどいまいちな箇所があります。
実際に変更しながら、フィールドのカスタマイズ方法を説明しいきます。

Inputフィールドのタイプを指定

asオプションを使うことで、Inputフィールドのタイプを指定することができます。
ここでは、categoriesをチェックボックスで表示させます。

<%= f.association :categories, as: :check_boxes %>

使えるInputタイプについては、「利用可能なInputタイプのMapping列」を確認して下さい。

ラベルを設定する

labelオプションを使うことで、ラベルの文字を指定できます。

  <%= f.input :name, label: "名前" %>

ヒントを追加する

hintオプションを使うことで、ヒントの文字を追加できます。
ヒントとは、inputフィールドの下に表示される文字列のことで、ユーザが入力時の簡単な説明になります。

  <%= f.input :price, label: "値段", hint: "値段は円で入力して下さい" %>

範囲を指定する

collectionオプションで範囲オブジェクトか配列を引数に渡すと、その値の範囲のセレクトボックスになります。しかし、今回はラジオボタンで表示したいので、asオプションでラジオボタンを指定しています。

  <%= f.input :rating, collection: 1..5, as: :radio_buttons %>

ラベルとinput要素のhtml属性を指定する

lable_htmlinput_htmlオプションとhtml属性を指定することにより、属性をしていできます。
今回はテストのため、名前を赤色に、入力ボックスの角を丸めるようにstyle属性を指定しました。

  <%= f.input :name, label: "名前",
    label_html: { style: 'color:red;' }, input_html: { style: 'border-radius:5px;' } %>

その他やりたいことがありましたら、Simple form 公式ドキュメントを確認して下さい。

では、変更した画像を見てみましょう。
実際に、ラジオボタンやチェックボックスになっていることが分かると思います。
f:id:nipe880324:20140730145404p:plain:w480




3. simple_formのバリデーションエラーの表示方法

Modelクラスにvalidatesメソッドを追加することで、フォーム上にValidationのエラーメッセージが表示されます。

# app/models/product.rb
...
  validates :email, presence: true # email属性が空の場合Validationエラー
...

f:id:nipe880324:20140730145701p:plain:w480



simple formはラベルに自動で"*"マークをつけ、エラー文字も表示してくれます。
"*"マークを表示したくないや、他のマークにしたい場合、config/initializers/simple_form.rbに設定項目があるので変更できます。

また、日本語化をしたい場合は、config/locales/simple_form.en.ymlを真似て、日本語用ファイルを作成してください。

エラーメッセージをフォームの上部に表示させたい場合は、下記を追加して下さい。

# app/views/products/_form.html.erb
<%= simple_form_for(@product) do |f| %>
  <%= f.error_notification %>

  <%= f.input :name, label: "名前",
    label_html: { style: 'color:red;' }, input_html: { style: 'border-radius:5px;' } %>
...

f:id:nipe880324:20140730145836p:plain:w480


デザインがいまいちと思われた方は、simple_formTwitter Bootstrapは連携できるので、
RailsにTwitter Bootstrapの導入と簡易な使い方 - Rails Webook
を参照して見て下さい。

以上です。