Simple Form
Railsアプリケーション向けの強力で柔軟なフォームビルダー
概要
Simple Formは、Ruby on Railsアプリケーション向けの強力なフォームビルダーです。フォーム作成を簡素化し、バリデーションの統合、CSSフレームワークとの連携、カスタマイズ可能なコンポーネントなど、豊富な機能を提供します。
主な特徴
- 簡潔な構文: 少ないコードで複雑なフォームを作成
- 自動バリデーション統合: Railsモデルのバリデーションを自動的に反映
- CSSフレームワーク対応: Bootstrap、Foundation、Tailwind CSSなどと簡単に統合
- 国際化対応: 多言語対応フォームの簡単な実装
- カスタマイズ可能: 独自のフォームコンポーネントを作成可能
- アクセシビリティ: WAI-ARIA属性の自動生成
インストール
Gemfileへの追加
gem 'simple_form'
バンドルインストール
bundle install
初期設定
# 基本的なインストール
rails generate simple_form:install
# Bootstrapを使用する場合
rails generate simple_form:install --bootstrap
# Foundation使用する場合
rails generate simple_form:install --foundation
基本的な使用方法
シンプルなフォームの作成
<%= simple_form_for @user do |f| %>
<%= f.input :name %>
<%= f.input :email %>
<%= f.input :password %>
<%= f.input :birthday, as: :date %>
<%= f.button :submit %>
<% end %>
入力タイプの指定
<%= simple_form_for @article do |f| %>
<%= f.input :title %>
<%= f.input :content, as: :text %>
<%= f.input :published, as: :boolean %>
<%= f.input :category, collection: ['News', 'Blog', 'Tutorial'] %>
<%= f.input :tags, as: :check_boxes, collection: Tag.all %>
<% end %>
バリデーション統合
モデルバリデーションの自動反映
# app/models/user.rb
class User < ApplicationRecord
validates :name, presence: true, length: { minimum: 2, maximum: 50 }
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :age, numericality: { greater_than_or_equal_to: 18 }
end
<!-- バリデーションエラーが自動的に表示される -->
<%= simple_form_for @user do |f| %>
<%= f.input :name, hint: '2文字以上50文字以下' %>
<%= f.input :email %>
<%= f.input :age, hint: '18歳以上である必要があります' %>
<%= f.button :submit %>
<% end %>
カスタムバリデーションメッセージ
<%= f.input :username,
error: 'ユーザー名は既に使用されています',
hint: '英数字とアンダースコアのみ使用可能' %>
フォームビルダーとヘルパー
利用可能な入力タイプ
<%= simple_form_for @product do |f| %>
<!-- テキスト入力 -->
<%= f.input :name %>
<!-- テキストエリア -->
<%= f.input :description, as: :text %>
<!-- 数値入力 -->
<%= f.input :price, as: :numeric %>
<!-- セレクトボックス -->
<%= f.input :category, collection: Category.all %>
<!-- ラジオボタン -->
<%= f.input :status, as: :radio_buttons,
collection: ['draft', 'published', 'archived'] %>
<!-- チェックボックス(複数選択) -->
<%= f.input :features, as: :check_boxes,
collection: Feature.all %>
<!-- 日付選択 -->
<%= f.input :release_date, as: :date %>
<!-- ファイルアップロード -->
<%= f.input :image, as: :file %>
<!-- カラーピッカー -->
<%= f.input :color, as: :color %>
<!-- 範囲入力 -->
<%= f.input :rating, as: :range, input_html: { min: 1, max: 5 } %>
<% end %>
アソシエーションの処理
<%= simple_form_for @post do |f| %>
<%= f.input :title %>
<!-- belongs_to アソシエーション -->
<%= f.association :author %>
<!-- has_many アソシエーション -->
<%= f.association :categories, as: :check_boxes %>
<!-- カスタムラベルとスコープ -->
<%= f.association :tags,
label_method: :name,
value_method: :id,
include_blank: false,
collection: Tag.active %>
<% end %>
カスタムフォームコンポーネント
カスタム入力の作成
# app/inputs/currency_input.rb
class CurrencyInput < SimpleForm::Inputs::Base
def input(wrapper_options)
merged_input_options = merge_wrapper_options(input_html_options, wrapper_options)
template.content_tag(:div, class: 'input-group') do
template.content_tag(:span, '¥', class: 'input-group-text') +
@builder.text_field(attribute_name, merged_input_options)
end
end
end
<!-- カスタム入力の使用 -->
<%= f.input :price, as: :currency %>
カスタムラッパーの定義
# config/initializers/simple_form_custom.rb
SimpleForm.setup do |config|
config.wrappers :custom_wrapper, tag: 'div', class: 'form-group' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength
b.optional :pattern
b.optional :min_max
b.optional :readonly
b.use :label, class: 'form-label'
b.use :input, class: 'form-control', error_class: 'is-invalid'
b.use :error, wrap_with: { tag: 'div', class: 'invalid-feedback' }
b.use :hint, wrap_with: { tag: 'small', class: 'form-text text-muted' }
end
end
Bootstrap統合
Bootstrap 5の設定
# config/initializers/simple_form_bootstrap.rb
SimpleForm.setup do |config|
# Bootstrap 5の水平フォーム設定
config.wrappers :horizontal_form, class: 'row mb-3' do |b|
b.use :html5
b.use :placeholder
b.optional :maxlength
b.optional :minlength
b.optional :pattern
b.optional :min_max
b.optional :readonly
b.use :label, class: 'col-sm-3 col-form-label'
b.wrapper :grid_wrapper, class: 'col-sm-9' do |ba|
ba.use :input, class: 'form-control', error_class: 'is-invalid'
ba.use :full_error, wrap_with: { class: 'invalid-feedback' }
ba.use :hint, wrap_with: { class: 'form-text' }
end
end
end
Bootstrap形式のフォーム
<%= simple_form_for @user, wrapper: :horizontal_form do |f| %>
<div class="card">
<div class="card-body">
<%= f.input :name %>
<%= f.input :email %>
<%= f.input :bio, as: :text, input_html: { rows: 4 } %>
<div class="form-check form-switch">
<%= f.input :newsletter, as: :boolean,
wrapper: :custom,
input_html: { class: 'form-check-input' },
label_html: { class: 'form-check-label' } %>
</div>
</div>
<div class="card-footer">
<%= f.button :submit, class: 'btn btn-primary' %>
<%= link_to 'キャンセル', users_path, class: 'btn btn-secondary' %>
</div>
</div>
<% end %>
国際化(i18n)
ロケールファイルの設定
# config/locales/simple_form.ja.yml
ja:
simple_form:
"yes": 'はい'
"no": 'いいえ'
required:
text: '必須'
mark: '*'
error_notification:
default_message: "エラーを確認してください:"
labels:
user:
name: '名前'
email: 'メールアドレス'
password: 'パスワード'
remember_me: 'ログイン状態を保持'
hints:
user:
name: 'フルネームを入力してください'
email: '有効なメールアドレスを入力してください'
password: '8文字以上で入力してください'
placeholders:
user:
name: '例: 山田太郎'
email: '例: [email protected]'
動的な国際化
<%= simple_form_for @user do |f| %>
<%= f.input :name, label: t('activerecord.attributes.user.name') %>
<%= f.input :role,
collection: User.roles.keys.map { |role| [t("user.roles.#{role}"), role] } %>
<% end %>
実践的な例
ユーザー登録フォーム
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2>新規登録</h2>
<%= simple_form_for @user, url: registration_path do |f| %>
<%= f.error_notification %>
<%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %>
<div class="form-inputs">
<%= f.input :name, required: true, autofocus: true %>
<%= f.input :email, required: true %>
<%= f.input :password, required: true, hint: ("最低#{@minimum_password_length}文字" if @minimum_password_length) %>
<%= f.input :password_confirmation, required: true %>
<%= f.input :terms_of_service, as: :boolean,
label: '利用規約に同意する',
error: '利用規約への同意が必要です' %>
</div>
<div class="form-actions">
<%= f.button :submit, '登録する', class: 'btn btn-primary btn-block' %>
</div>
<% end %>
<hr>
<p class="text-center">
既にアカウントをお持ちですか?
<%= link_to 'ログイン', login_path %>
</p>
</div>
</div>
</div>
検索フォーム
<%= simple_form_for :search, url: search_path, method: :get do |f| %>
<div class="row g-3">
<div class="col-md-4">
<%= f.input :keyword, label: false,
placeholder: 'キーワードを入力',
input_html: { value: params.dig(:search, :keyword) } %>
</div>
<div class="col-md-3">
<%= f.input :category,
collection: Category.all,
include_blank: '全てのカテゴリ',
label: false,
selected: params.dig(:search, :category) %>
</div>
<div class="col-md-3">
<%= f.input :sort_by,
collection: [['新着順', 'created_at'], ['人気順', 'popularity'], ['価格順', 'price']],
label: false,
selected: params.dig(:search, :sort_by) || 'created_at' %>
</div>
<div class="col-md-2">
<%= f.button :submit, '検索', class: 'btn btn-primary w-100' %>
</div>
</div>
<% end %>
Ajax対応フォーム
<%= simple_form_for @comment, remote: true, html: { data: { type: 'json' } } do |f| %>
<div id="comment-errors"></div>
<%= f.input :content, as: :text, label: false,
placeholder: 'コメントを入力...',
input_html: { rows: 3 } %>
<div class="d-flex justify-content-between align-items-center">
<div class="form-check">
<%= f.input :anonymous, as: :boolean,
label: '匿名で投稿',
wrapper: false,
label_html: { class: 'form-check-label' },
input_html: { class: 'form-check-input' } %>
</div>
<%= f.button :submit, '投稿',
class: 'btn btn-primary',
data: { disable_with: '投稿中...' } %>
</div>
<% end %>
<script>
document.addEventListener('turbo:load', () => {
const form = document.querySelector('#new_comment');
form.addEventListener('ajax:success', (event) => {
const [data, status, xhr] = event.detail;
// 成功時の処理
form.reset();
});
form.addEventListener('ajax:error', (event) => {
const [data, status, xhr] = event.detail;
// エラー処理
document.getElementById('comment-errors').innerHTML =
'<div class="alert alert-danger">エラーが発生しました</div>';
});
});
</script>
高度な機能
条件付き表示
<%= simple_form_for @subscription do |f| %>
<%= f.input :plan, as: :radio_buttons,
collection: ['basic', 'premium', 'enterprise'] %>
<div id="billing-info" style="display: none;">
<%= f.input :card_number %>
<%= f.input :expiry_date %>
<%= f.input :cvv %>
</div>
<script>
document.querySelectorAll('input[name="subscription[plan]"]').forEach(radio => {
radio.addEventListener('change', (e) => {
const billingInfo = document.getElementById('billing-info');
billingInfo.style.display = e.target.value !== 'basic' ? 'block' : 'none';
});
});
</script>
<% end %>
ネストされたフォーム
<%= simple_form_for @project do |f| %>
<%= f.input :name %>
<%= f.input :description %>
<h3>タスク</h3>
<div id="tasks">
<%= f.simple_fields_for :tasks do |task| %>
<%= render 'task_fields', f: task %>
<% end %>
</div>
<div class="links">
<%= link_to_add_association 'タスクを追加', f, :tasks,
class: 'btn btn-sm btn-secondary' %>
</div>
<%= f.button :submit %>
<% end %>
<!-- _task_fields.html.erb -->
<div class="nested-fields">
<%= f.input :title %>
<%= f.input :completed, as: :boolean %>
<%= link_to_remove_association "削除", f, class: 'btn btn-sm btn-danger' %>
</div>
パフォーマンス最適化
フォームオブジェクトパターン
# app/forms/user_registration_form.rb
class UserRegistrationForm
include ActiveModel::Model
attr_accessor :name, :email, :password, :password_confirmation, :terms_of_service
validates :name, presence: true
validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :password, presence: true, confirmation: true, length: { minimum: 8 }
validates :terms_of_service, acceptance: true
def save
return false unless valid?
user = User.new(user_params)
if user.save
UserMailer.welcome(user).deliver_later
true
else
errors.merge!(user.errors)
false
end
end
private
def user_params
{ name: name, email: email, password: password }
end
end
<%= simple_form_for @registration_form, url: registrations_path do |f| %>
<%= f.input :name %>
<%= f.input :email %>
<%= f.input :password %>
<%= f.input :password_confirmation %>
<%= f.input :terms_of_service, as: :boolean %>
<%= f.button :submit, '登録' %>
<% end %>
キャッシュの活用
<% cache [@user, 'edit_form'] do %>
<%= simple_form_for @user do |f| %>
<!-- フォーム内容 -->
<% end %>
<% end %>
トラブルシューティング
よくある問題と解決策
-
バリデーションエラーが表示されない
# コントローラーでエラーを保持 def create @user = User.new(user_params) if @user.save redirect_to @user else render :new # renderを使用してエラーを保持 end end -
カスタムラッパーが適用されない
<!-- wrapper オプションを明示的に指定 --> <%= f.input :name, wrapper: :custom_wrapper %> -
日本語化が反映されない
# config/application.rb config.i18n.default_locale = :ja config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.yml')]
まとめ
Simple Formは、Railsアプリケーションでのフォーム作成を大幅に簡素化する強力なツールです。バリデーションの自動統合、多様な入力タイプ、CSSフレームワークとの連携など、開発効率を向上させる多くの機能を提供します。適切に設定・カスタマイズすることで、保守性の高い美しいフォームを効率的に実装できます。