How do I find where a Ruby method is called at runtime?
This specific method is causing application to break:
# needed becasuse of a rails bug
def to_s
"#{self.class.name.underscore}__#{object_id}"
end
with the following error:
ActionView::TemplateError (`@content_for_details_view_builder__2234321380' is not allowed as an instance variable name) in app/views/shared/_details_view.haml:
Basically here's the code execution that leads up to the above exception:
# controller
def new
@view_mode = :new
template = "new"
render template, :layout => false
end
# new.haml
- details_view @user do |b, f|
# build_helper
def details_view(model, options = {}, &block)
# instantiate OptionsWrapper passing a hash of key/value pairs, in this case
# an empty hash. OptionsWrapper contains a method_missing method so when we
# invoke mode on our instance of OptionsWrapper, since it does not contain
# instance method of mode, it invokes method_missing and we set a number of
# methods on fly.
options = OptionsWrapper.new(options)
options.mode ||= @view_mode
model_name = model.class.model_name.singular.dasherize
options.dom_id = "#{model_name}-details-view"
# we instantiate DetailsViewBuilder passing in a few arguments (e.g. new,
# ActionView, user) to the constructor method.
builder = DetailsViewBuilder.new(options.mode, self, model)
options.html ||= {}
options.html[:id] ||= nil
options.html[:class] = "x-#{options.mode} #{options.html[:class]}".strip
render :layout => "shared/details_view", :locals => {:builder => builder,
:model => model, :options => options}, &block
end
#details_view_builder
class DetailsViewBuilder
attr_accessor :mode, :template, :model, :form
def initialize(mode, template, model)
@mode, @template, @model = mode, template, model #new, ActionView, user
end
def to_s开发者_运维问答
# here's the method causing error!
"#{self.class.name.underscore}__#{object_id}"
end
end
# we then render the details_view passing in some locals as shown a
# few lines above:
.details-dialog{:id => options.dom_id }
case options.mode
- when :new
- form_for model, options.slice(:url, :html) do |form|
= yield builder, form
I'm 100 percent sure that the to_s method is causing the error. I am not sure though where it is being called that it is causing this error. Notice above it yields the builder variable. That builder variable contains the class that has this method. I need to find a better way to inspect elements so I can see the callstack transparently and that given a method, I know where it's called. Oddly enough, this method works on my local machine but causes error on production on server. Here's a tidbit of the backtrace:
ActionView::TemplateError
(`@content_for_details_view_builder__-626960428' is not allowed as an
instance variable name) in app/views/shared/_details_view.haml:
searchlogic (2.4.27) lib/searchlogic/rails_helpers.rb:75:in
`fields_for'
searchlogic (2.4.27) lib/searchlogic/rails_helpers.rb:64:in
`form_for'
app/helpers/builders_helper.rb:68:in `details_view'
/home/app-sptr/.rvm/rubies/ruby-1.8.7-p334/lib/ruby/1.8/benchmark.rb:308:in
`realtime'
app/other/restful_component.rb:488:in `new_html'
app/other/restful_component.rb:73:in `new'
app/other/restful_component.rb:72:in `new'
/home/app-sptr/.rvm/rubies/ruby-1.8.7-p334/lib/ruby/1.8/benchmark.rb:308:in
`realtime'
rack (1.1.2) lib/rack/head.rb:9:in `call'
rack (1.1.2) lib/rack/methodoverride.rb:24:in `call'
rack (1.1.2) lib/rack/lock.rb:11:in `call'
rack (1.1.2) lib/rack/lock.rb:11:in `synchronize'
rack (1.1.2) lib/rack/lock.rb:11:in `call'
I also asked question here but wasn't satisfied with answer: http://www.ruby-forum.com/topic/1641800#new
The suggestion to use caller()
is fine for figuring out the stack trace from a certain method. However, I'll also share (what I think is) the solution to your problem.
The to_s
method returns a string that is used to set an instance variable dynamically. The DetailsViewBuilder
's object_id
is part of the variable name. The object ID represents the object's location in memory. Contrary to intuition, the object_id
of an object may sometimes be negative integer. Whether or not this happens may also be platform-dependant. This explains why you are only seeing this error in production. The negative object ID causes the instance variable name to become something like @content_for_details_view_builder__-626960428
, which contains a dash. Dashes are not allowed as part of identifiers in Ruby. Hence the error.
It seems that you are using an instance of DetailsViewBuilder
as argument to content_for
somewhere, is that right? Or it might happen indirectly, in some library you are using.
In any case, if this is the correct behaviour, and all you want is to avoid the instance variable name error, then the fix is simple. Change object_id
to object_id.abs
to guarantee that the instance variable never contains a dash:
def to_s
"#{self.class.name.underscore}__#{object_id.abs}"
end
The suggestion to use caller()
in the link you posted, is actually very good.
The docs have a decent example, but what you seem to have missed was this part:
The optional start parameter determines the number of initial stack entries to omit from the result.
Basically caller()
is the guts of a stack trace you'd get from an unhandled exception, only you can say how many lines it should display.
精彩评论