Class: JSONAPI::Resource

Inherits:
Object
  • Object
show all
Includes:
Callbacks, ResourceFor
Defined in:
lib/jsonapi/resource.rb

Constant Summary collapse

@@resource_types =
{}

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Callbacks

included

Methods included from ResourceFor

included

Constructor Details

#initialize(model, context = nil) ⇒ Resource

Returns a new instance of Resource.



28
29
30
31
# File 'lib/jsonapi/resource.rb', line 28

def initialize(model, context = nil)
  @model = model
  @context = context
end

Class Attribute Details

._allowed_filtersObject

Returns the value of attribute _allowed_filters.



209
210
211
# File 'lib/jsonapi/resource.rb', line 209

def _allowed_filters
  @_allowed_filters
end

._associationsObject

Returns the value of attribute _associations.



209
210
211
# File 'lib/jsonapi/resource.rb', line 209

def _associations
  @_associations
end

._attributesObject

Returns the value of attribute _attributes.



209
210
211
# File 'lib/jsonapi/resource.rb', line 209

def _attributes
  @_attributes
end

._paginatorObject

Returns the value of attribute _paginator.



209
210
211
# File 'lib/jsonapi/resource.rb', line 209

def _paginator
  @_paginator
end

._typeObject

Returns the value of attribute _type.



209
210
211
# File 'lib/jsonapi/resource.rb', line 209

def _type
  @_type
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



13
14
15
# File 'lib/jsonapi/resource.rb', line 13

def context
  @context
end

#modelObject (readonly)

Returns the value of attribute model.



14
15
16
# File 'lib/jsonapi/resource.rb', line 14

def model
  @model
end

Class Method Details

._allowed_filter?(filter) ⇒ Boolean

:nocov:

Returns:

  • (Boolean)


504
505
506
# File 'lib/jsonapi/resource.rb', line 504

def _allowed_filter?(filter)
  _allowed_filters.include?(filter)
end

._as_parent_keyObject



467
468
469
# File 'lib/jsonapi/resource.rb', line 467

def _as_parent_key
  @_as_parent_key ||= "#{_type.to_s.singularize}_#{_primary_key}"
end

._association(type) ⇒ Object



447
448
449
450
# File 'lib/jsonapi/resource.rb', line 447

def _association(type)
  type = type.to_sym
  @_associations[type]
end

._attribute_options(attr) ⇒ Object

quasi private class methods



427
428
429
# File 'lib/jsonapi/resource.rb', line 427

def _attribute_options(attr)
  default_attribute_options.merge(@_attributes[attr])
end

._has_association?(type) ⇒ Boolean

Returns:

  • (Boolean)


442
443
444
445
# File 'lib/jsonapi/resource.rb', line 442

def _has_association?(type)
  type = type.to_s
  @_associations.has_key?(type.singularize.to_sym) || @_associations.has_key?(type.pluralize.to_sym)
end

._keyObject



456
457
458
459
460
461
# File 'lib/jsonapi/resource.rb', line 456

def _key
  # :nocov:
  warn '[DEPRECATION] `_key` is deprecated.  Please use `_primary_key` instead.'
  _primary_key
  # :nocov:
end

._model_nameObject



452
453
454
# File 'lib/jsonapi/resource.rb', line 452

def _model_name
  @_model_name ||= self.name.demodulize.sub(/Resource$/, '')
end

._primary_keyObject



463
464
465
# File 'lib/jsonapi/resource.rb', line 463

def _primary_key
  @_primary_key ||= :id
end

._resource_name_from_type(type) ⇒ Object



475
476
477
478
479
480
481
482
# File 'lib/jsonapi/resource.rb', line 475

def _resource_name_from_type(type)
  class_name = @@resource_types[type]
  if class_name.nil?
    class_name = type.to_s.singularize.camelize + 'Resource'
    @@resource_types[type] = class_name
  end
  return class_name
end

._updateable_associationsObject



431
432
433
434
435
436
437
438
439
440
# File 'lib/jsonapi/resource.rb', line 431

def _updateable_associations
  associations = []

  @_associations.each do |key, association|
    if association.is_a?(JSONAPI::Association::HasOne) || association.acts_as_set
      associations.push(key)
    end
  end
  associations
end

.apply_filter(records, filter, value) ⇒ Object



313
314
315
# File 'lib/jsonapi/resource.rb', line 313

def apply_filter(records, filter, value)
  records.where(filter => value)
end

.apply_filters(records, filters) ⇒ Object



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/jsonapi/resource.rb', line 317

def apply_filters(records, filters)
  required_includes = []
  filters.each do |filter, value|
    if _associations.include?(filter)
      if _associations[filter].is_a?(JSONAPI::Association::HasMany)
        required_includes.push(filter)
        records = apply_filter(records, "#{filter}.#{_associations[filter].primary_key}", value)
      else
        records = apply_filter(records, "#{_associations[filter].foreign_key}", value)
      end
    else
      records = apply_filter(records, filter, value)
    end
  end
  records.includes(required_includes)
end

.apply_pagination(records, paginator) ⇒ Object



302
303
304
305
306
307
# File 'lib/jsonapi/resource.rb', line 302

def apply_pagination(records, paginator)
  if paginator
    records = paginator.apply(records)
  end
  records
end

.apply_sort(records, order_options) ⇒ Object



309
310
311
# File 'lib/jsonapi/resource.rb', line 309

def apply_sort(records, order_options)
  records.order(order_options)
end

.attribute(attr, options = {}) ⇒ Object



234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/jsonapi/resource.rb', line 234

def attribute(attr, options = {})
  check_reserved_attribute_name(attr)

  @_attributes ||= {}
  @_attributes[attr] = options
  define_method attr do
    @model.send(attr)
  end unless method_defined?(attr)

  define_method "#{attr}=" do |value|
    @model.send "#{attr}=", value
  end unless method_defined?("#{attr}=")
end

.attributes(*attrs) ⇒ Object

Methods used in defining a resource class



228
229
230
231
232
# File 'lib/jsonapi/resource.rb', line 228

def attributes(*attrs)
  attrs.each do |attr|
    attribute(attr)
  end
end

.construct_order_options(sort_params) ⇒ Object



512
513
514
515
516
# File 'lib/jsonapi/resource.rb', line 512

def construct_order_options(sort_params)
  sort_params.each_with_object({}) { |sort, order_hash|
    order_hash[sort[:field]] = sort[:direction]
  }
end

.create(context) ⇒ Object



211
212
213
# File 'lib/jsonapi/resource.rb', line 211

def create(context)
  self.new(self.create_model, context)
end

.create_modelObject



215
216
217
# File 'lib/jsonapi/resource.rb', line 215

def create_model
  _model_class.new
end

.createable_fields(context = nil) ⇒ Object

Override in your resource to filter the createable keys



289
290
291
# File 'lib/jsonapi/resource.rb', line 289

def createable_fields(context = nil)
  _updateable_associations | _attributes.keys
end

.default_attribute_optionsObject



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

def default_attribute_options
  {format: :default}
end

.fieldsObject



298
299
300
# File 'lib/jsonapi/resource.rb', line 298

def fields
  _associations.keys | _attributes.keys
end

.filter(attr) ⇒ Object



268
269
270
# File 'lib/jsonapi/resource.rb', line 268

def filter(attr)
  @_allowed_filters.add(attr.to_sym)
end

.filters(*attrs) ⇒ Object



264
265
266
# File 'lib/jsonapi/resource.rb', line 264

def filters(*attrs)
  @_allowed_filters.merge(attrs)
end

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

Override this method if you have more complex requirements than this basic find method provides



335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/jsonapi/resource.rb', line 335

def find(filters, options = {})
  context = options[:context]
  sort_criteria = options.fetch(:sort_criteria) { [] }

  resources = []

  records = records(options)
  records = apply_filters(records, filters)
  records = apply_sort(records, construct_order_options(sort_criteria))
  records = apply_pagination(records, options[:paginator])

  records.each do |model|
    resources.push self.new(model, context)
  end

  return resources
end

.find_by_key(key, options = {}) ⇒ Object



353
354
355
356
357
358
359
360
# File 'lib/jsonapi/resource.rb', line 353

def find_by_key(key, options = {})
  context = options[:context]
  model = records(options).where({_primary_key => key}).first
  if model.nil?
    raise JSONAPI::Exceptions::RecordNotFound.new(key)
  end
  self.new(model, context)
end

.find_by_keys(keys, options = {}) ⇒ Object



362
363
364
365
366
367
368
369
370
371
372
# File 'lib/jsonapi/resource.rb', line 362

def find_by_keys(keys, options = {})
  context = options[:context]
  _models = records(options).where({_primary_key => keys})

  unless _models.length == keys.length
    key = (keys - _models.pluck(_primary_key).map(&:to_s)).first
    raise JSONAPI::Exceptions::RecordNotFound.new(key)
  end

  _models.map { |model| self.new(model, context) }
end

.has_many(*attrs) ⇒ Object



256
257
258
# File 'lib/jsonapi/resource.rb', line 256

def has_many(*attrs)
  _associate(Association::HasMany, *attrs)
end

.has_one(*attrs) ⇒ Object



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

def has_one(*attrs)
  _associate(Association::HasOne, *attrs)
end

.inherited(base) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/jsonapi/resource.rb', line 191

def inherited(base)
  base._attributes = (_attributes || {}).dup
  base._associations = (_associations || {}).dup
  base._allowed_filters = (_allowed_filters || Set.new).dup

  type = base.name.demodulize.sub(/Resource$/, '').underscore
  base._type = type.pluralize.to_sym

  base.attribute :id, format: :id

  check_reserved_resource_name(base._type, base.name)

  # If eager loading is on this is how all the resource types are setup
  # If eager loading is off some resource types will be initialized in
  # _resource_name_from_type
  @@resource_types[base._type] ||= base.name.demodulize
end

.is_filter_association?(filter) ⇒ Boolean

Returns:

  • (Boolean)


389
390
391
# File 'lib/jsonapi/resource.rb', line 389

def is_filter_association?(filter)
  filter == _type || _associations.include?(filter)
end

.key(key) ⇒ Object



272
273
274
275
276
277
# File 'lib/jsonapi/resource.rb', line 272

def key(key)
  # :nocov:
  warn '[DEPRECATION] `key` is deprecated.  Please use `primary_key` instead.'
  @_primary_key = key.to_sym
  # :nocov:
end

.model_name(model) ⇒ Object



260
261
262
# File 'lib/jsonapi/resource.rb', line 260

def model_name(model)
  @_model_name = model.to_sym
end

.module_pathObject



508
509
510
# File 'lib/jsonapi/resource.rb', line 508

def module_path
  @module_path ||= self.name =~ /::[^:]+\Z/ ? ($`.freeze.gsub('::', '/') + '/').downcase : ''
end

.paginator(paginator) ⇒ Object



488
489
490
# File 'lib/jsonapi/resource.rb', line 488

def paginator(paginator)
  @_paginator = paginator
end

.primary_key(key) ⇒ Object



279
280
281
# File 'lib/jsonapi/resource.rb', line 279

def primary_key(key)
  @_primary_key = key.to_sym
end

.records(options = {}) ⇒ Object

Override this method if you want to customize the relation for finder methods (find, find_by_key, find_by_keys)



376
377
378
# File 'lib/jsonapi/resource.rb', line 376

def records(options = {})
  _model_class
end

.routing_options(options) ⇒ Object



219
220
221
# File 'lib/jsonapi/resource.rb', line 219

def routing_options(options)
  @_routing_resource_options = options
end

.routing_resource_optionsObject



223
224
225
# File 'lib/jsonapi/resource.rb', line 223

def routing_resource_options
  @_routing_resource_options ||= {}
end

.sortable_fields(context = nil) ⇒ Object

Override in your resource to filter the sortable keys



294
295
296
# File 'lib/jsonapi/resource.rb', line 294

def sortable_fields(context = nil)
  _attributes.keys
end

.updateable_fields(context = nil) ⇒ Object

Override in your resource to filter the updateable keys



284
285
286
# File 'lib/jsonapi/resource.rb', line 284

def updateable_fields(context = nil)
  _updateable_associations | _attributes.keys - [_primary_key]
end

.verify_association_filter(filter, raw, context = nil) ⇒ Object

override to allow for custom association logic, such as uuids, multiple keys or permission checks on keys



422
423
424
# File 'lib/jsonapi/resource.rb', line 422

def verify_association_filter(filter, raw, context = nil)
  return filter, raw
end

.verify_custom_filter(filter, value, context = nil) ⇒ Object

override to allow for custom filters



417
418
419
# File 'lib/jsonapi/resource.rb', line 417

def verify_custom_filter(filter, value, context = nil)
  return filter, value
end

.verify_filter(filter, raw, context = nil) ⇒ Object



393
394
395
396
397
398
399
400
401
402
# File 'lib/jsonapi/resource.rb', line 393

def verify_filter(filter, raw, context = nil)
  filter_values = []
  filter_values += CSV.parse_line(raw) unless raw.nil? || raw.empty?

  if is_filter_association?(filter)
    verify_association_filter(filter, filter_values, context)
  else
    verify_custom_filter(filter, filter_values, context)
  end
end

.verify_filters(filters, context = nil) ⇒ Object



380
381
382
383
384
385
386
387
# File 'lib/jsonapi/resource.rb', line 380

def verify_filters(filters, context = nil)
  verified_filters = {}
  filters.each do |filter, raw_value|
    verified_filter = verify_filter(filter, raw_value, context)
    verified_filters[verified_filter[0]] = verified_filter[1]
  end
  verified_filters
end

.verify_key(key, context = nil) ⇒ Object

override to allow for key processing and checking



405
406
407
# File 'lib/jsonapi/resource.rb', line 405

def verify_key(key, context = nil)
  return key
end

.verify_keys(keys, context = nil) ⇒ Object

override to allow for key processing and checking



410
411
412
413
414
# File 'lib/jsonapi/resource.rb', line 410

def verify_keys(keys, context = nil)
  return keys.collect do |key|
    verify_key(key, context)
  end
end

Instance Method Details

#_model_classObject



494
495
496
# File 'lib/jsonapi/resource.rb', line 494

def _model_class
  @model ||= Object.const_get(_model_name.to_s)
end

#change(callback) ⇒ Object



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

def change(callback)
  if @changing
    run_callbacks callback do
      yield
    end
  else
    run_callbacks is_new? ? :create : :update do
      @changing = true
      run_callbacks callback do
        yield
        save if @save_needed
      end
    end
  end
end


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

def create_has_many_links(association_type, association_key_values)
  change :create_has_many_link do
    _create_has_many_links(association_type, association_key_values)
  end
end

#fetchable_fieldsObject

Override this on a resource instance to override the fetchable keys



100
101
102
# File 'lib/jsonapi/resource.rb', line 100

def fetchable_fields
  self.class.fields
end

#idObject



33
34
35
# File 'lib/jsonapi/resource.rb', line 33

def id
  model.send(self.class._primary_key)
end

#is_new?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/jsonapi/resource.rb', line 37

def is_new?
  id.nil?
end

#removeObject



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

def remove
  run_callbacks :remove do
    _remove
  end
end


81
82
83
84
85
# File 'lib/jsonapi/resource.rb', line 81

def remove_has_many_link(association_type, key)
  change :remove_has_many_link do
    _remove_has_many_link(association_type, key)
  end
end


87
88
89
90
91
# File 'lib/jsonapi/resource.rb', line 87

def remove_has_one_link(association_type)
  change :remove_has_one_link do
    _remove_has_one_link(association_type)
  end
end

#replace_fields(field_data) ⇒ Object



93
94
95
96
97
# File 'lib/jsonapi/resource.rb', line 93

def replace_fields(field_data)
  change :replace_fields do
    _replace_fields(field_data)
  end
end


69
70
71
72
73
# File 'lib/jsonapi/resource.rb', line 69

def replace_has_many_links(association_type, association_key_values)
  change :replace_has_many_links do
    _replace_has_many_links(association_type, association_key_values)
  end
end


75
76
77
78
79
# File 'lib/jsonapi/resource.rb', line 75

def replace_has_one_link(association_type, association_key_value)
  change :replace_has_one_link do
    _replace_has_one_link(association_type, association_key_value)
  end
end