1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
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
|