module Her::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. {Her}, 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 of {Her::Model}s with an “updatable” interface.
Public Class Methods
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/her/lazy_accessors.rb, line 25 def self.included(base) base.singleton_class.class_eval do attr_accessor :_accessor_container end end
Public Instance Methods
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/her/lazy_accessors.rb, line 80 def fetch return self if @_fetch klass = self.class params = { :_method => klass.method_for(:find), :_path => self.request_path } klass.request(params) do |data, response| if @_fetch = response.success? parsed = klass.parse(data[:data]) parsed.merge!(:_metadata => data[:metadata], :_errors => data[:errors]) self.send(:initialize, parsed) self.run_callbacks(:find) end end return self end
Override the default {Her::Model}#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/her/lazy_accessors.rb, line 36 def inspect attrs = attributes.map do |x, y| [ x, attribute_for_inspect(y) ].join('=') end "#<#{self.class}(#{request_path}) #{attrs.join(' ')}>" end
Override the default {Her::Model}#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.
# File lib/her/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