Module: PuppetForge::LazyAccessors
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
-
.included(base) ⇒ void
Callback for module inclusion.
Instance Method Summary collapse
-
#class_name ⇒ Object
Provide class name for object.
-
#fetch ⇒ self
Updates model data from the API.
-
#inspect ⇒ Object
Override the default #inspect behavior.
-
#method_missing(name, *args, &blk) ⇒ Object
Override the default #method_misssing behavior.
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.
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_name ⇒ Object
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 |
#fetch ⇒ self
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.
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 |
#inspect ⇒ Object
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 |