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
.
super
without an argument list resends the current message, starting message dispatch at the superclass, passing along the exact same arguments that were passed to the current method.
In your case, the arguments being passed to the current method are (field, *args)
, so super
without an argument list would pass along (field, *args)
, but unfortunately super
without an argument list doesn't work inside define_method
, so you have to pass along the arguments explicitly using super(field, *args)
.
This will call the method with the name given by name
in the superclass passing along (field, *args)
just as if you had called super
without an argument list.
Looking at the code, I'm not sure how to change it.
Simple: since super
without an argument list implicitly passes along the arguments exactly as they were passed, the explicit equivalent to that is to just copy&paste the parameter list from the method definition into the argument list of super
. In this case the method definition is define_method(name) do |field, *args| … end
, so the parameter list is (field, *args)
, and you just copy that into the argument list for super
: super(field, *args)
.
It may or may not be possible (and desirable) to pass different arguments, depending on how the method is designed, and how you process the arguments, but this always works. (Or, put it another way: if this doesn't work, then the original code using just super
also doesn't work. The only exception is when you want to pass along a block and you don't capture that block into a Proc
, you will have to modify the method signature to add an &blk
parameter.)