Class: JSONAPI::Resource

Inherits:
Object
  • Object
show all
Includes:
Callbacks
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

Constructor Details

#initialize(model, context = nil) ⇒ Resource

Returns a new instance of Resource.



26
27
28
29
# File 'lib/jsonapi/resource.rb', line 26

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

Class Attribute Details

._allowed_filtersObject

Returns the value of attribute _allowed_filters.



217
218
219
# File 'lib/jsonapi/resource.rb', line 217

def _allowed_filters
  @_allowed_filters
end

._associationsObject

Returns the value of attribute _associations.



217
218
219
# File 'lib/jsonapi/resource.rb', line 217

def _associations
  @_associations
end

._attributesObject

Returns the value of attribute _attributes.



217
218
219
# File 'lib/jsonapi/resource.rb', line 217

def _attributes
  @_attributes
end

._paginatorObject

Returns the value of attribute _paginator.



217
218
219
# File 'lib/jsonapi/resource.rb', line 217

def _paginator
  @_paginator
end

._typeObject

Returns the value of attribute _type.



217
218
219
# File 'lib/jsonapi/resource.rb', line 217

def _type
  @_type
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



11
12
13
# File 'lib/jsonapi/resource.rb', line 11

def context
  @context
end

#modelObject (readonly)

Returns the value of attribute model.



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

def model
  @model
end

Class Method Details

._allowed_filter?(filter) ⇒ Boolean

Returns:

  • (Boolean)


478
479
480
# File 'lib/jsonapi/resource.rb', line 478

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

._as_parent_keyObject



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

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

._association(type) ⇒ Object



436
437
438
439
# File 'lib/jsonapi/resource.rb', line 436

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

._attribute_options(attr) ⇒ Object

quasi private class methods



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

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

._has_association?(type) ⇒ Boolean

Returns:

  • (Boolean)


431
432
433
434
# File 'lib/jsonapi/resource.rb', line 431

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

._model_classObject



474
475
476
# File 'lib/jsonapi/resource.rb', line 474

def _model_class
  @model ||= _model_name.to_s.safe_constantize
end

._model_nameObject



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

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

._primary_keyObject



445
446
447
# File 'lib/jsonapi/resource.rb', line 445

def _primary_key
  @_primary_key ||= :id
end

._resource_name_from_type(type) ⇒ Object



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

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



422
423
424
425
426
427
428
429
# File 'lib/jsonapi/resource.rb', line 422

def _updateable_associations
  associations = []

  @_associations.each do |key, association|
    associations.push(key)
  end
  associations
end

.apply_filter(records, filter, value) ⇒ Object



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

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

.apply_filters(records, filters) ⇒ Object



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

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



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

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

.apply_sort(records, order_options) ⇒ Object



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

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

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



242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/jsonapi/resource.rb', line 242

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



236
237
238
239
240
# File 'lib/jsonapi/resource.rb', line 236

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

.construct_order_options(sort_params) ⇒ Object



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

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

.create(context) ⇒ Object



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

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

.create_modelObject



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

def create_model
  _model_class.new
end

.createable_fields(context = nil) ⇒ Object

Override in your resource to filter the createable keys



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

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

.default_attribute_optionsObject



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

def default_attribute_options
  {format: :default}
end

.fieldsObject



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

def fields
  _associations.keys | _attributes.keys
end

.filter(attr) ⇒ Object



276
277
278
# File 'lib/jsonapi/resource.rb', line 276

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

.filters(*attrs) ⇒ Object



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

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



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

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



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

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

.has_many(*attrs) ⇒ Object



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

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

.has_one(*attrs) ⇒ Object



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

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

.inherited(base) ⇒ Object



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

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)
end

.is_filter_association?(filter) ⇒ Boolean

Returns:

  • (Boolean)


378
379
380
# File 'lib/jsonapi/resource.rb', line 378

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

.model_name(model) ⇒ Object



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

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

.module_pathObject



482
483
484
# File 'lib/jsonapi/resource.rb', line 482

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

.paginator(paginator) ⇒ Object



470
471
472
# File 'lib/jsonapi/resource.rb', line 470

def paginator(paginator)
  @_paginator = paginator
end

.primary_key(key) ⇒ Object



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

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)



365
366
367
# File 'lib/jsonapi/resource.rb', line 365

def records(options = {})
  _model_class
end

.resource_for(type) ⇒ Object



208
209
210
211
212
213
214
215
# File 'lib/jsonapi/resource.rb', line 208

def resource_for(type)
  resource_name = JSONAPI::Resource._resource_name_from_type(type)
  resource = resource_name.safe_constantize if resource_name
  if resource.nil?
    raise NameError, "JSONAPI: Could not find resource '#{type}'. (Class #{resource_name} not found)"
  end
  resource
end

.routing_options(options) ⇒ Object



227
228
229
# File 'lib/jsonapi/resource.rb', line 227

def routing_options(options)
  @_routing_resource_options = options
end

.routing_resource_optionsObject



231
232
233
# File 'lib/jsonapi/resource.rb', line 231

def routing_resource_options
  @_routing_resource_options ||= {}
end

.sortable_fields(context = nil) ⇒ Object

Override in your resource to filter the sortable keys



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

def sortable_fields(context = nil)
  _attributes.keys
end

.updateable_fields(context = nil) ⇒ Object

Override in your resource to filter the updateable keys



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

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



413
414
415
# File 'lib/jsonapi/resource.rb', line 413

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



408
409
410
# File 'lib/jsonapi/resource.rb', line 408

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

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



382
383
384
385
386
387
388
389
390
391
# File 'lib/jsonapi/resource.rb', line 382

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



369
370
371
372
373
374
375
376
# File 'lib/jsonapi/resource.rb', line 369

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



394
395
396
397
398
# File 'lib/jsonapi/resource.rb', line 394

def verify_key(key, context = nil)
  key && Integer(key)
rescue
  raise JSONAPI::Exceptions::InvalidFieldValue.new(_primary_key, key)
end

.verify_keys(keys, context = nil) ⇒ Object

override to allow for key processing and checking



401
402
403
404
405
# File 'lib/jsonapi/resource.rb', line 401

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

Instance Method Details

#change(callback) ⇒ Object



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

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


61
62
63
64
65
# File 'lib/jsonapi/resource.rb', line 61

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



98
99
100
# File 'lib/jsonapi/resource.rb', line 98

def fetchable_fields
  self.class.fields
end

#idObject



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

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

#is_new?Boolean

Returns:

  • (Boolean)


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

def is_new?
  id.nil?
end

#records_for(association_name, options = {}) ⇒ Object

Override this on a resource to customize how the associated records are fetched for a model. Particularly helpful for authoriztion.



104
105
106
# File 'lib/jsonapi/resource.rb', line 104

def records_for(association_name, options = {})
  model.send association_name
end

#removeObject



55
56
57
58
59
# File 'lib/jsonapi/resource.rb', line 55

def remove
  run_callbacks :remove do
    _remove
  end
end


79
80
81
82
83
# File 'lib/jsonapi/resource.rb', line 79

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


85
86
87
88
89
# File 'lib/jsonapi/resource.rb', line 85

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



91
92
93
94
95
# File 'lib/jsonapi/resource.rb', line 91

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


67
68
69
70
71
# File 'lib/jsonapi/resource.rb', line 67

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


73
74
75
76
77
# File 'lib/jsonapi/resource.rb', line 73

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