Martin Martin - 1 month ago 8
Ruby Question

Return Instance Variable in Object.new and keep Chained methods working

I try to create a gem that performs a call to an API and returns an

HTTParty::Response
object using a minimal syntax like this:

MyGem.list_all
#<HTTParty::Response:0x7fb859922908 parsed_response={...}>


My idea is to follow an ActiveRecord syntax style and allow chained methods like:

Mygem.list_all.total_count
> 22


The current code goes like this

module MyGem
class << self
def list_all
@list_all ||= Get.new('list_all')
end
end

class Get
def initialize(path)
@get ||= get(path)
end

def total_count
@get['total_count']
end

def get(path)
HTTParty.get(url+path)
end
end
end


The code above works with the following call fetching the data from the
HTTParty::Response
and extracting the count value correctly.

Mygem.list_all.total_count
> 22


However when I call
MyGem.list_all
I get
MyGem::Get
object instead including its
@get
instance which contains the
HTTParty::Response
like this:

#<MyGem::Get:0x007fb26d10f4b0 @get=HTTParty response...


I understand
new
is returning its object and not the instance variable, my question is how can I still perform this call, get the HTTParty response and still have its chained (
total_count
) methods to work?

MyGem.list_all
#<HTTParty::Response:0x7fb859922908 parsed_response={...}>

Answer

The rule of thumb is: whether you want to chain, return self from each single method, and implement everything you expect to retrieve from any subsequent method by delegation to self.

module MyGem
  class << self
    def list_all
      @list_all ||= Get.new('list_all')
    end

    class Get < BasicObject
      def initialize(path)
        @get ||= get(path)
      end
      HTTParty::Response.instance_methods(true).each do |m|
        meth = m # damn ruby closures
        define_method meth do |*args, &λ|
          @get.public_send(meth, *args, &λ)
        end
      end
    end
  end
end