weird binding error

P

Perry Smith

# 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?"]
 
A

ara howard

# 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/
 
P

Perry Smith

ara said:
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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,072
Latest member
trafficcone

Latest Threads

Top