0

I'm running a legacy Rails 2.3 (yes, I know) application on a new VM. It's running Ruby v2.3.1. I've made all of the recommended changes to get this old app running on this version. Everything runs great, and much faster, except for this code. This code is from the old Advanced Recipes for Rails book and is error_handling_form_builder.rb.

First, here's the form declaration:

<% form_for(:review, :builder => ErrorHandlingFormBuilder) do |f| %>
    <%= f.collection_select :system_id, @systems, :id, :name, { :include_blank => "Select a Standard"}, { :onchange => remote_function(:url => { :action => :makes }, :submit => :text, :method => 'post') } %>
    <div id="categoriesdiv">
    </div>
<% end %>

This code works fine if I remove the builder param. Here's the code from ErrorHandlingFormBuilder:

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|
    options = args.detect {|argument| argument.is_a?(Hash)} || {}
    build_shell(field, options) do
      super # This call fails
    end
  end
end

The call to super above fails with following error:

implicit argument passing of super from method defined by define_method()
is not supported. Specify all arguments explicitly.

Looking at the code, I'm not sure how to change it.

I know I have to call super with the explicit arguments, but I tried this:

super(field, options)

and I get:

wrong number of arguments (given 2, expected 4..6)

I also tried:

build_shell(field, options) do
  super(*args)
end

but I get:

undefined method `map' for :id:Symbol
Did you mean?  tap

It seems like it's looking for the collection attribute 2nd in the list of *args. But it's first. If i pass field as the first argument though, I get a Stack Level Too Deep.

4

1 回答 1

4

super如果没有参数列表,则重新发送当前消息,在超类开始消息分发,传递与传递给当前方法的完全相同的参数。

在您的情况下,传递给当前方法的参数是(field, *args),因此super没有参数列表会传递(field, *args),但不幸super的是,没有参数列表在内部不起作用define_method,因此您必须使用显式传递参数super(field, *args)

这将使用name传递的超类中给出的名称调用方法,(field, *args)就像您在super没有参数列表的情况下调用一样。

查看代码,我不确定如何更改它。

简单:因为super没有参数列表会隐式传递参数,与传递的参数完全相同,显式等价于将参数列表从方法定义复制并粘贴到super. 在这种情况下,方法定义是define_method(name) do |field, *args| … end,所以参数列表是(field, *args),您只需将其复制到参数列表中supersuper(field, *args)

根据方法的设计方式以及处理参数的方式,传递不同的参数可能会也可能不会(并且是可取的),但这总是有效的。(或者,换一种说法:如果这不起作用,那么使用 just 的原始代码super也不起作用。唯一的例外是当您想要传递一个块并且您没有将该块捕获到 aProc中时,您将不得不修改方法签名以添加&blk参数。)

于 2016-07-20T22:58:01.387 回答