Slicedpan
Slicedpan

Reputation: 5015

ActiveRecord Class Methods / Relations self

I am writing some rails code that uses proxy objects around ActiveRecord models. However, whenever a Class Method is called on an ActiveRecord::Relation or ActiveRecord::Associations::CollectionProxy, the value of self is the original ActiveRecord class as opposed to the relation. 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>

Desired functionality would be that the wrapped object in the second case would be the ActiveRecord::Relation object returned by Blah.where. Is there an easy way of achieving this?

Upvotes: 5

Views: 1215

Answers (3)

BroiSatse
BroiSatse

Reputation: 44725

@FrederickCheung answer is the Right Way To Go. I post this answer to explain why it is happenning.

The mystery sits in active_record/relation/delegation.rb file:

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 relation object, which is just delegating it to the class, hence self is always a class itself here.

Upvotes: 1

Frederick Cheung
Frederick Cheung

Reputation: 84182

Something along the lines of

class Blah < ActiveRecord::Base   
  def self.thing
    MyProxyObject.new(all)
  end
end

works for me (prior to rails 4, use scoped rather than all):

Blah.where(:foo => 1).thing

results in the proxy object holding a relation with the appropriate conditions applied.

Upvotes: 5

Slicedpan
Slicedpan

Reputation: 5015

Have found a hackish way of doing this, I will leave this unaccepted for a while on the off chance that someone will come 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 the method to the ActiveRecord::Relation class (in practice this should also be defined for ActiveRecord::Associations::CollectionProxy and ActiveRecord::AssociationRelation). It checks to see if the base class has the method defined, and if so creates the proxy object wrapped around itself instead

Upvotes: 0

Related Questions