使用super的define_method会导致无限递归

时间:2016-07-22 14:54:32

标签: ruby-on-rails ruby

我有一个表单构建器子类:

class ErrorHandlingFormBuilder < ActionView::Helpers::FormBuilder

使用以下代码块:

helpers.each do |name|
  # We don't want to have a label for a hidden field

  # ERROR: The call to super below is actually calling itself and causing infinite recursion.
  #        How can I get it to call 
  next if name=="hidden_field"
  define_method name do |field, *args|
    options = args.detect {|argument| argument.is_a?(Hash)} || {}
    build_shell(field, options) do
      super(field,*args)
    end
  end
end

super调用正在调用它封装代码块正在定义的方法!这导致无限递归和Stack Level Too Deep

我需要的是它在表单构建器实例本身中调用name变量定义的方法。

我只是不知道如何引用该实例。即使使用self.send而不是super,仍会导致递归。

以下是完整的代码,以及我用于跟踪堆栈的一些日志记录:

class ErrorHandlingFormBuilder < ActionView::Helpers::FormBuilder

  helpers = field_helpers +
            %w(date_select datetime_select calendar_date_select time_select collection_select) +
            %w(collection_select select country_select time_zone_select) -
            %w(label fields_for)

  helpers.each do |name|
    # We don't want to have a label for a hidden field
    next if name=="hidden_field"

    define_method name do |field, *args|
      ErrorPrinter.print "name: #{name}"
      ErrorPrinter.print "field: #{field}"
      options = args.detect {|argument| argument.is_a?(Hash)} || {}
      build_shell(field, options) do
        super(field,*args)
      end
    end
  end

  def build_shell(field, options)

    # Capitalize the string, unless it's already been hardcoded.
    options[:label] = field.to_s.humanize.gsub(/^[a-z]|\s+[a-z]/) { |a| a.upcase } unless options[:label]

    options[:label].gsub!(/\w+/) { |word| CAPITALS.include?(word.upcase) ? word.upcase : word } 
    options[:label] += ":" unless options[:label].last==":"

    @template.capture do
      ErrorPrinter.print "Before"
      locals = {:element => yield, :label => label(field, options[:label] )}
      ErrorPrinter.print "After"

      if has_errors_on?(field)
        locals.merge!(:error => error_message(field, options))
        @template.render :partial => 'forms/field_with_errors', :locals => locals
      else
        @template.render :partial => 'forms/field', :locals => locals
      end
    end
  end

  def error_message(field, options)
    if has_errors_on?(field)
      errors = object.errors.on(field)
      errors.is_a?(Array) ? errors.to_sentence : errors
    else
      ''
    end
  end

  def has_errors_on?(field)
    !(object.nil? || object.errors.on(field).blank?)
  end

end

递归问题在于build_shell

这一行
  locals = {:element => yield, :label => label(field, options[:label] )}

由日志证明:

XXXXXXXXX
name: collection_select
XXXXXXXXX


XXXXXXXXX
field: system_id
XXXXXXXXX


XXXXXXXXX
Before
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb collection_select start
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb object: :review
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb method: :system_id
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb collection: #<Set: {#<System id: 1, name: "Catalog"}>
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb value_method: :id
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb text_method: :name
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb options: {:include_blank=>"Select a Standard", :label=>"System:", :object=>nil}
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb html_options: {:onchange=>"new Ajax.Request('/review/makes', {asynchronous:true, evalScripts:true, method:'post', parameters:Form.serialize('text')})"}
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb collection_select end
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb to_collection_select_tag start
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb collection: #<Set: {#<System id: 1, name: "Catalog"}>
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb value_method: id
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb text_method: name
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb options: {:include_blank=>"Select a Standard", :label=>"System:"}
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb html_options: {:onchange=>"new Ajax.Request('/review/makes', {asynchronous:true, evalScripts:true, method:'post', parameters:Form.serialize('text')})"}
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb to_collection_select_tag end
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb options_from_collection_for_select start
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb collection: #<Set: {#<System id: 1, name: "Catalog"}>
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb value_method: :id
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb text_method: :name
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb selected: {:selected=>nil, :disabled=>nil}
XXXXXXXXX


XXXXXXXXX
actionpack form_options_helper.rb options_from_collection_for_select end
XXXXXXXXX


XXXXXXXXX
End options_from_collection_for_select
XXXXXXXXX


XXXXXXXXX
name: label
XXXXXXXXX


XXXXXXXXX
field: system_id
XXXXXXXXX


XXXXXXXXX
Before
XXXXXXXXX


XXXXXXXXX
name: label
XXXXXXXXX


XXXXXXXXX
field: system_id
XXXXXXXXX


XXXXXXXXX
Before
XXXXXXXXX


XXXXXXXXX
name: label
XXXXXXXXX


XXXXXXXXX
field: system_id
XXXXXXXXX


XXXXXXXXX
Before
XXXXXXXXX


XXXXXXXXX
name: label
XXXXXXXXX


XXXXXXXXX
field: system_id
XXXXXXXXX


XXXXXXXXX
Before
XXXXXXXXX


XXXXXXXXX
name: label
XXXXXXXXX


XXXXXXXXX
field: system_id
XXXXXXXXX


XXXXXXXXX
Before
XXXXXXXXX


XXXXXXXXX
name: label
XXXXXXXXX


XXXXXXXXX
field: system_id
XXXXXXXXX


XXXXXXXXX
Before
XXXXXXXXX


XXXXXXXXX
name: label
XXXXXXXXX


XXXXXXXXX
field: system_id
XXXXXXXXX


XXXXXXXXX
Before
XXXXXXXXX

1 个答案:

答案 0 :(得分:0)

3天后解决了。递归发生在label的调用上。如果你看一下代码:

helpers = field_helpers +
          %w(date_select datetime_select calendar_date_select time_select collection_select) +
          %w(collection_select select country_select time_zone_select) -
          %w(label fields_for)

那不行。 labelfields_for未从阵列中删除。它们作为符号出现在field_helpers中。但即使将它们标记为符号也不起作用。

这段代码在Ruby 1.8.7中运行良好,但在其他任何方面都无法工作。显然,Ruby的现代版本有一些变化,可以防止像上面那样的声明。我刚刚决定跳过labelfields_for,就像我跳过hidden_field一样,现在代码正常运行。