Class: Latitude::API::APIResource

Inherits:
Object
  • Object
show all
Extended by:
Latitude::API::Attributes::ClassMethods
Includes:
Latitude::API::Attributes::InstanceMethods
Defined in:
lib/latitude/api/api_resource.rb

Constant Summary collapse

READ_ONLY_ATTRIBUTES =
%w[id type created_at updated_at].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Latitude::API::Attributes::ClassMethods

attribute, attribute_specs

Methods included from Latitude::API::Attributes::InstanceMethods

#attribute_spec, #read_attribute, #wrap_attribute

Constructor Details

#initialize(data, meta: nil, path_params: {}, opts: {}) ⇒ APIResource

Returns a new instance of APIResource.



107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/latitude/api/api_resource.rb', line 107

def initialize(data, meta: nil, path_params: {}, opts: {})
  @raw_data    = data
  @id          = data["id"]
  @type        = data["type"]
  @meta        = data["meta"] || meta || {}
  @path_params = path_params || {}
  @opts        = opts || {}
  @values      = {}
  @unsaved     = []
  Array(data["attributes"]).each do |k, v|
    @values[k.to_s] = wrap_attribute(k, v)
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/latitude/api/api_resource.rb', line 211

def method_missing(name, *args)
  name_s = name.to_s
  if name_s.end_with?("=") && args.size == 1
    attr_name = name_s.chomp("=")
    if read_only_attribute?(attr_name)
      raise NoMethodError, "cannot assign to read-only attribute #{attr_name}"
    end

    return self[attr_name] = args.first
  end

  return @values[name_s] if @values.key?(name_s) && args.empty?

  super
end

Instance Attribute Details

#idObject (readonly)

Returns the value of attribute id.



105
106
107
# File 'lib/latitude/api/api_resource.rb', line 105

def id
  @id
end

#metaObject (readonly)

Returns the value of attribute meta.



105
106
107
# File 'lib/latitude/api/api_resource.rb', line 105

def meta
  @meta
end

#path_paramsObject (readonly)

Returns the value of attribute path_params.



105
106
107
# File 'lib/latitude/api/api_resource.rb', line 105

def path_params
  @path_params
end

#raw_dataObject (readonly)

Returns the value of attribute raw_data.



105
106
107
# File 'lib/latitude/api/api_resource.rb', line 105

def raw_data
  @raw_data
end

#typeObject (readonly)

Returns the value of attribute type.



105
106
107
# File 'lib/latitude/api/api_resource.rb', line 105

def type
  @type
end

Class Method Details

.class_url(path_params: {}) ⇒ Object



32
33
34
# File 'lib/latitude/api/api_resource.rb', line 32

def class_url(path_params: {})
  fill_path(resource_path, path_params)
end

.config_sourceObject



63
64
65
# File 'lib/latitude/api/api_resource.rb', line 63

def config_source
  Thread.current[:latitude_api_config] || Latitude.config
end

.construct_from(parsed, opts: {}, path_params: {}) ⇒ Object

Raises:



56
57
58
59
60
61
# File 'lib/latitude/api/api_resource.rb', line 56

def construct_from(parsed, opts: {}, path_params: {})
  data = parsed.is_a?(Hash) ? parsed["data"] : nil
  raise APIError, "response missing data envelope" unless data.is_a?(Hash)

  new(data, meta: parsed["meta"], path_params: path_params, opts: opts)
end

.execute_request(method:, path:, query: nil, body: nil, opts: {}, headers: {}) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/latitude/api/api_resource.rb', line 42

def execute_request(method:, path:, query: nil, body: nil, opts: {}, headers: {})
  options = RequestOptions.wrap(opts)
  effective = options.apply_to(config_source)
  RequestExecutor.new(effective).execute(
    method: method,
    path: path,
    query: query,
    body: body,
    headers: headers,
    idempotency_key: options.idempotency_key,
    extra_headers: options.headers,
  )
end

.extract_path_params(params, keys) ⇒ Object



75
76
77
78
79
80
81
82
# File 'lib/latitude/api/api_resource.rb', line 75

def extract_path_params(params, keys)
  extracted = {}
  keys.each do |k|
    v = params.delete(k) || params.delete(k.to_s)
    extracted[k] = v if v
  end
  extracted
end

.id_prefix(value = nil) ⇒ Object



20
21
22
23
# File 'lib/latitude/api/api_resource.rb', line 20

def id_prefix(value = nil)
  @id_prefix = value if value
  @id_prefix
end

.instance_url(id, path_params: {}) ⇒ Object

Raises:

  • (ArgumentError)


36
37
38
39
40
# File 'lib/latitude/api/api_resource.rb', line 36

def instance_url(id, path_params: {})
  raise ArgumentError, "id is required" if id.nil? || id.to_s.empty?

  "#{class_url(path_params: path_params)}/#{URI.encode_www_form_component(id)}"
end

.path_param_keysObject



84
85
86
# File 'lib/latitude/api/api_resource.rb', line 84

def path_param_keys
  resource_path.scan(/:(\w+)/).flatten.map(&:to_sym)
end

.resource_path(value = nil) ⇒ Object



15
16
17
18
# File 'lib/latitude/api/api_resource.rb', line 15

def resource_path(value = nil)
  @resource_path = value if value
  @resource_path || raise(NotImplementedError, "#{name} must declare resource_path")
end

.resource_type(value = nil) ⇒ Object



10
11
12
13
# File 'lib/latitude/api/api_resource.rb', line 10

def resource_type(value = nil)
  @resource_type = value if value
  @resource_type || raise(NotImplementedError, "#{name} must declare resource_type")
end

.with_config(config) ⇒ Object



67
68
69
70
71
72
73
# File 'lib/latitude/api/api_resource.rb', line 67

def with_config(config)
  prev = Thread.current[:latitude_api_config]
  Thread.current[:latitude_api_config] = config
  yield
ensure
  Thread.current[:latitude_api_config] = prev
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



140
141
142
# File 'lib/latitude/api/api_resource.rb', line 140

def ==(other)
  other.is_a?(self.class) && other.id == @id
end

#[](key) ⇒ Object



154
155
156
# File 'lib/latitude/api/api_resource.rb', line 154

def [](key)
  @values[key.to_s]
end

#[]=(key, value) ⇒ Object



158
159
160
161
162
163
# File 'lib/latitude/api/api_resource.rb', line 158

def []=(key, value)
  key_s = key.to_s
  @values[key_s] = wrap_attribute(key_s, value)
  @unsaved << key_s unless @unsaved.include?(key_s)
  value
end

#as_json(*_) ⇒ Object



136
137
138
# File 'lib/latitude/api/api_resource.rb', line 136

def as_json(*_)
  to_h
end

#attributesObject



121
122
123
# File 'lib/latitude/api/api_resource.rb', line 121

def attributes
  @values.each_with_object({}) { |(k, v), h| h[k] = unwrap(v) }
end

#changed?Boolean

Returns:

  • (Boolean)


173
174
175
# File 'lib/latitude/api/api_resource.rb', line 173

def changed?
  !@unsaved.empty?
end

#delete(opts = {}) ⇒ Object Also known as: destroy



193
194
195
196
# File 'lib/latitude/api/api_resource.rb', line 193

def delete(opts = {})
  self.class.execute_request(method: :delete, path: resource_url, opts: opts)
  self
end

#hashObject



146
147
148
# File 'lib/latitude/api/api_resource.rb', line 146

def hash
  [@type, @id].hash
end

#inspectObject



150
151
152
# File 'lib/latitude/api/api_resource.rb', line 150

def inspect
  "#<#{self.class.name} id=#{@id.inspect} #{attributes.map { |k, v| "#{k}=#{v.inspect}" }.join(' ')}>"
end

#refresh(opts = {}) ⇒ Object



177
178
179
180
181
# File 'lib/latitude/api/api_resource.rb', line 177

def refresh(opts = {})
  parsed = self.class.execute_request(method: :get, path: resource_url, opts: opts)
  update_from(parsed)
  self
end

#resource_urlObject



200
201
202
# File 'lib/latitude/api/api_resource.rb', line 200

def resource_url
  self.class.instance_url(@id, path_params: @path_params)
end

#respond_to_missing?(name, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


204
205
206
207
208
209
# File 'lib/latitude/api/api_resource.rb', line 204

def respond_to_missing?(name, include_private = false)
  return true if @values.key?(name.to_s)
  return true if name.to_s.end_with?("=") && !read_only_attribute?(name.to_s.chomp("="))

  super
end

#save(opts = {}) ⇒ Object



183
184
185
186
187
188
189
190
191
# File 'lib/latitude/api/api_resource.rb', line 183

def save(opts = {})
  return self unless changed?

  payload = JSONAPI.encode(type: @type || self.class.resource_type, id: @id, attributes: unsaved_attributes)
  parsed = self.class.execute_request(method: :patch, path: resource_url, body: payload, opts: opts)
  update_from(parsed)
  @unsaved.clear
  self
end

#to_hObject Also known as: to_hash



125
126
127
128
129
130
131
132
# File 'lib/latitude/api/api_resource.rb', line 125

def to_h
  out = {}
  out["id"] = @id if @id
  out["type"] = @type if @type
  out["attributes"] = attributes
  out["meta"] = @meta unless @meta.nil? || @meta.empty?
  out
end

#unsaved_attributesObject



169
170
171
# File 'lib/latitude/api/api_resource.rb', line 169

def unsaved_attributes
  @unsaved.each_with_object({}) { |k, h| h[k] = unwrap(@values[k]) }
end

#write_attribute(name, value) ⇒ Object



165
166
167
# File 'lib/latitude/api/api_resource.rb', line 165

def write_attribute(name, value)
  self[name] = value
end