Class: Aptible::Resource::Base

Inherits:
HyperResource show all
Defined in:
lib/aptible/resource/base.rb

Overview

rubocop:disable ClassLength

Constant Summary

Constants inherited from HyperResource

HyperResource::VERSION, HyperResource::VERSION_DATE

Constants included from HyperResource::Modules::HTTP

HyperResource::Modules::HTTP::MAX_COORDINATOR_RETRIES

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from HyperResource

#[], #_hr_new_from_link, #_hr_response_class, #changed?, #deserialized_response, #each, #get_data_type_from_response, #incoming_body_filter, #inspect, #method_missing, namespaced_class, #outgoing_body_filter, #outgoing_uri_filter, #response_body, response_class, #response_object

Methods included from HyperResource::Modules::InternalAttributes

included

Methods included from HyperResource::Modules::HTTP

#create, #faraday_connection, #get, #patch, #post, #put

Constructor Details

#initialize(options = {}) ⇒ Base

Returns a new instance of Base.



224
225
226
227
228
229
230
# File 'lib/aptible/resource/base.rb', line 224

def initialize(options = {})
  return super(options) unless options.is_a?(Hash)

  populate_default_options!(options)
  super(options)
  self.token = options[:token] if options[:token]
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class HyperResource

Instance Attribute Details

#errorsObject

Returns the value of attribute errors.



19
20
21
# File 'lib/aptible/resource/base.rb', line 19

def errors
  @errors
end

#tokenObject

Returns the value of attribute token.



20
21
22
# File 'lib/aptible/resource/base.rb', line 20

def token
  @token
end

Class Method Details

.adapterObject



27
28
29
# File 'lib/aptible/resource/base.rb', line 27

def self.adapter
  Aptible::Resource::Adapter
end

.all(options = {}) ⇒ Object



57
58
59
60
61
# File 'lib/aptible/resource/base.rb', line 57

def self.all(options = {})
  out = []
  each_page(options) { |page| out.concat page }
  out
end

.basenameObject



35
36
37
# File 'lib/aptible/resource/base.rb', line 35

def self.basename
  name.split('::').last.underscore.pluralize
end

.belongs_to(relation) ⇒ Object



117
118
119
120
121
122
123
124
125
126
# File 'lib/aptible/resource/base.rb', line 117

def self.belongs_to(relation)
  define_method relation do
    get unless loaded
    if (memoized = instance_variable_get("@#{relation}"))
      memoized
    elsif links[relation]
      instance_variable_set("@#{relation}", links[relation].get)
    end
  end
end

.cast_field(value, type) ⇒ Object



204
205
206
207
208
209
210
211
212
# File 'lib/aptible/resource/base.rb', line 204

def self.cast_field(value, type)
  if type == Time
    Time.parse(value) if value
  elsif type == DateTime
    DateTime.parse(value) if value
  else
    value
  end
end

.collection_hrefObject



31
32
33
# File 'lib/aptible/resource/base.rb', line 31

def self.collection_href
  "/#{basename}"
end

.create(params = {}) ⇒ Object



90
91
92
93
94
# File 'lib/aptible/resource/base.rb', line 90

def self.create(params = {})
  create!(params)
rescue HyperResource::ResponseError => e
  new.tap { |resource| resource.errors = Errors.from_exception(e) }
end

.create!(params = {}) ⇒ Object



84
85
86
87
88
# File 'lib/aptible/resource/base.rb', line 84

def self.create!(params = {})
  token = params.delete(:token)
  resource = new(token: token)
  resource.send(basename).create(normalize_params(params))
end

.define_embeds_many_getter(relation) ⇒ Object



169
170
171
172
173
174
# File 'lib/aptible/resource/base.rb', line 169

def self.define_embeds_many_getter(relation)
  define_method relation do
    get unless loaded
    objects[relation].entries
  end
end

.define_has_many_getters(relation) ⇒ Object

rubocop:disable MethodLength rubocop:disable AbcSize



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/aptible/resource/base.rb', line 137

def self.define_has_many_getters(relation)
  define_method relation do
    get unless loaded
    if (memoized = instance_variable_get("@#{relation}"))
      memoized
    elsif links[relation]
      depaginated = self.class.all(href: links[relation].base_href,
                                   token: token,
                                   headers: headers)
      instance_variable_set("@#{relation}", depaginated)
    end
  end

  define_method "each_#{relation.to_s.singularize}" do |&block|
    return if block.nil?
    self.class.each_page(href: links[relation].base_href,
                         token: token,
                         headers: headers) do |page|
      page.each { |entry| block.call entry }
    end
  end
end

.define_has_many_setter(relation) ⇒ Object

rubocop:disable MethodLength rubocop:disable AbcSize



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/aptible/resource/base.rb', line 178

def self.define_has_many_setter(relation)
  define_method "create_#{relation.to_s.singularize}!" do |params = {}|
    get unless loaded
    links[relation].create(self.class.normalize_params(params))
  end

  define_method "create_#{relation.to_s.singularize}" do |params = {}|
    begin
      send "create_#{relation.to_s.singularize}!", params
    rescue HyperResource::ResponseError => e
      Base.new(root: root_url, namespace: namespace).tap do |base|
        base.errors = Errors.from_exception(e)
      end
    end
  end
end

.each_page(options = {}) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/aptible/resource/base.rb', line 39

def self.each_page(options = {})
  return unless block_given?
  href = options[:href] || collection_href
  while href
    # TODO: Breaking here is consistent with the existing behavior of
    # .all, but it essentially swallows an error if the page you're
    # hitting happens to 404. This should probably be addressed in an
    # effort to make this API client more strict.
    resource = find_by_url(href, options)
    break if resource.nil?

    yield resource.entries

    next_link = resource.links['next']
    href = next_link ? next_link.href : nil
  end
end

.embeds_many(relation) ⇒ Object

rubocop:enable PredicateName



103
104
105
106
# File 'lib/aptible/resource/base.rb', line 103

def self.embeds_many(relation)
  define_embeds_many_getter(relation)
  define_has_many_setter(relation)
end

.embeds_one(relation) ⇒ Object

rubocop:enable AbcSize rubocop:enable MethodLength



162
163
164
165
166
167
# File 'lib/aptible/resource/base.rb', line 162

def self.embeds_one(relation)
  define_method relation do
    get unless loaded
    objects[relation]
  end
end

.faraday_optionsObject



214
215
216
217
218
219
220
221
222
# File 'lib/aptible/resource/base.rb', line 214

def self.faraday_options
  # Default Faraday options. May be overridden by passing
  # faraday_options to the initializer.
  {
    request: {
      open_timeout: 10
    }
  }
end

.field(name, options = {}) ⇒ Object



108
109
110
111
112
113
114
115
# File 'lib/aptible/resource/base.rb', line 108

def self.field(name, options = {})
  define_method name do
    self.class.cast_field(attributes[name], options[:type])
  end

  # Define ? accessor for Boolean attributes
  define_method("#{name}?") { !!send(name) } if options[:type] == Boolean
end

.find(id, options = {}) ⇒ Object



69
70
71
72
73
74
# File 'lib/aptible/resource/base.rb', line 69

def self.find(id, options = {})
  params = options.except(:token, :root, :namespace, :headers)
  params = normalize_params(params)
  params = params.empty? ? '' : '?' + params.to_query
  find_by_url("#{collection_href}/#{id}#{params}", options)
end

.find_by_url(url, options = {}) ⇒ Object



76
77
78
79
80
81
82
# File 'lib/aptible/resource/base.rb', line 76

def self.find_by_url(url, options = {})
  # REVIEW: Should exception be raised if return type mismatch?
  new(options).find_by_url(url)
rescue HyperResource::ClientError => e
  return nil if e.response.status == 404
  raise e
end

.get_data_type_from_response(response) ⇒ Object



22
23
24
25
# File 'lib/aptible/resource/base.rb', line 22

def self.get_data_type_from_response(response)
  return nil unless response && response.body
  adapter.get_data_type_from_object(adapter.deserialize(response.body))
end

.has_many(relation) ⇒ Object

rubocop:disable PredicateName



97
98
99
100
# File 'lib/aptible/resource/base.rb', line 97

def self.has_many(relation)
  define_has_many_getters(relation)
  define_has_many_setter(relation)
end

.has_one(relation) ⇒ Object

rubocop:disable PredicateName



129
130
131
132
# File 'lib/aptible/resource/base.rb', line 129

def self.has_one(relation)
  # Better than class << self + alias_method?
  belongs_to(relation)
end

.normalize_params(params = {}) ⇒ Object

rubocop: enable AbcSize rubocop:enable MethodLength



197
198
199
200
201
202
# File 'lib/aptible/resource/base.rb', line 197

def self.normalize_params(params = {})
  params_array = params.map do |key, value|
    value.is_a?(HyperResource) ? [key, value.href] : [key, value]
  end
  Hash[params_array]
end

.where(options = {}) ⇒ Object



63
64
65
66
67
# File 'lib/aptible/resource/base.rb', line 63

def self.where(options = {})
  params = options.except(:token, :root, :namespace, :headers)
  params = normalize_params(params)
  find_by_url("#{collection_href}?#{params.to_query}", options).entries
end

Instance Method Details

#_hyperresource_updateObject

rubocop:disable Style/Alias



271
# File 'lib/aptible/resource/base.rb', line 271

alias_method :_hyperresource_update, :update

#adapterObject



244
245
246
# File 'lib/aptible/resource/base.rb', line 244

def adapter
  self.class.adapter
end

#bearer_tokenObject



262
263
264
265
266
267
268
# File 'lib/aptible/resource/base.rb', line 262

def bearer_token
  case token
  when Aptible::Resource::Base then token.access_token
  when Fridge::AccessToken then token.to_s
  when String then token
  end
end

#deleteObject Also known as: destroy



287
288
289
290
291
292
293
294
295
296
297
# File 'lib/aptible/resource/base.rb', line 287

def delete
  super
rescue HyperResource::ServerError
  raise
rescue HyperResource::ClientError => e
  # Already deleted
  raise unless e.response.status == 404
rescue HyperResource::ResponseError
  # HyperResource/Faraday choke on empty response bodies
  nil
end

#error_htmlObject



312
313
314
# File 'lib/aptible/resource/base.rb', line 312

def error_html
  errors.full_messages.join('<br />')
end

#find_by_url(url_or_href) ⇒ Object



256
257
258
259
260
# File 'lib/aptible/resource/base.rb', line 256

def find_by_url(url_or_href)
  resource = dup
  resource.href = url_or_href.gsub(/^#{root}/, '')
  resource.get
end

#namespaceObject



248
249
250
# File 'lib/aptible/resource/base.rb', line 248

def namespace
  raise 'Resource server namespace must be defined by subclass'
end

#populate_default_options!(options) ⇒ Object



232
233
234
235
236
237
# File 'lib/aptible/resource/base.rb', line 232

def populate_default_options!(options)
  options[:root] ||= root_url
  options[:namespace] ||= namespace
  options[:headers] ||= {}
  options[:headers]['Content-Type'] = 'application/json'
end

#reloadObject

NOTE: The following does not update the object in-place



304
305
306
# File 'lib/aptible/resource/base.rb', line 304

def reload
  self.class.find_by_url(href, token: token, headers: headers)
end

#root_urlObject



252
253
254
# File 'lib/aptible/resource/base.rb', line 252

def root_url
  raise 'Resource server root URL must be defined by subclass'
end

#update(params) ⇒ Object



281
282
283
284
285
# File 'lib/aptible/resource/base.rb', line 281

def update(params)
  update!(params)
rescue HyperResource::ResponseError
  false
end

#update!(params) ⇒ Object

rubocop:enable Style/Alias



274
275
276
277
278
279
# File 'lib/aptible/resource/base.rb', line 274

def update!(params)
  _hyperresource_update(self.class.normalize_params(params))
rescue HyperResource::ResponseError => e
  self.errors = Errors.from_exception(e)
  raise e
end