Module: PuppetForge::LazyAccessors

Included in:
V3::Base, V3::User
Defined in:
lib/puppet_forge/lazy_accessors.rb

Overview

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.

Defined Under Namespace

Classes: AccessorContainer

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &blk) ⇒ Object

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.



61
62
63
64
65
66
67
68
69
70
71
72
73
# 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

Class Method Details

.included(base) ⇒ void

This method returns an undefined value.

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.

Parameters:

  • base (Class)

    the Class this module was included into



19
20
21
22
23
# 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

Instance Method Details

#class_nameObject

Provide class name for object



27
28
29
# File 'lib/puppet_forge/lazy_accessors.rb', line 27

def class_name
  self.class.name.split("::").last.downcase
end

#fetchself

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.

Returns:

  • (self)


80
81
82
83
84
85
86
87
88
89
90
91
# 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

#inspectObject

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.



36
37
38
39
40
41
# 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