jsonapi-resources 0.4.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,13 +13,13 @@ module JSONAPI
13
13
  :update,
14
14
  :remove,
15
15
  :save,
16
- :create_has_many_link,
17
- :replace_has_many_links,
18
- :create_has_one_link,
19
- :replace_has_one_link,
20
- :replace_polymorphic_has_one_link,
21
- :remove_has_many_link,
22
- :remove_has_one_link,
16
+ :create_to_many_link,
17
+ :replace_to_many_links,
18
+ :create_to_one_link,
19
+ :replace_to_one_link,
20
+ :replace_polymorphic_to_one_link,
21
+ :remove_to_many_link,
22
+ :remove_to_one_link,
23
23
  :replace_fields
24
24
 
25
25
  def initialize(model, context = nil)
@@ -28,7 +28,7 @@ module JSONAPI
28
28
  end
29
29
 
30
30
  def id
31
- model.send(self.class._primary_key)
31
+ model.public_send(self.class._primary_key)
32
32
  end
33
33
 
34
34
  def is_new?
@@ -62,39 +62,39 @@ module JSONAPI
62
62
  end
63
63
  end
64
64
 
65
- def create_has_many_links(association_type, association_key_values)
66
- change :create_has_many_link do
67
- _create_has_many_links(association_type, association_key_values)
65
+ def create_to_many_links(relationship_type, relationship_key_values)
66
+ change :create_to_many_link do
67
+ _create_to_many_links(relationship_type, relationship_key_values)
68
68
  end
69
69
  end
70
70
 
71
- def replace_has_many_links(association_type, association_key_values)
72
- change :replace_has_many_links do
73
- _replace_has_many_links(association_type, association_key_values)
71
+ def replace_to_many_links(relationship_type, relationship_key_values)
72
+ change :replace_to_many_links do
73
+ _replace_to_many_links(relationship_type, relationship_key_values)
74
74
  end
75
75
  end
76
76
 
77
- def replace_has_one_link(association_type, association_key_value)
78
- change :replace_has_one_link do
79
- _replace_has_one_link(association_type, association_key_value)
77
+ def replace_to_one_link(relationship_type, relationship_key_value)
78
+ change :replace_to_one_link do
79
+ _replace_to_one_link(relationship_type, relationship_key_value)
80
80
  end
81
81
  end
82
82
 
83
- def replace_polymorphic_has_one_link(association_type, association_key_value, association_key_type)
84
- change :replace_polymorphic_has_one_link do
85
- _replace_polymorphic_has_one_link(association_type, association_key_value, association_key_type)
83
+ def replace_polymorphic_to_one_link(relationship_type, relationship_key_value, relationship_key_type)
84
+ change :replace_polymorphic_to_one_link do
85
+ _replace_polymorphic_to_one_link(relationship_type, relationship_key_value, relationship_key_type)
86
86
  end
87
87
  end
88
88
 
89
- def remove_has_many_link(association_type, key)
90
- change :remove_has_many_link do
91
- _remove_has_many_link(association_type, key)
89
+ def remove_to_many_link(relationship_type, key)
90
+ change :remove_to_many_link do
91
+ _remove_to_many_link(relationship_type, key)
92
92
  end
93
93
  end
94
94
 
95
- def remove_has_one_link(association_type)
96
- change :remove_has_one_link do
97
- _remove_has_one_link(association_type)
95
+ def remove_to_one_link(relationship_type)
96
+ change :remove_to_one_link do
97
+ _remove_to_one_link(relationship_type)
98
98
  end
99
99
  end
100
100
 
@@ -111,8 +111,8 @@ module JSONAPI
111
111
 
112
112
  # Override this on a resource to customize how the associated records
113
113
  # are fetched for a model. Particularly helpful for authorization.
114
- def records_for(association_name, _options = {})
115
- model.send association_name
114
+ def records_for(relationship_name, _options = {})
115
+ model.public_send relationship_name
116
116
  end
117
117
 
118
118
  private
@@ -138,7 +138,7 @@ module JSONAPI
138
138
  # ```
139
139
  def _save
140
140
  unless @model.valid?
141
- fail JSONAPI::Exceptions::ValidationErrors.new(@model.errors.messages)
141
+ fail JSONAPI::Exceptions::ValidationErrors.new(self)
142
142
  end
143
143
 
144
144
  if defined? @model.save
@@ -159,65 +159,65 @@ module JSONAPI
159
159
  :completed
160
160
  end
161
161
 
162
- def _create_has_many_links(association_type, association_key_values)
163
- association = self.class._associations[association_type]
162
+ def _create_to_many_links(relationship_type, relationship_key_values)
163
+ relationship = self.class._relationships[relationship_type]
164
164
 
165
- association_key_values.each do |association_key_value|
166
- related_resource = association.resource_klass.find_by_key(association_key_value, context: @context)
165
+ relationship_key_values.each do |relationship_key_value|
166
+ related_resource = relationship.resource_klass.find_by_key(relationship_key_value, context: @context)
167
167
 
168
168
  # TODO: Add option to skip relations that already exist instead of returning an error?
169
- relation = @model.send(association.type).where(association.primary_key => association_key_value).first
169
+ relation = @model.public_send(relationship.type).where(relationship.primary_key => relationship_key_value).first
170
170
  if relation.nil?
171
- @model.send(association.type) << related_resource.model
171
+ @model.public_send(relationship.type) << related_resource.model
172
172
  else
173
- fail JSONAPI::Exceptions::HasManyRelationExists.new(association_key_value)
173
+ fail JSONAPI::Exceptions::HasManyRelationExists.new(relationship_key_value)
174
174
  end
175
175
  end
176
176
 
177
177
  :completed
178
178
  end
179
179
 
180
- def _replace_has_many_links(association_type, association_key_values)
181
- association = self.class._associations[association_type]
180
+ def _replace_to_many_links(relationship_type, relationship_key_values)
181
+ relationship = self.class._relationships[relationship_type]
182
182
 
183
- send("#{association.foreign_key}=", association_key_values)
183
+ send("#{relationship.foreign_key}=", relationship_key_values)
184
184
  @save_needed = true
185
185
 
186
186
  :completed
187
187
  end
188
188
 
189
- def _replace_has_one_link(association_type, association_key_value)
190
- association = self.class._associations[association_type]
189
+ def _replace_to_one_link(relationship_type, relationship_key_value)
190
+ relationship = self.class._relationships[relationship_type]
191
191
 
192
- send("#{association.foreign_key}=", association_key_value)
192
+ send("#{relationship.foreign_key}=", relationship_key_value)
193
193
  @save_needed = true
194
194
 
195
195
  :completed
196
196
  end
197
197
 
198
- def _replace_polymorphic_has_one_link(association_type, key_value, key_type)
199
- association = self.class._associations[association_type.to_sym]
198
+ def _replace_polymorphic_to_one_link(relationship_type, key_value, key_type)
199
+ relationship = self.class._relationships[relationship_type.to_sym]
200
200
 
201
- model.send("#{association.foreign_key}=", key_value)
202
- model.send("#{association.polymorphic_type}=", key_type.to_s.classify)
201
+ model.public_send("#{relationship.foreign_key}=", key_value)
202
+ model.public_send("#{relationship.polymorphic_type}=", key_type.to_s.classify)
203
203
 
204
204
  @save_needed = true
205
205
 
206
206
  :completed
207
207
  end
208
208
 
209
- def _remove_has_many_link(association_type, key)
210
- association = self.class._associations[association_type]
209
+ def _remove_to_many_link(relationship_type, key)
210
+ relationship = self.class._relationships[relationship_type]
211
211
 
212
- @model.send(association.type).delete(key)
212
+ @model.public_send(relationship.type).delete(key)
213
213
 
214
214
  :completed
215
215
  end
216
216
 
217
- def _remove_has_one_link(association_type)
218
- association = self.class._associations[association_type]
217
+ def _remove_to_one_link(relationship_type)
218
+ relationship = self.class._relationships[relationship_type]
219
219
 
220
- send("#{association.foreign_key}=", nil)
220
+ send("#{relationship.foreign_key}=", nil)
221
221
  @save_needed = true
222
222
 
223
223
  :completed
@@ -235,22 +235,22 @@ module JSONAPI
235
235
  end
236
236
  end
237
237
 
238
- field_data[:has_one].each do |association_type, value|
238
+ field_data[:to_one].each do |relationship_type, value|
239
239
  if value.nil?
240
- remove_has_one_link(association_type)
240
+ remove_to_one_link(relationship_type)
241
241
  else
242
242
  case value
243
243
  when Hash
244
- replace_polymorphic_has_one_link(association_type.to_s, value.fetch(:id), value.fetch(:type))
244
+ replace_polymorphic_to_one_link(relationship_type.to_s, value.fetch(:id), value.fetch(:type))
245
245
  else
246
- replace_has_one_link(association_type, value)
246
+ replace_to_one_link(relationship_type, value)
247
247
  end
248
248
  end
249
- end if field_data[:has_one]
249
+ end if field_data[:to_one]
250
250
 
251
- field_data[:has_many].each do |association_type, values|
252
- replace_has_many_links(association_type, values)
253
- end if field_data[:has_many]
251
+ field_data[:to_many].each do |relationship_type, values|
252
+ replace_to_many_links(relationship_type, values)
253
+ end if field_data[:to_many]
254
254
 
255
255
  :completed
256
256
  end
@@ -258,7 +258,7 @@ module JSONAPI
258
258
  class << self
259
259
  def inherited(base)
260
260
  base._attributes = (_attributes || {}).dup
261
- base._associations = (_associations || {}).dup
261
+ base._relationships = (_relationships || {}).dup
262
262
  base._allowed_filters = (_allowed_filters || Set.new).dup
263
263
 
264
264
  type = base.name.demodulize.sub(/Resource$/, '').underscore
@@ -278,7 +278,7 @@ module JSONAPI
278
278
  resource
279
279
  end
280
280
 
281
- attr_accessor :_attributes, :_associations, :_allowed_filters, :_type, :_paginator
281
+ attr_accessor :_attributes, :_relationships, :_allowed_filters, :_type, :_paginator
282
282
 
283
283
  def create(context)
284
284
  new(create_model, context)
@@ -314,11 +314,11 @@ module JSONAPI
314
314
  @_attributes ||= {}
315
315
  @_attributes[attr] = options
316
316
  define_method attr do
317
- @model.send(attr)
317
+ @model.public_send(attr)
318
318
  end unless method_defined?(attr)
319
319
 
320
320
  define_method "#{attr}=" do |value|
321
- @model.send "#{attr}=", value
321
+ @model.public_send "#{attr}=", value
322
322
  end unless method_defined?("#{attr}=")
323
323
  end
324
324
 
@@ -326,12 +326,27 @@ module JSONAPI
326
326
  { format: :default }
327
327
  end
328
328
 
329
+ def relationship(*attrs)
330
+ options = attrs.extract_options!
331
+ klass = case options[:to]
332
+ when :one
333
+ Relationship::ToOne
334
+ when :many
335
+ Relationship::ToMany
336
+ else
337
+ #:nocov:#
338
+ fail ArgumentError.new('to: must be either :one or :many')
339
+ #:nocov:#
340
+ end
341
+ _add_relationship(klass, *attrs, options.except(:to))
342
+ end
343
+
329
344
  def has_one(*attrs)
330
- _associate(Association::HasOne, *attrs)
345
+ _add_relationship(Relationship::ToOne, *attrs)
331
346
  end
332
347
 
333
348
  def has_many(*attrs)
334
- _associate(Association::HasMany, *attrs)
349
+ _add_relationship(Relationship::ToMany, *attrs)
335
350
  end
336
351
 
337
352
  def model_name(model)
@@ -367,12 +382,12 @@ module JSONAPI
367
382
 
368
383
  # Override in your resource to filter the updatable keys
369
384
  def updatable_fields(_context = nil)
370
- _updatable_associations | _attributes.keys - [:id]
385
+ _updatable_relationships | _attributes.keys - [:id]
371
386
  end
372
387
 
373
388
  # Override in your resource to filter the creatable keys
374
389
  def creatable_fields(_context = nil)
375
- _updatable_associations | _attributes.keys
390
+ _updatable_relationships | _attributes.keys
376
391
  end
377
392
 
378
393
  # Override in your resource to filter the sortable keys
@@ -381,33 +396,33 @@ module JSONAPI
381
396
  end
382
397
 
383
398
  def fields
384
- _associations.keys | _attributes.keys
399
+ _relationships.keys | _attributes.keys
385
400
  end
386
401
 
387
- def resolve_association_names_to_relations(resource_klass, model_includes, options = {})
402
+ def resolve_relationship_names_to_relations(resource_klass, model_includes, options = {})
388
403
  case model_includes
389
404
  when Array
390
405
  return model_includes.map do |value|
391
- resolve_association_names_to_relations(resource_klass, value, options)
406
+ resolve_relationship_names_to_relations(resource_klass, value, options)
392
407
  end
393
408
  when Hash
394
409
  model_includes.keys.each do |key|
395
- association = resource_klass._associations[key]
410
+ relationship = resource_klass._relationships[key]
396
411
  value = model_includes[key]
397
412
  model_includes.delete(key)
398
- model_includes[association.relation_name(options)] = resolve_association_names_to_relations(association.resource_klass, value, options)
413
+ model_includes[relationship.relation_name(options)] = resolve_relationship_names_to_relations(relationship.resource_klass, value, options)
399
414
  end
400
415
  return model_includes
401
416
  when Symbol
402
- association = resource_klass._associations[model_includes]
403
- return association.relation_name(options)
417
+ relationship = resource_klass._relationships[model_includes]
418
+ return relationship.relation_name(options)
404
419
  end
405
420
  end
406
421
 
407
422
  def apply_includes(records, options = {})
408
423
  include_directives = options[:include_directives]
409
424
  if include_directives
410
- model_includes = resolve_association_names_to_relations(self, include_directives.model_includes, options)
425
+ model_includes = resolve_relationship_names_to_relations(self, include_directives.model_includes, options)
411
426
  records = records.includes(model_includes)
412
427
  end
413
428
 
@@ -436,12 +451,12 @@ module JSONAPI
436
451
 
437
452
  if filters
438
453
  filters.each do |filter, value|
439
- if _associations.include?(filter)
440
- if _associations[filter].is_a?(JSONAPI::Association::HasMany)
454
+ if _relationships.include?(filter)
455
+ if _relationships[filter].is_a?(JSONAPI::Relationship::ToMany)
441
456
  required_includes.push(filter.to_s)
442
- records = apply_filter(records, "#{filter}.#{_associations[filter].primary_key}", value, options)
457
+ records = apply_filter(records, "#{filter}.#{_relationships[filter].primary_key}", value, options)
443
458
  else
444
- records = apply_filter(records, "#{_associations[filter].foreign_key}", value, options)
459
+ records = apply_filter(records, "#{_relationships[filter].foreign_key}", value, options)
445
460
  end
446
461
  else
447
462
  records = apply_filter(records, filter, value, options)
@@ -514,16 +529,16 @@ module JSONAPI
514
529
  verified_filters
515
530
  end
516
531
 
517
- def is_filter_association?(filter)
518
- filter == _type || _associations.include?(filter)
532
+ def is_filter_relationship?(filter)
533
+ filter == _type || _relationships.include?(filter)
519
534
  end
520
535
 
521
536
  def verify_filter(filter, raw, context = nil)
522
537
  filter_values = []
523
538
  filter_values += CSV.parse_line(raw) unless raw.nil? || raw.empty?
524
539
 
525
- if is_filter_association?(filter)
526
- verify_association_filter(filter, filter_values, context)
540
+ if is_filter_relationship?(filter)
541
+ verify_relationship_filter(filter, filter_values, context)
527
542
  else
528
543
  verify_custom_filter(filter, filter_values, context)
529
544
  end
@@ -548,8 +563,8 @@ module JSONAPI
548
563
  [filter, value]
549
564
  end
550
565
 
551
- # override to allow for custom association logic, such as uuids, multiple keys or permission checks on keys
552
- def verify_association_filter(filter, raw, _context = nil)
566
+ # override to allow for custom relationship logic, such as uuids, multiple keys or permission checks on keys
567
+ def verify_relationship_filter(filter, raw, _context = nil)
553
568
  [filter, raw]
554
569
  end
555
570
 
@@ -558,18 +573,18 @@ module JSONAPI
558
573
  default_attribute_options.merge(@_attributes[attr])
559
574
  end
560
575
 
561
- def _updatable_associations
562
- @_associations.map { |key, _association| key }
576
+ def _updatable_relationships
577
+ @_relationships.map { |key, _relationship| key }
563
578
  end
564
579
 
565
- def _has_association?(type)
580
+ def _has_relationship?(type)
566
581
  type = type.to_s
567
- @_associations.key?(type.singularize.to_sym) || @_associations.key?(type.pluralize.to_sym)
582
+ @_relationships.key?(type.singularize.to_sym) || @_relationships.key?(type.pluralize.to_sym)
568
583
  end
569
584
 
570
- def _association(type)
585
+ def _relationship(type)
571
586
  type = type.to_sym
572
- @_associations[type]
587
+ @_relationships[type]
573
588
  end
574
589
 
575
590
  def _model_name
@@ -577,7 +592,7 @@ module JSONAPI
577
592
  end
578
593
 
579
594
  def _primary_key
580
- @_primary_key ||= :id
595
+ @_primary_key ||= _model_class.respond_to?(:primary_key) ? _model_class.primary_key : :id
581
596
  end
582
597
 
583
598
  def _as_parent_key
@@ -638,68 +653,85 @@ module JSONAPI
638
653
  def check_reserved_attribute_name(name)
639
654
  # Allow :id since it can be used to specify the format. Since it is a method on the base Resource
640
655
  # an attribute method won't be created for it.
641
- if [:type, :href, :links].include?(name.to_sym)
656
+ if [:type, :href, :links, :model].include?(name.to_sym)
642
657
  warn "[NAME COLLISION] `#{name}` is a reserved key in #{@@resource_types[_type]}."
643
658
  end
644
659
  end
645
660
 
646
- def check_reserved_association_name(name)
661
+ def check_reserved_relationship_name(name)
647
662
  if [:id, :ids, :type, :types, :href, :hrefs, :link, :links].include?(name.to_sym)
648
- warn "[NAME COLLISION] `#{name}` is a reserved association name in #{@@resource_types[_type]}."
663
+ warn "[NAME COLLISION] `#{name}` is a reserved relationship name in #{@@resource_types[_type]}."
649
664
  end
650
665
  end
651
666
 
652
- def _associate(klass, *attrs)
667
+ def _add_relationship(klass, *attrs)
653
668
  options = attrs.extract_options!
654
669
  options[:module_path] = module_path
655
670
 
656
671
  attrs.each do |attr|
657
- check_reserved_association_name(attr)
658
- @_associations[attr] = association = klass.new(attr, options)
672
+ check_reserved_relationship_name(attr)
673
+ @_relationships[attr] = relationship = klass.new(attr, options)
659
674
 
660
- associated_records_method_name = case association
661
- when JSONAPI::Association::HasOne then "record_for_#{attr}"
662
- when JSONAPI::Association::HasMany then "records_for_#{attr}"
675
+ associated_records_method_name = case relationship
676
+ when JSONAPI::Relationship::ToOne then "record_for_#{attr}"
677
+ when JSONAPI::Relationship::ToMany then "records_for_#{attr}"
663
678
  end
664
679
 
665
- foreign_key = association.foreign_key
680
+ foreign_key = relationship.foreign_key
666
681
 
667
682
  define_method "#{foreign_key}=" do |value|
668
683
  @model.method("#{foreign_key}=").call(value)
669
684
  end unless method_defined?("#{foreign_key}=")
670
685
 
671
686
  define_method associated_records_method_name do |options = {}|
672
- relation_name = association.relation_name(options.merge({context: @context}))
687
+ options = options.merge({context: @context})
688
+ relation_name = relationship.relation_name(options)
673
689
  records_for(relation_name, options)
674
690
  end unless method_defined?(associated_records_method_name)
675
691
 
676
- if association.is_a?(JSONAPI::Association::HasOne)
677
- define_method foreign_key do
678
- @model.method(foreign_key).call
679
- end unless method_defined?(foreign_key)
692
+ if relationship.is_a?(JSONAPI::Relationship::ToOne)
693
+ if relationship.belongs_to?
694
+ define_method foreign_key do
695
+ @model.method(foreign_key).call
696
+ end unless method_defined?(foreign_key)
680
697
 
681
- define_method attr do |options = {}|
682
- if association.polymorphic?
683
- associated_model = public_send(associated_records_method_name)
684
- resource_klass = Resource.resource_for(self.class.module_path + associated_model.class.to_s.underscore) if associated_model
685
- return resource_klass.new(associated_model, @context) if resource_klass
686
- else
687
- resource_klass = association.resource_klass
698
+ define_method attr do |options = {}|
699
+ if relationship.polymorphic?
700
+ associated_model = public_send(associated_records_method_name)
701
+ resource_klass = Resource.resource_for(self.class.module_path + associated_model.class.to_s.underscore) if associated_model
702
+ return resource_klass.new(associated_model, @context) if resource_klass
703
+ else
704
+ resource_klass = relationship.resource_klass
705
+ if resource_klass
706
+ associated_model = public_send(associated_records_method_name)
707
+ return associated_model ? resource_klass.new(associated_model, @context) : nil
708
+ end
709
+ end
710
+ end unless method_defined?(attr)
711
+ else
712
+ define_method foreign_key do
713
+ record = public_send(associated_records_method_name)
714
+ return nil if record.nil?
715
+ record.public_send(relationship.resource_klass._primary_key)
716
+ end unless method_defined?(foreign_key)
717
+
718
+ define_method attr do |options = {}|
719
+ resource_klass = relationship.resource_klass
688
720
  if resource_klass
689
721
  associated_model = public_send(associated_records_method_name)
690
722
  return associated_model ? resource_klass.new(associated_model, @context) : nil
691
723
  end
692
- end
693
- end unless method_defined?(attr)
694
- elsif association.is_a?(JSONAPI::Association::HasMany)
724
+ end unless method_defined?(attr)
725
+ end
726
+ elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
695
727
  define_method foreign_key do
696
728
  records = public_send(associated_records_method_name)
697
729
  return records.collect do |record|
698
- record.send(association.resource_klass._primary_key)
730
+ record.public_send(relationship.resource_klass._primary_key)
699
731
  end
700
732
  end unless method_defined?(foreign_key)
701
733
  define_method attr do |options = {}|
702
- resource_klass = association.resource_klass
734
+ resource_klass = relationship.resource_klass
703
735
  records = public_send(associated_records_method_name)
704
736
 
705
737
  filters = options.fetch(:filters, {})
@@ -719,7 +751,7 @@ module JSONAPI
719
751
  end
720
752
 
721
753
  return records.collect do |record|
722
- if association.polymorphic?
754
+ if relationship.polymorphic?
723
755
  resource_klass = Resource.resource_for(self.class.module_path + record.class.to_s.underscore)
724
756
  end
725
757
  resource_klass.new(record, @context)