ActiveRecord Class Methods / Relationships
I am writing some rails code that uses proxy objects around ActiveRecord models. However, whenever a class method is called on ActiveRecord::Relation
or ActiveRecord::Associations::CollectionProxy
, the value self
is the original ActiveRecord class and not the relationship. For example:
class Blah < ActiveRecord::Base
def self.thing
MyProxyObject.new(self)
end
end
Blah.thing #<MyProxyObject @wrapped=Blah>
Blah.where(:column => 'value').thing #<MyProxyObject @wrapped=ActiveRecord::Relation>
The desired functionality would be that the wrapped object in the second case would be the object ActiveRecord::Relation
returned Blah.where
. Is there an easy way to achieve this?
source to share
@FrederickCheung's answer is the right way to go. I am posting this answer to explain why this is happening.
The secret is in the file active_record/relation/delegation.rb
:
def self.delegate_to_scoped_klass(method)
if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args, &block)
scoping { @klass.#{method}(*args, &block) }
end
RUBY
else
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args, &block)
scoping { @klass.send(#{method.inspect}, *args, &block) }
end
RUBY
end
end
As you can see, it defines a new method on the relationship object that simply delegates it to the class, so self
it is always the class itself.
source to share
Found a hacky way to do this, I'll leave this unacceptable for a while in case someone comes up with a more elegant solution.
ActiveRecord::Relation.instance_eval do
def thing
if klass.respond_to? :thing
MyProxyObject.new(self)
else
raise NoMethodError.new
end
end
end
This just adds a method to the class ActiveRecord::Relation
(in practice this should also be defined for ActiveRecord::Associations::CollectionProxy
and ActiveRecord::AssociationRelation
). It checks if the base class has a specific method, and if it creates a proxy object wrapped around itself, instead
source to share