module PuppetForge::LazyAccessors

When dealing with a remote service, it's reasonably common to receive only a partial representation of the underlying object, with additional data available upon request. PuppetForge, by default, provides a convenient interface for accessing whatever local data is available, but lacks good support for fleshing out partial representations. In order to build a seamless interface for both local and remote attriibutes, this module replaces the default behavior with an “updatable” interface.

Public Class Methods

included(base) click to toggle source

Callback for module inclusion.

On each lazy class we'll store a reference to a Module, which will act as the container for the attribute methods.

@param base [Class] the Class this module was included into @return [void]

# File lib/puppet_forge/lazy_accessors.rb, line 19
def self.included(base)
  base.singleton_class.class_eval do
    attr_accessor :_accessor_container
  end
end

Public Instance Methods

class_name() click to toggle source

Provide class name for object

# File lib/puppet_forge/lazy_accessors.rb, line 27
def class_name
  self.class.name.split("::").last.downcase
end
fetch() click to toggle source

Updates model data from the API. This method will short-circuit if this model has already been fetched from the remote server, to avoid duplicate requests.

@return [self]

# File lib/puppet_forge/lazy_accessors.rb, line 80
def fetch
  return self if @_fetch

  klass = self.class

  response = klass.request("#{self.class_name}s/#{self.slug}")
  if @_fetch = response.success?
    self.send(:initialize, response.body)
  end

  return self
end
inspect() click to toggle source

Override the default inspect behavior.

The original behavior actually invokes each attribute accessor, which can be somewhat problematic when the accessors have been overridden. This implementation simply reports the contents of the attributes hash.

# File lib/puppet_forge/lazy_accessors.rb, line 36
def inspect
  attrs = attributes.map do |x, y|
    [ x, y ].join('=')
  end
  "#<#{self.class}(#{uri}) #{attrs.join(' ')}>"
end
method_missing(name, *args, &blk) click to toggle source

Override the default method_misssing behavior.

When we receive a {#method_missing} call, one of three things is true:

  • the caller is looking up a piece of local data without an accessor

  • the caller is looking up a piece of remote data

  • the method doesn't actually exist

To solve the remote case, we begin by ensuring our attribute list is up-to-date with a call to {#fetch}, create a new {AccessorContainer} if necessary, and add any missing accessors to the container. We can then dispatch the method call to the newly created accessor.

The local case is almost identical, except that we can skip updating the model's attributes.

If, after our work, we haven't seen the requested method name, we can surmise that it doesn't actually exist, and pass the call along to upstream handlers.

Calls superclass method
# File lib/puppet_forge/lazy_accessors.rb, line 61
def method_missing(name, *args, &blk)
  fetch unless has_attribute?(name.to_s[/\w+/])

  klass = self.class
  mod = (klass._accessor_container ||= AccessorContainer.new(klass))
  mod.add_attributes(attributes.keys)

  if (meth = mod.instance_method(name) rescue nil)
    return meth.bind(self).call(*args)
  else
    return super(name, *args, &blk)
  end
end