Rails の form_for でエラー出たときに、エラーはちゃんとエラーが出た項目の下に出したい。
エラーは各フォームの近くに表示したいけど form_for
の中身の記述を増やしたくはないのでカスタムフォームビルダーを作成する。
Rails Guide でやり方読む
どうやってカスタムフォームビルダーをつくる
FormBuilder を継承してタグを書きだす部分を override する。
class LabellingFormBuilder < ActionView::Helpers::FormBuilder def text_field(attribute, options={}) label(attribute) + super end end
どうやってカスタムフォームビルダーをつかう
ブロックはそのままで form_for
で builder
を指定するだけ。
使用前
<%= form_for @model do |f| %> <%= f.text_field :name %> <% end %>
使用後
<%= form_for @model, builder: LabellingFormBuilder do |f| %> <%= f.text_field :name %> <% end %>
中身に手を加える必要がない(重要っぽい)。
素の FormBuilder はどんな感じ
つかえる変数
@object
でモデルのインスタンスにアクセスできることがわかる。
# 抜粋 def initialize(object_name, object, template, options) @nested_child_index = {} @object_name, @object, @template, @options = object_name, object, template, options @default_options = @options ? @options.slice(:index, :namespace) : {} if @object_name.to_s.match(/\[\]$/) if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param) @auto_index = object.to_param else raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" end end @multipart = nil @index = options[:index] || options[:child_index] end
各書きだしメソッド
タグを書きだすメソッドは Array から eval されている。今回特に使えそうな項目はないので単純に super
のままで OK。
# 抜粋 class_attribute :field_helpers self.field_helpers = [:fields_for, :label, :text_field, :password_field, :hidden_field, :file_field, :text_area, :check_box, :radio_button, :color_field, :search_field, :telephone_field, :phone_field, :date_field, :time_field, :datetime_field, :datetime_local_field, :month_field, :week_field, :url_field, :email_field, :number_field, :range_field] (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{selector}(method, options = {}) # def text_field(method, options = {}) @template.send( # @template.send( #{selector.inspect}, # "text_field", @object_name, # @object_name, method, # method, objectify_options(options)) # objectify_options(options)) end # end RUBY_EVAL end
カスタマイズする
まずためす
class WithErrorFormBuilder < ActionView::Helpers::FormBuilder def pick_errors(attribute) return nil if @object.nil? || (messages = @object.errors.messages[attribute]).nil? lis = messages.collect do |message| %{<li>#{message}</li>} end.join %{<ul class="errors">#{lis}</ul>}.html_safe end def text_field(attribute, options={}) return super if options[:no_errors] super + pick_errors(attribute) end end
<%= form_for @model, builder: WithErrorFormBuilder do |f| %> <%= f.text_field :name %> <% end %>
いい感じに書きだされた。
素の FormBuilder にならって一気に定義する感じにする。
class WithErrorFormBuilder < ActionView::Helpers::FormBuilder def pick_error(attribute) return nil if @object.nil? || (messages = @object.errors.messages[attribute]).nil? lis = messages.collect do |message| %{<li>#{message}</li>} end.join %{<ul class="errors">#{lis}</ul>}.html_safe end (field_helpers - [:label, :check_box, :radio_button, :fields_for, :hidden_field, :file_field]).each do |selector| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{selector}(attribute, options = {}) return super if options[:no_errors] super + pick_error(attribute) end RUBY_EVAL end end
いけました。
次
除外されてる check_box
、 radio_button
は select
と同じ感じに一発で書けたら素敵なのでそうしたいですね。