Search code examples
ruby-on-railsrubyruby-on-rails-3activeresourcedynamic-dispatch

Changing an instance variable within dynamic method (dynamic resource using ActiveResource)


I am using dynamic dispatch to define several class methods in a class that inherits from ActiveResource.

class Invoice < ActiveResource::Base
  self.site = 'http://localhost:8080/'

  def self.define(method)
    define_singleton_method(method) do |params = {}|
      site = self.site
      self.site = "#{self.site}/Invoice/#{method.to_s.camelize(:lower)}"

      puts "self.site -> #{self.site}"  
      results = Invoice.all(params: params)

      self.site = site
      puts "changing self.site back to #{site}"     

      return results
    end
  end

  define :get_details
  define :put_approval
  define :get_attachment
  define :get_pending_notifications
end

This works great for the first call, whatever it is (Invoice.get_details, Invoice.get_pending_notifications...), but always fails on the second call.

I would like to understand why this is happening and what I can do to get it fixed.


Solution

  • Researching this further, I have found that self.site is not actually changing when I ask it to. It tells me it is changing in the logs, but it lies! self.site does not change from its initial set state in the first method called. I have a theory as to why this is not working and a workaround to get it working.

    First, my theory:

    When any one of the class methods defined is called, site is set. Since site is outside of the scope of the class method being called, once it is set initially, it cannot be changed thereafter.

    An example:

    Invoice.get_details
    

    Invoice.site is initially set to "localhost:8080/Invoice/getDetails"

    Invoice.get_pending_notifications
    

    Invoice.site cannot be changed because it is already defined as "localhost:8080/Invoice/getDetails"

    The above is just a working theory.

    How I got this working:

    Remove all references to self.site except for the initial set self.site = "localhost:8080" and instead use a url string. (Under the section "Finding resources from custom paths" at http://ofps.oreilly.com/titles/9780596521424/activeresource_id59243.html )

    class Invoice < ActiveResource::Base
      self.site = "localhost:8080"
      def self.define(method)
        define_singleton_method(method) do |params = {}|
          url = "#{self.site}/Invoice/#{method.to_s.camelize(:lower)}"
    
          # results = Invoice.all(params: params) <- Change this call to below
          results = Invoice.find(:all, from: url, params: params) 
    
          return results
        end
      end
    
      define :get_details
      define :put_approval
      define :get_attachment
      define :get_pending_notifications
    end
    

    Using the code above, I can call any of the methods defined, each pointing to a different url, without issue.