Class: Fera::Base

Inherits:
ActiveResource::Base
  • Object
show all
Defined in:
lib/fera/models/base.rb

Direct Known Subclasses

Customer, Media, Order, Product, Rating, Review, Store, Submission, Webhook

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes = nil, persisted = nil, options = {}) ⇒ Base

Returns a new instance of Base.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/fera/models/base.rb', line 143

def initialize(attributes = nil, persisted = nil, options = {})
  @options = options.to_h

  dynamic_attributes = attributes.to_h.dup

  association_keys = self.class.has_manys.keys + self.class.has_ones.keys + self.class.belongs_tos.keys

  dynamic_attributes.except!(*(association_keys + association_keys.map(&:to_s)))

  super(dynamic_attributes, persisted)

  return unless attributes.present?

  association_keys.each do |name, _opts|
    if attributes.key?(name.to_s) || attributes.key?(name.to_sym)
      val = attributes.to_h[name.to_s] || attributes.to_h[name.to_sym]
      self.send("#{ name }=", val) if respond_to?("#{ name }=")
    end
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object

Method missing adapters to define is_* methods for boolean attributes



337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/fera/models/base.rb', line 337

def method_missing(method_name, *args, &block)
  matcher = method_name.to_s.match(/^(?!is_)([a-z_]+)\?$/) || method_name.to_s.match(/^is_([a-z_]+)\?$/)
  if matcher.present?
    attribute_name = matcher[1]
    return super if attribute_name.blank?

    attribute_name = "is_#{ attribute_name }" unless attribute_name =~ /^is_/
    return super unless known_attribute?(attribute_name.to_s)

    return !!send(attribute_name.to_sym).presence
  end

  super
end

Class Attribute Details

.default_params=(value) ⇒ Object (writeonly)

Sets the attribute default_params

Parameters:

  • value

    the value to set the attribute default_params to.



85
86
87
# File 'lib/fera/models/base.rb', line 85

def default_params=(value)
  @default_params = value
end

Instance Attribute Details

#last_responseObject (readonly)

Returns the value of attribute last_response.



5
6
7
# File 'lib/fera/models/base.rb', line 5

def last_response
  @last_response
end

#last_response_bodyObject (readonly)

Returns the value of attribute last_response_body.



5
6
7
# File 'lib/fera/models/base.rb', line 5

def last_response_body
  @last_response_body
end

#last_response_exceptionObject (readonly)

Returns the value of attribute last_response_exception.



5
6
7
# File 'lib/fera/models/base.rb', line 5

def last_response_exception
  @last_response_exception
end

#last_response_messageObject (readonly)

Returns the value of attribute last_response_message.



5
6
7
# File 'lib/fera/models/base.rb', line 5

def last_response_message
  @last_response_message
end

#optionsObject (readonly)

Returns the value of attribute options.



5
6
7
# File 'lib/fera/models/base.rb', line 5

def options
  @options
end

Class Method Details

.api_keyObject



41
42
43
# File 'lib/fera/models/base.rb', line 41

def api_key
  self.headers['Secret-Key'] || self.headers['Public-Key'] || self.headers['Authorization'].to_s.split.last.presence
end

.api_key=(api_key) ⇒ Object

Sets the header API key for subsequent requests

Parameters:

  • api_key (String)

    Secret Key, Public Key or OAuth Access Token.



14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/fera/models/base.rb', line 14

def api_key=(api_key)
  if api_key.blank?
    self.headers.delete('Secret-Key')
    self.headers.delete('Public-Key')
    self.headers.delete('Authorization')
  elsif api_key =~ /^sk_/
    self.headers['Secret-Key'] = api_key
  elsif api_key =~ /^pk_/
    self.headers['Public-Key'] = api_key
  else
    self.headers['Authorization'] = "Bearer #{ api_key }"
  end
end

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



57
58
59
# File 'lib/fera/models/base.rb', line 57

def belongs_to(name, options = {})
  @belongs_tos = @belongs_tos.to_h.merge(name => options)
end

.belongs_tosObject



61
# File 'lib/fera/models/base.rb', line 61

def belongs_tos; @belongs_tos.to_h; end

.create(attributes = {}, extra_params = {}) ⇒ Object



89
90
91
# File 'lib/fera/models/base.rb', line 89

def create(attributes = {}, extra_params = {})
  self.new(attributes, false).tap { |resource| resource.create(extra_params) }
end

.create!(attributes = {}, extra_params = {}) ⇒ Object



95
96
97
# File 'lib/fera/models/base.rb', line 95

def create!(attributes = {}, extra_params = {})
  self.new(attributes, false).tap { |resource| resource.create!(extra_params) }
end

.find_every(options) ⇒ Object



101
102
103
# File 'lib/fera/models/base.rb', line 101

def find_every(options)
  super(add_default_params(options))
end

.find_one(options) ⇒ Object



107
108
109
# File 'lib/fera/models/base.rb', line 107

def find_one(options)
  super(add_default_params(options))
end

.find_single(scope, options) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
# File 'lib/fera/models/base.rb', line 113

def find_single(scope, options)
  options = add_default_params(options)
  prefix_options, query_options = split_options(options[:params])
  path = element_path(scope, prefix_options, query_options)

  response = connection.get(path, headers)
  record = instantiate_record(format.decode(response.body), prefix_options)

  record.set_last_response(response)
  record
end

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



63
64
65
# File 'lib/fera/models/base.rb', line 63

def has_many(name, options = {})
  @has_manys = @has_manys.to_h.merge(name => options)
end

.has_manysObject



67
# File 'lib/fera/models/base.rb', line 67

def has_manys; @has_manys.to_h; end

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



69
70
71
# File 'lib/fera/models/base.rb', line 69

def has_one(name, options = {})
  @has_ones = @has_ones.to_h.merge(name => options)
end

.has_onesObject



73
# File 'lib/fera/models/base.rb', line 73

def has_ones; @has_ones.to_h; end

.headersObject



75
76
77
78
79
80
81
82
83
# File 'lib/fera/models/base.rb', line 75

def headers
  if _headers_defined?
    _headers
  elsif superclass != Object && superclass.headers
    superclass.headers
  else
    _headers ||= {} # rubocop:disable Lint/UnderscorePrefixedVariableName
  end
end

.headers=(new_headers) ⇒ Object

Sets all the headers for subsequent requests

Parameters:

  • new_headers (Hash)

    Hash of new headers to set



31
32
33
34
35
36
37
38
39
# File 'lib/fera/models/base.rb', line 31

def headers=(new_headers)
  new_headers.to_h.each do |key, value|
    self.headers[key] = value
  end

  self.headers.to_h.each do |key, _|
    self.headers.delete(key) unless new_headers.key?(key)
  end
end

.new_element_path(prefix_options = {}, extra_params = {}) ⇒ Object



125
126
127
128
129
# File 'lib/fera/models/base.rb', line 125

def new_element_path(prefix_options = {}, extra_params = {})
  url = "#{ prefix(prefix_options) }#{ collection_name }/new#{ format_extension }"
  url += "?#{ extra_params.to_param }" if extra_params.present?
  url
end

.order(sorts) ⇒ Object

Note:

This only works from the root, not with scoped results (‘Fera::Review.order(created_at: :asc)`)

Returns sorted results.

Parameters:

  • sorts (Hash, String, Symbol)


49
50
51
52
53
54
55
# File 'lib/fera/models/base.rb', line 49

def order(sorts)
  sorts = { sorts.to_s => :desc.to_s } if sorts.is_a?(String) || sorts.is_a?(Symbol)
  sort_by = sorts.map do |column, direction|
    "#{ column }:#{ direction.presence || :desc }"
  end.join(",")
  where(sort_by: sort_by)
end

Instance Method Details

#clone_selected_fields(model, fields) ⇒ Object



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/fera/models/base.rb', line 315

def clone_selected_fields(model, fields)
  fields = fields.is_a?(Array) ? fields : fields.to_s.split(',').map(&:strip)

  # find fields
  changed_attributes = HashWithIndifferentAccess.new
  changed_attributes[model.class.primary_key] = model.attributes[model.class.primary_key]
  fields.each do |key|
    if key.include?(':')
      clone_sub_fields(model, key, changed_attributes)
    elsif fields.include?(key)
      changed_attributes[key] = model.attributes[key]
    end
  end

  # create new object
  self.class.new(changed_attributes, true)
end

#clone_with_nilObject



303
304
305
306
307
308
309
310
311
312
313
# File 'lib/fera/models/base.rb', line 303

def clone_with_nil
  # Clone all attributes except the pk and any nested ARes
  cloned = attributes.reject { |k, v| k == self.class.primary_key || v.is_a?(ActiveResource::Base) }.map { |k, v| [k, v.clone] }.to_h
  # Form the new resource - bypass initialize of resource with 'new' as that will call 'load' which
  # attempts to convert hashes into member objects and arrays into collections of objects. We want
  # the raw objects to be cloned so we bypass load by directly setting the attributes hash.
  resource = self.class.new({}, true, { cloned: true })
  resource.prefix_options = prefix_options
  resource.send :instance_variable_set, '@attributes', cloned
  resource
end

#create(extra_params = {}, raise: false) ⇒ Object



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/fera/models/base.rb', line 228

def create(extra_params = {}, raise: false)
  run_callbacks :create do
    data = as_json
    self.class.belongs_tos.merge(self.class.has_ones).each do |name, _opts|
      next unless instance_variable_defined?(:"@#{ name }")

      nested_resource = self.send(name)
      if nested_resource.present? && !nested_resource.persisted?
        nested_resource.validate!
        data[name] = nested_resource.as_json
      end
    end

    self.class.has_manys.each do |name, _opts|
      next unless instance_variable_defined?(:"@#{ name }")

      nested_resource = self.send(name)

      next if nested_resource.nil?

      nested_resource.each do |nested_resource_instance|
        next if nested_resource_instance.persisted?

        nested_resource_instance.validate!

        data[name] ||= []
        data[name] << nested_resource_instance.as_json
      end
    end

    connection.post(collection_path(nil, extra_params), { data: data }.to_json, self.class.headers).tap do |response|
      self.id = id_from_response(response)
      load_attributes_from_response(response)
    end
  end

  true
rescue ActiveResource::ConnectionError => e
  set_last_response(e)

  if raise
    raise(ActiveResource::ResourceInvalid.new(last_response, last_response_message.presence))
  end

  false
end

#create!(extra_params = {}) ⇒ Object



275
276
277
# File 'lib/fera/models/base.rb', line 275

def create!(extra_params = {})
  create(extra_params, raise: true)
end

#created_at=(new_created_at) ⇒ Object



182
183
184
185
186
187
188
# File 'lib/fera/models/base.rb', line 182

def created_at=(new_created_at)
  if new_created_at.is_a?(String)
    super(Time.parse(new_created_at))
  else
    super
  end
end

#destroy!Object



178
179
180
# File 'lib/fera/models/base.rb', line 178

def destroy!
  destroy
end

#known_attribute?(attribute_name) ⇒ Boolean

Returns:

  • (Boolean)


365
366
367
# File 'lib/fera/models/base.rb', line 365

def known_attribute?(attribute_name)
  known_attributes.map(&:to_s).include?(attribute_name.to_s)
end

#load(attributes, *args) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/fera/models/base.rb', line 164

def load(attributes, *args)
  load_result = super(attributes, *args)

  attributes.each do |attr, val|
    if respond_to?("#{ attr }=".to_sym)
      self.send("#{ attr }=".to_sym, val)
    end
  end

  @clean_copy = clone_with_nil if persisted? && !options[:cloned]

  load_result
end

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

Returns:

  • (Boolean)


352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/fera/models/base.rb', line 352

def respond_to_missing?(method_name, include_private = false)
  matcher = method_name.to_s.match(/^(?!is_)([a-z_]+)\?$/) || method_name.to_s.match(/^is_([a-z_]+)\?$/)
  if matcher.present?
    attribute_name = matcher[1]
    return super if attribute_name.blank?

    attribute_name = "is_#{ attribute_name }" unless attribute_name =~ /^is_/
    return true if known_attribute?(attribute_name)
  end

  super
end

#save(extra_params = {}, raise: false) ⇒ Object



279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/fera/models/base.rb', line 279

def save(extra_params = {}, raise: false)
  run_callbacks :save do
    if new?
      create(extra_params, raise: raise) # We'll raise the error below
    else
      # find changes
      changed_attributes = attributes.filter { |key, value| !@clean_copy.attributes.key?(key) || (@clean_copy.attributes[key] != value) || (key == self.class.primary_key) }
      changed_attributes.reject! { |k| k == 'id' }
      return false unless changed_attributes.keys.any?

      # save
      update(changed_attributes, extra_params, raise: raise)
    end

    @clean_copy = clone_with_nil # Clear changes

    self
  end
end

#save!(extra_params = {}) ⇒ Object



299
300
301
# File 'lib/fera/models/base.rb', line 299

def save!(extra_params = {})
  save(extra_params, raise: true)
end

#set_last_response(result) ⇒ Object

rubocop:disable Naming/AccessorMethodName



369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/fera/models/base.rb', line 369

def set_last_response(result) # rubocop:disable Naming/AccessorMethodName
  response = if result.is_a?(StandardError)
               @last_response_exception = result
               @last_response_exception.response
             else
               @last_response_exception = nil
               result
             end

  @last_response = response
  @last_response_body = response.body.present? ? self.class.format.decode(response.body) : nil
  @last_response_message = last_response_body.to_h['message']
end

#update(changed_attributes, extra_params = {}, raise: false) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/fera/models/base.rb', line 198

def update(changed_attributes, extra_params = {}, raise: false)
  run_callbacks(:update) do
    connection.put(element_path(prefix_options, extra_params), changed_attributes.to_json, self.class.headers).tap do |response|
      load_attributes_from_response(response)
    end

    load(changed_attributes)
  end

  true
rescue ActiveResource::ConnectionError => e
  set_last_response(e)

  if raise
    raise(ActiveResource::ResourceInvalid.new(last_response, last_response_message.presence))
  end

  false
end

#update!(changed_attributes, extra_params = {}) ⇒ Object



218
219
220
# File 'lib/fera/models/base.rb', line 218

def update!(changed_attributes, extra_params = {})
  update(changed_attributes, extra_params, raise: true)
end

#updated_at=(new_updated_at) ⇒ Object



190
191
192
193
194
195
196
# File 'lib/fera/models/base.rb', line 190

def updated_at=(new_updated_at)
  if new_updated_at.is_a?(String)
    super(Time.parse(new_updated_at))
  else
    super
  end
end

#valid?(_context = nil) ⇒ Boolean

Returns:

  • (Boolean)


222
223
224
# File 'lib/fera/models/base.rb', line 222

def valid?(_context = nil)
  super()
end