161 lines
4.1 KiB
Ruby
161 lines
4.1 KiB
Ruby
require 'forwardable'
|
|
|
|
module Sawyer
|
|
class Resource
|
|
SPECIAL_METHODS = Set.new(%w(agent rels fields))
|
|
attr_reader :_agent, :_rels, :_fields
|
|
attr_reader :attrs
|
|
include Enumerable
|
|
extend Forwardable
|
|
|
|
# Initializes a Resource with the given data.
|
|
#
|
|
# agent - The Sawyer::Agent that made the API request.
|
|
# data - Hash of key/value properties.
|
|
def initialize(agent, data = {})
|
|
@_agent = agent
|
|
data, links = agent.parse_links(data)
|
|
@_rels = Relation.from_links(agent, links)
|
|
@_fields = Set.new
|
|
@_metaclass = (class << self; self; end)
|
|
@attrs = {}
|
|
data.each do |key, value|
|
|
@_fields << key
|
|
@attrs[key.to_sym] = process_value(value)
|
|
end
|
|
@_metaclass.send(:attr_accessor, *data.keys)
|
|
end
|
|
|
|
# Processes an individual value of this resource. Hashes get exploded
|
|
# into another Resource, and Arrays get their values processed too.
|
|
#
|
|
# value - An Object value of a Resource's data.
|
|
#
|
|
# Returns an Object to set as the value of a Resource key.
|
|
def process_value(value)
|
|
case value
|
|
when Hash then self.class.new(@_agent, value)
|
|
when Array then value.map { |v| process_value(v) }
|
|
else value
|
|
end
|
|
end
|
|
|
|
# Checks to see if the given key is in this resource.
|
|
#
|
|
# key - A Symbol key.
|
|
#
|
|
# Returns true if the key exists, or false.
|
|
def key?(key)
|
|
@_fields.include? key
|
|
end
|
|
|
|
# Allow fields to be retrieved via Hash notation
|
|
#
|
|
# method - key name
|
|
#
|
|
# Returns the value from attrs if exists
|
|
def [](method)
|
|
send(method.to_sym)
|
|
rescue NoMethodError
|
|
nil
|
|
end
|
|
|
|
# Allow fields to be set via Hash notation
|
|
#
|
|
# method - key name
|
|
# value - value to set for the attr key
|
|
#
|
|
# Returns - value
|
|
def []=(method, value)
|
|
send("#{method}=", value)
|
|
rescue NoMethodError
|
|
nil
|
|
end
|
|
|
|
def_delegators :attrs, :dig, :fetch
|
|
|
|
ATTR_SETTER = '='.freeze
|
|
ATTR_PREDICATE = '?'.freeze
|
|
|
|
# Provides access to a resource's attributes.
|
|
def method_missing(method, *args)
|
|
attr_name, suffix = method.to_s.scan(/([a-z0-9\_]+)(\?|\=)?$/i).first
|
|
if suffix == ATTR_SETTER
|
|
@_metaclass.send(:attr_accessor, attr_name)
|
|
@_fields << attr_name.to_sym
|
|
send(method, args.first)
|
|
elsif attr_name && @_fields.include?(attr_name.to_sym)
|
|
value = @attrs[attr_name.to_sym]
|
|
case suffix
|
|
when nil
|
|
@_metaclass.send(:attr_accessor, attr_name)
|
|
value
|
|
when ATTR_PREDICATE then !!value
|
|
end
|
|
elsif suffix.nil? && SPECIAL_METHODS.include?(attr_name)
|
|
instance_variable_get "@_#{attr_name}"
|
|
elsif attr_name && !@_fields.include?(attr_name.to_sym)
|
|
nil
|
|
else
|
|
super
|
|
end
|
|
end
|
|
|
|
# Wire up accessor methods to pull from attrs
|
|
def self.attr_accessor(*attrs)
|
|
attrs.each do |attribute|
|
|
class_eval do
|
|
define_method attribute do
|
|
@attrs[attribute.to_sym]
|
|
end
|
|
|
|
define_method "#{attribute}=" do |value|
|
|
@attrs[attribute.to_sym] = value
|
|
end
|
|
|
|
define_method "#{attribute}?" do
|
|
!!@attrs[attribute.to_sym]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def inspect
|
|
to_attrs.respond_to?(:pretty_inspect) ? to_attrs.pretty_inspect : to_attrs.inspect
|
|
end
|
|
|
|
def each(&block)
|
|
@attrs.each(&block)
|
|
end
|
|
|
|
# private
|
|
def to_yaml_properties
|
|
[:@attrs, :@_fields, :@_rels]
|
|
end
|
|
|
|
def to_attrs
|
|
hash = self.attrs.clone
|
|
hash.keys.each do |k|
|
|
if hash[k].is_a?(Sawyer::Resource)
|
|
hash[k] = hash[k].to_attrs
|
|
elsif hash[k].is_a?(Array) && hash[k].all?{|el| el.is_a?(Sawyer::Resource)}
|
|
hash[k] = hash[k].collect{|el| el.to_attrs}
|
|
end
|
|
end
|
|
hash
|
|
end
|
|
|
|
alias to_hash to_attrs
|
|
alias to_h to_attrs
|
|
|
|
def marshal_dump
|
|
[@attrs, @_fields, @_rels]
|
|
end
|
|
|
|
def marshal_load(dumped)
|
|
@attrs, @_fields, @_rels = *dumped.shift(3)
|
|
@_metaclass = (class << self; self; end)
|
|
end
|
|
end
|
|
end
|