Weird binding error

Below is code that represents the code I am working on. The line

that reads temp.wrap_with_combined is the problem. I am not sure if

this is a bug in my brain, Rails, or Ruby. And, I’m stumped as to

how to continue to debug it.

At the point temp.wrap_with_combined is called, temp is a

Cached::Queue according to the dump_me output. The dump_me code and

its output are included here. According to the debugging output,

temp is a Cached::Queue but instead of calling wrap_with_combined in

Cached::Queue or Cached::Base, it calls wrap_with_combined in

Object. I can see this from the debug output.

I’ve tried moving the def of wrap_with_combined in Cached::Base so

it gets defined when the subclass is defined. I’ve tried many

things but it always ends up calling Object#wrap.

Another clue is, I replace the temp.wrap call with this:

dump_me(temp)

m = temp.class.instance_method(:wrap_with_combined)

m.bind(temp).call

But I get this error:

ActionView::TemplateError (bind argument must be an instance of

Cached::Queue) on line #6 of retain/favorite_queues/new.html.erb:

So, while the debug says I have a Cached::Queue, the internals of

Ruby say that I do not.

I’ve tried to make a simple testcase to show this but I have not

been able to. If ActiveRecord::Base is replaced with a simple base,

the problem goes away. Or if the Cached::Queue is created directly,

the problem goes away.

Any ideas?

class Object
def wrap_with_combined
logger.debug “Object wrap returns self”
self
end
end

class Cached::Base < ActiveRecord::Base
def wrap_with_combined
logger.debug “Cached::Base wrap returns wrapped object”
wrap_object(self)
end

def self.inherited(subclass)
super(subclass)
subclass.class_eval {
def wrap_with_combined
logger.debug “Alternate place to put wrap.”
logger.debug “Cached::Base wrap returns wrapped object”
wrap_object(self)
end
}
end
end

class Cached::Queue < Cached::Base

end

class WrappedBase
def self.inherited(subclass)
super(subclass)
subclass.class_eval {
# Define getter methods for each association
db_associations.each do |name|
eval(“def #{name}
temp = @cached.#{name}
unless temp && cache_valid
call_load
temp = @cached.#{name}
end
dump_me(temp)
# Problem occurs here wrap calls Object#wrap instead
# of Cached::Base#wrap or Cached::Queue#wrap
temp.wrap_with_combined
end”, nil, FILE, LINE)
end

  def dump_me(obj)
    klass = obj.class
    return if klass.nil?
    logger.debug("DMP: 1 class=#{klass} name=#{klass.name}

methods=#{klass.instance_methods(false).inspect}")
klass = klass.superclass
return if klass.nil?
logger.debug(“DMP: 2 class=#{klass} name=#{klass.name}
methods=#{klass.instance_methods(false).inspect}”)
klass = klass.superclass
return if klass.nil?
logger.debug(“DMP: 3 class=#{klass} name=#{klass.name}
methods=#{klass.instance_methods(false).inspect}”)
klass = klass.superclass
return if klass.nil?
logger.debug(“DMP: 4 class=#{klass} name=#{klass.name}
methods=#{klass.instance_methods(false).inspect}”)
klass = klass.superclass
return if klass.nil?
logger.debug(“DMP: 5 class=#{klass} name=#{klass.name}
methods=#{klass.instance_methods(false).inspect}”)
end
}
end
end

class WrappedQueue < WrappedBase

end

DMP: 1 class=Cached::Queue name=Cached::Queue

methods=[“wrap_with_combined”, “favorite_queues”, “favorite_queue_ids”,
“favorite_queues=”, “calls”, “call_ids”, “favorite_queue_ids=”,
“validate_associated_records_for_favorite_queues”, “calls=”,
“call_ids=”, “validate_associated_records_for_calls”]

DMP: 2 class=Cached::Base name=Cached::Base methods=[“to_combined”]

DMP: 3 class=ActiveRecord::Base name=ActiveRecord::Base

methods=[“to_param”, “increment”, “readonly!”, “allow_concurrency”,
“destroy”, “default_timezone”, “record_timestamps”, “quoted_id”,
“reload”, “save_without_transactions”, “save!”, “update_attributes”,
“valid?”, “update_attributes!”, “verification_timeout”,
“attributes_before_type_cast”, “colorize_logging”, “new_record?”,
“logger”, “id_before_type_cast”, “save_without_transactions!”, “eql?”,
“toggle!”, “pluralize_table_names”, “valid_without_callbacks?”, “id”,
“[]”, “attribute_present?”, “inspect”, “column_for_attribute”, “[]=”,
“save_without_validation”, “decrement!”, “update_attribute”, “frozen?”,
“id=”, “hash”, “update_attribute_without_validation_skipping”,
“table_name_suffix”, “connection”, “configurations”,
“save_without_validation!”, “toggle”, “becomes”, “attribute_names”,
“readonly?”, “clone”, “increment!”, “attribute_for_inspect”,
“attribute_types_cached_by_default”, “table_name_prefix”, “attributes”,
“freeze”, “save”, “schema_format”, “decrement”,
“destroy_without_callbacks”, “==”, “has_attribute?”, “attributes=”,
“primary_key_prefix_type”, “lock_optimistically”,
“destroy_without_transactions”]

DMP: 4 class=Object name=Object methods=[“subclasses_of”,

“require_or_load”, “require”, “to_yaml_style”, “wrap_with_combined”,
“with_options”, “require_association”, “copy_instance_variables_from”,
“to_yaml_properties”, “duplicable?”, “instance_exec”, “load”,
“unloadable”, “taguri”, “to_json”, “blank?”, “require_dependency”,
“to_query”, “instance_values”, “dclone”, “remove_subclasses_of”,
“extend_with_included_modules_from”, “taguri=”, “to_yaml”, “`”,
“to_param”, “returning”, “extended_by”, “unwrap_to_cached”, “send!”,
“acts_like?”]

On Jan 31, 2008, at 10:33 AM, Perry S. wrote:

           # Problem occurs here  wrap calls Object#wrap instead
           # of Cached::Base#wrap or Cached::Queue#wrap
           temp.wrap_with_combined

there really isn’t enough code there to say, but i’m guessing you have
a ‘has_many’ association and are calling ‘wrap_with_combined’ on a
Array, thus defining it on Object intercepts this call.

perhaps an explanation of what you are trying to do?

regards.

a @ http://codeforpeople.com/

ara howard wrote:

On Jan 31, 2008, at 10:33 AM, Perry S. wrote:

           # Problem occurs here  wrap calls Object#wrap instead
           # of Cached::Base#wrap or Cached::Queue#wrap
           temp.wrap_with_combined

there really isn’t enough code there to say, but i’m guessing you have
a ‘has_many’ association and are calling ‘wrap_with_combined’ on a
Array, thus defining it on Object intercepts this call.

perhaps an explanation of what you are trying to do?

regards.

a @ http://codeforpeople.com/

Thanks Howard.

I found a way to dump out ancestors and I found a thread talking about
association proxies. In that thread, it tells to do (from memory) (
class << temp; self; end).ancestors

Doing this produces a list of associations and proxies, etc (as you
predicted). And Cached::Queue is not in the list.

I added wrap and unwrap methods to a couple of the association classes
and that solved my problem. I am not yet to what I think is a general
solution but I’m at least understanding what is happening at little
more.

What am I doing? I have a legacy system that is remote called Retain.
For each concept, like a PMR, I have three classes. Retain::PMR,
Cached::PMR, and Combined::PMR. Retain::PMR goes to retain and gets the
data. Cached::PMR is a subclass of ActiveRecord::Base and uses all the
normal ActiveRecord features. Combined::PMR is the magic that glues
them together. For any request, I look to see if I have it in
Cached::PMR. I use time stamps to expire it, etc. If it is there and
current, I just present that. If not, I go to Retain::PMR to get the
data from Retain and save it in Cached::PMR. All the usual rails code
(the controllers and views) work with Combined objects (or thats the
idea).

Most of the magic is done with method_method and respond_to? so when
pmr.owner comes, it goes to a Combined instance’s method_missing which
(if the cache is deemed valid) is forwarded on to the Cached
(ActiveRecord) instance. When the request completes, it is “wrapped” so
the result is a Combined instance – not a Cached instance.

Most things, the “wrapped” version is self. For instances of bases
which are a subclass of Cached::Base, the wrapped version is the
equivalent Combined version. For somethings like Arrays and Hash, the
wrap version is an Arrary or Hash with each value wrapped.

Right now, my biggest question is how does the association do this
magic? I haven’t found it yet. Driving home I thought I need to look
for places that do things like class << self and other things like that
which modify the metaclass of an object. I’ve been looking at
initialize and those things but that hasn’t told me anything. But I’m
wondering how an object that isn’t an Array (to pick an example) says
“Array#…” when inspected or when name is called, etc.

Thank you for your help