jsonapi-resources 0.10.6 → 0.11.0.beta2

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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +39 -2
  4. data/lib/generators/jsonapi/controller_generator.rb +2 -0
  5. data/lib/generators/jsonapi/resource_generator.rb +2 -0
  6. data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +3 -2
  7. data/lib/jsonapi/active_relation/join_manager.rb +30 -18
  8. data/lib/jsonapi/active_relation/join_manager_v10.rb +305 -0
  9. data/lib/jsonapi/active_relation_retrieval.rb +885 -0
  10. data/lib/jsonapi/active_relation_retrieval_v09.rb +715 -0
  11. data/lib/jsonapi/{active_relation_resource.rb → active_relation_retrieval_v10.rb} +113 -135
  12. data/lib/jsonapi/acts_as_resource_controller.rb +49 -49
  13. data/lib/jsonapi/cached_response_fragment.rb +4 -2
  14. data/lib/jsonapi/callbacks.rb +2 -0
  15. data/lib/jsonapi/compiled_json.rb +2 -0
  16. data/lib/jsonapi/configuration.rb +35 -15
  17. data/lib/jsonapi/error.rb +2 -0
  18. data/lib/jsonapi/error_codes.rb +2 -0
  19. data/lib/jsonapi/exceptions.rb +2 -0
  20. data/lib/jsonapi/formatter.rb +2 -0
  21. data/lib/jsonapi/include_directives.rb +77 -19
  22. data/lib/jsonapi/link_builder.rb +2 -0
  23. data/lib/jsonapi/mime_types.rb +6 -10
  24. data/lib/jsonapi/naive_cache.rb +2 -0
  25. data/lib/jsonapi/operation.rb +2 -0
  26. data/lib/jsonapi/operation_result.rb +2 -0
  27. data/lib/jsonapi/paginator.rb +2 -0
  28. data/lib/jsonapi/path.rb +2 -0
  29. data/lib/jsonapi/path_segment.rb +4 -2
  30. data/lib/jsonapi/processor.rb +95 -140
  31. data/lib/jsonapi/relationship.rb +89 -35
  32. data/lib/jsonapi/{request_parser.rb → request.rb} +157 -164
  33. data/lib/jsonapi/resource.rb +7 -2
  34. data/lib/jsonapi/{basic_resource.rb → resource_common.rb} +187 -88
  35. data/lib/jsonapi/resource_controller.rb +2 -0
  36. data/lib/jsonapi/resource_controller_metal.rb +2 -0
  37. data/lib/jsonapi/resource_fragment.rb +17 -15
  38. data/lib/jsonapi/resource_identity.rb +6 -0
  39. data/lib/jsonapi/resource_serializer.rb +20 -4
  40. data/lib/jsonapi/resource_set.rb +36 -16
  41. data/lib/jsonapi/resource_tree.rb +191 -0
  42. data/lib/jsonapi/resources/railtie.rb +3 -1
  43. data/lib/jsonapi/resources/version.rb +3 -1
  44. data/lib/jsonapi/response_document.rb +4 -2
  45. data/lib/jsonapi/routing_ext.rb +4 -2
  46. data/lib/jsonapi/simple_resource.rb +13 -0
  47. data/lib/jsonapi-resources.rb +10 -4
  48. data/lib/tasks/check_upgrade.rake +3 -1
  49. metadata +47 -15
  50. data/lib/jsonapi/resource_id_tree.rb +0 -112
@@ -1,28 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'jsonapi/callbacks'
2
4
  require 'jsonapi/configuration'
3
5
 
4
6
  module JSONAPI
5
- class BasicResource
6
- include Callbacks
7
-
8
- @abstract = true
9
- @immutable = true
10
- @root = true
11
-
12
- attr_reader :context
13
-
14
- define_jsonapi_resources_callbacks :create,
15
- :update,
16
- :remove,
17
- :save,
18
- :create_to_many_link,
19
- :replace_to_many_links,
20
- :create_to_one_link,
21
- :replace_to_one_link,
22
- :replace_polymorphic_to_one_link,
23
- :remove_to_many_link,
24
- :remove_to_one_link,
25
- :replace_fields
7
+ module ResourceCommon
8
+
9
+ def self.included(base)
10
+ base.extend ClassMethods
11
+
12
+ base.include Callbacks
13
+ base.define_jsonapi_resources_callbacks :create,
14
+ :update,
15
+ :remove,
16
+ :save,
17
+ :create_to_many_link,
18
+ :replace_to_many_links,
19
+ :create_to_one_link,
20
+ :replace_to_one_link,
21
+ :replace_polymorphic_to_one_link,
22
+ :remove_to_many_link,
23
+ :remove_to_one_link,
24
+ :replace_fields
25
+
26
+ base.attr_reader :context
27
+ end
26
28
 
27
29
  def initialize(model, context)
28
30
  @model = model
@@ -44,8 +46,16 @@ module JSONAPI
44
46
  JSONAPI::ResourceIdentity.new(self.class, id)
45
47
  end
46
48
 
49
+ def fragment(cache: nil, primary: false)
50
+ @fragment ||= JSONAPI::ResourceFragment.new(identity, resource: self, cache: cache, primary: primary)
51
+ end
52
+
53
+ def cache_field_value
54
+ _model.public_send(self.class._cache_field)
55
+ end
56
+
47
57
  def cache_id
48
- [id, self.class.hash_cache_field(_model.public_send(self.class._cache_field))]
58
+ [id, self.class.hash_cache_field(cache_field_value)]
49
59
  end
50
60
 
51
61
  def is_new?
@@ -232,7 +242,7 @@ module JSONAPI
232
242
  return false if !relationship.reflect ||
233
243
  (!JSONAPI.configuration.use_relationship_reflection || options[:reflected_source])
234
244
 
235
- inverse_relationship = relationship.resource_klass._relationships[relationship.inverse_relationship]
245
+ inverse_relationship = relationship.resource_klass._relationship(relationship.inverse_relationship)
236
246
  if inverse_relationship.nil?
237
247
  warn "Inverse relationship could not be found for #{self.class.name}.#{relationship.name}. Relationship reflection disabled."
238
248
  return false
@@ -241,7 +251,7 @@ module JSONAPI
241
251
  end
242
252
 
243
253
  def _create_to_many_links(relationship_type, relationship_key_values, options)
244
- relationship = self.class._relationships[relationship_type]
254
+ relationship = self.class._relationship(relationship_type)
245
255
  relation_name = relationship.relation_name(context: @context)
246
256
 
247
257
  if options[:reflected_source]
@@ -263,7 +273,7 @@ module JSONAPI
263
273
 
264
274
  related_resources.each do |related_resource|
265
275
  if reflect
266
- if related_resource.class._relationships[relationship.inverse_relationship].is_a?(JSONAPI::Relationship::ToMany)
276
+ if related_resource.class._relationship(relationship.inverse_relationship).is_a?(JSONAPI::Relationship::ToMany)
267
277
  related_resource.create_to_many_links(relationship.inverse_relationship, [id], reflected_source: self)
268
278
  else
269
279
  related_resource.replace_to_one_link(relationship.inverse_relationship, id, reflected_source: self)
@@ -285,9 +295,7 @@ module JSONAPI
285
295
  reflect = reflect_relationship?(relationship, options)
286
296
 
287
297
  if reflect
288
- existing_rids = self.class.find_related_fragments([identity], relationship_type, options)
289
-
290
- existing = existing_rids.keys.collect { |rid| rid.id }
298
+ existing = find_related_ids(relationship, options)
291
299
 
292
300
  to_delete = existing - (relationship_key_values & existing)
293
301
  to_delete.each do |key|
@@ -304,8 +312,8 @@ module JSONAPI
304
312
  ids = relationship_key_value[:ids]
305
313
 
306
314
  related_records = relationship_resource_klass
307
- .records(options)
308
- .where({relationship_resource_klass._primary_key => ids})
315
+ .records(options)
316
+ .where({relationship_resource_klass._primary_key => ids})
309
317
 
310
318
  missed_ids = ids - related_records.pluck(relationship_resource_klass._primary_key)
311
319
 
@@ -327,7 +335,7 @@ module JSONAPI
327
335
  end
328
336
 
329
337
  def _replace_to_one_link(relationship_type, relationship_key_value, _options)
330
- relationship = self.class._relationships[relationship_type]
338
+ relationship = self.class._relationship(relationship_type)
331
339
 
332
340
  send("#{relationship.foreign_key}=", relationship_key_value)
333
341
  @save_needed = true
@@ -336,7 +344,7 @@ module JSONAPI
336
344
  end
337
345
 
338
346
  def _replace_polymorphic_to_one_link(relationship_type, key_value, key_type, _options)
339
- relationship = self.class._relationships[relationship_type.to_sym]
347
+ relationship = self.class._relationship(relationship_type.to_sym)
340
348
 
341
349
  send("#{relationship.foreign_key}=", {type: key_type, id: key_value})
342
350
  @save_needed = true
@@ -345,7 +353,7 @@ module JSONAPI
345
353
  end
346
354
 
347
355
  def _remove_to_many_link(relationship_type, key, options)
348
- relationship = self.class._relationships[relationship_type]
356
+ relationship = self.class._relationship(relationship_type)
349
357
 
350
358
  reflect = reflect_relationship?(relationship, options)
351
359
 
@@ -356,7 +364,7 @@ module JSONAPI
356
364
  if related_resource.nil?
357
365
  fail JSONAPI::Exceptions::RecordNotFound.new(key)
358
366
  else
359
- if related_resource.class._relationships[relationship.inverse_relationship].is_a?(JSONAPI::Relationship::ToMany)
367
+ if related_resource.class._relationship(relationship.inverse_relationship).is_a?(JSONAPI::Relationship::ToMany)
360
368
  related_resource.remove_to_many_link(relationship.inverse_relationship, id, reflected_source: self)
361
369
  else
362
370
  related_resource.remove_to_one_link(relationship.inverse_relationship, reflected_source: self)
@@ -377,7 +385,7 @@ module JSONAPI
377
385
  end
378
386
 
379
387
  def _remove_to_one_link(relationship_type, _options)
380
- relationship = self.class._relationships[relationship_type]
388
+ relationship = self.class._relationship(relationship_type)
381
389
 
382
390
  send("#{relationship.foreign_key}=", nil)
383
391
  @save_needed = true
@@ -417,8 +425,56 @@ module JSONAPI
417
425
  :completed
418
426
  end
419
427
 
420
- class << self
428
+ def find_related_ids(relationship, options = {})
429
+ send(relationship.foreign_key)
430
+ end
431
+
432
+ module ClassMethods
433
+ def resource_retrieval_strategy(module_name = JSONAPI.configuration.default_resource_retrieval_strategy)
434
+ if @_resource_retrieval_strategy_loaded
435
+ warn "Resource retrieval strategy #{@_resource_retrieval_strategy_loaded} already loaded for #{self.name}"
436
+ return
437
+ end
438
+
439
+ module_name = module_name.to_s
440
+
441
+ return if module_name.blank? || module_name == 'self' || module_name == 'none'
442
+
443
+ class_eval do
444
+ resource_retrieval_module = module_name.safe_constantize
445
+ raise "Unable to find resource_retrieval_strategy #{module_name}" unless resource_retrieval_module
446
+
447
+ include resource_retrieval_module
448
+ extend "#{module_name}::ClassMethods".safe_constantize
449
+ @_resource_retrieval_strategy_loaded = module_name
450
+ end
451
+ end
452
+
453
+ def warn_about_missing_retrieval_methods
454
+ resource_retrieval_methods = %i[find count find_by_key find_by_keys find_to_populate_by_keys find_fragments
455
+ find_related_fragments find_included_fragments count_related]
456
+
457
+ resource_retrieval_methods.each do |method_name|
458
+ warn "#{self.name} has not defined standard method #{method_name}" unless self.respond_to?(method_name)
459
+ end
460
+ end
461
+
421
462
  def inherited(subclass)
463
+ super
464
+
465
+ # Defer loading the resource retrieval strategy module until the class has been fully read to allow setting
466
+ # a custom resource_retrieval_strategy in the class definition
467
+ trace_point = TracePoint.new(:end) do |tp|
468
+ if subclass == tp.self
469
+ unless subclass._abstract
470
+ subclass.warn_about_missing_retrieval_methods
471
+ subclass.warn_about_unused_methods if subclass.methods.include?(:warn_about_unused_methods)
472
+ end
473
+ tp.disable
474
+ end
475
+ end
476
+ trace_point.enable
477
+
422
478
  subclass.abstract(false)
423
479
  subclass.immutable(false)
424
480
  subclass.caching(_caching)
@@ -456,6 +512,9 @@ module JSONAPI
456
512
 
457
513
  subclass._clear_cached_attribute_options
458
514
  subclass._clear_fields_cache
515
+
516
+ subclass._resource_retrieval_strategy_loaded = @_resource_retrieval_strategy_loaded
517
+ subclass.resource_retrieval_strategy unless subclass._resource_retrieval_strategy_loaded
459
518
  end
460
519
 
461
520
  def rebuild_relationships(relationships)
@@ -467,7 +526,7 @@ module JSONAPI
467
526
  original_relationships.each_value do |relationship|
468
527
  options = relationship.options.dup
469
528
  options[:parent_resource] = self
470
- options[:inverse_relationship] = relationship.inverse_relationship
529
+ options[:inverse_relationship] = relationship.options[:inverse_relationship]
471
530
  _add_relationship(relationship.class, relationship.name, options)
472
531
  end
473
532
  end
@@ -502,7 +561,8 @@ module JSONAPI
502
561
  end
503
562
  end
504
563
 
505
- attr_accessor :_attributes, :_relationships, :_type, :_model_hints, :_routed, :_warned_missing_route
564
+ attr_accessor :_attributes, :_relationships, :_type, :_model_hints, :_routed, :_warned_missing_route,
565
+ :_resource_retrieval_strategy_loaded
506
566
  attr_writer :_allowed_filters, :_paginator, :_allowed_sort
507
567
 
508
568
  def create(context)
@@ -565,23 +625,15 @@ module JSONAPI
565
625
  # Note: this will allow the returning of model attributes without a corresponding
566
626
  # resource attribute, for example a belongs_to id such as `author_id` or bypassing
567
627
  # the delegate.
568
- attr = @_attributes[attribute]
628
+ attr = @_attributes[attribute.to_sym]
569
629
  attr && attr[:delegate] ? attr[:delegate].to_sym : attribute
570
630
  end
571
- if Rails::VERSION::MAJOR >= 5
572
- attribute_type = _model_class.attribute_types[field_name.to_s]
573
- else
574
- attribute_type = _model_class.column_types[field_name.to_s]
575
- end
576
- { name: field_name, type: attribute_type}
631
+
632
+ { name: field_name, type: _model_class.attribute_types[field_name.to_s]}
577
633
  end
578
634
 
579
635
  def cast_to_attribute_type(value, type)
580
- if Rails::VERSION::MAJOR >= 5
581
- return type.cast(value)
582
- else
583
- return type.type_cast_from_database(value)
584
- end
636
+ type.cast(value)
585
637
  end
586
638
 
587
639
  def default_attribute_options
@@ -591,14 +643,14 @@ module JSONAPI
591
643
  def relationship(*attrs)
592
644
  options = attrs.extract_options!
593
645
  klass = case options[:to]
594
- when :one
595
- Relationship::ToOne
596
- when :many
597
- Relationship::ToMany
598
- else
599
- #:nocov:#
600
- fail ArgumentError.new('to: must be either :one or :many')
601
- #:nocov:#
646
+ when :one
647
+ Relationship::ToOne
648
+ when :many
649
+ Relationship::ToMany
650
+ else
651
+ #:nocov:#
652
+ fail ArgumentError.new('to: must be either :one or :many')
653
+ #:nocov:#
602
654
  end
603
655
  _add_relationship(klass, *attrs, options.except(:to))
604
656
  end
@@ -609,10 +661,10 @@ module JSONAPI
609
661
 
610
662
  def belongs_to(*attrs)
611
663
  ActiveSupport::Deprecation.warn "In #{name} you exposed a `has_one` relationship "\
612
- " using the `belongs_to` class method. We think `has_one`" \
613
- " is more appropriate. If you know what you're doing," \
614
- " and don't want to see this warning again, override the" \
615
- " `belongs_to` class method on your resource."
664
+ " using the `belongs_to` class method. We think `has_one`" \
665
+ " is more appropriate. If you know what you're doing," \
666
+ " and don't want to see this warning again, override the" \
667
+ " `belongs_to` class method on your resource."
616
668
  _add_relationship(Relationship::ToOne, *attrs)
617
669
  end
618
670
 
@@ -636,7 +688,7 @@ module JSONAPI
636
688
  end
637
689
 
638
690
  def model_hint(model: _model_name, resource: _type)
639
- resource_type = ((resource.is_a?(Class)) && (resource < JSONAPI::Resource)) ? resource._type : resource.to_s
691
+ resource_type = ((resource.is_a?(Class)) && resource.include?(JSONAPI::ResourceCommon)) ? resource._type : resource.to_s
640
692
 
641
693
  _model_hints[model.to_s.gsub('::', '/').underscore] = resource_type.to_s
642
694
  end
@@ -699,7 +751,22 @@ module JSONAPI
699
751
  end
700
752
 
701
753
  def fields
702
- @_fields_cache ||= _relationships.keys | _attributes.keys
754
+ @_fields_cache ||= _relationships.select { |k,v| !v.hidden? }.keys | _attributes.keys
755
+ end
756
+
757
+ def to_one_relationships_including_optional_linkage_data
758
+ # ToDo: can we only calculate this once?
759
+ @to_one_relationships_including_optional_linkage_data =
760
+ _relationships.select do |_name, relationship|
761
+ relationship.is_a?(JSONAPI::Relationship::ToOne) && relationship.include_optional_linkage_data?
762
+ end
763
+ end
764
+
765
+ def to_one_relationships_for_linkage(include_related)
766
+ # exclude the relationships that are already included in the include_related param
767
+ include_related_names = include_related.present? ? include_related.keys : []
768
+ relationship_names = to_one_relationships_including_optional_linkage_data.keys - include_related_names
769
+ _relationships.fetch_values(*relationship_names)
703
770
  end
704
771
 
705
772
  def resources_for(records, context)
@@ -709,10 +776,14 @@ module JSONAPI
709
776
  end
710
777
 
711
778
  def resource_for(model_record, context)
712
- resource_klass = self.resource_klass_for_model(model_record)
779
+ resource_klass = resource_klass_for_model(model_record)
713
780
  resource_klass.new(model_record, context)
714
781
  end
715
782
 
783
+ def resource_for_model(model, context)
784
+ resource_for(resource_type_for(model), context)
785
+ end
786
+
716
787
  def verify_filters(filters, context = nil)
717
788
  verified_filters = {}
718
789
  filters.each do |filter, raw_value|
@@ -773,12 +844,12 @@ module JSONAPI
773
844
  if @_singleton_options && @_singleton_options[:singleton_key]
774
845
  strategy = @_singleton_options[:singleton_key]
775
846
  case strategy
776
- when Proc
777
- key = strategy.call(context)
778
- when Symbol, String
779
- key = send(strategy, context)
780
- else
781
- raise "singleton_key must be a proc or function name"
847
+ when Proc
848
+ key = strategy.call(context)
849
+ when Symbol, String
850
+ key = send(strategy, context)
851
+ else
852
+ raise "singleton_key must be a proc or function name"
782
853
  end
783
854
  end
784
855
  key
@@ -853,13 +924,12 @@ module JSONAPI
853
924
 
854
925
  def _relationship(type)
855
926
  return nil unless type
856
- type = type.to_sym
857
- @_relationships[type]
927
+ @_relationships[type.to_sym]
858
928
  end
859
929
 
860
930
  def _model_name
861
931
  if _abstract
862
- ''
932
+ ''
863
933
  else
864
934
  return @_model_name.to_s if defined?(@_model_name)
865
935
  class_name = self.name
@@ -882,7 +952,7 @@ module JSONAPI
882
952
  end
883
953
 
884
954
  def _default_primary_key
885
- @_default_primary_key ||=_model_class.respond_to?(:primary_key) ? _model_class.primary_key : :id
955
+ @_default_primary_key ||= _model_class.respond_to?(:primary_key) ? _model_class.primary_key : :id
886
956
  end
887
957
 
888
958
  def _cache_field
@@ -973,14 +1043,14 @@ module JSONAPI
973
1043
 
974
1044
  def parse_exclude_links(exclude)
975
1045
  case exclude
976
- when :default, "default"
977
- [:self]
978
- when :none, "none"
979
- []
980
- when Array
981
- exclude.collect {|link| link.to_sym}
982
- else
983
- fail "Invalid exclude_links"
1046
+ when :default, "default"
1047
+ [:self]
1048
+ when :none, "none"
1049
+ []
1050
+ when Array
1051
+ exclude.collect {|link| link.to_sym}
1052
+ else
1053
+ fail "Invalid exclude_links"
984
1054
  end
985
1055
  end
986
1056
 
@@ -1016,6 +1086,22 @@ module JSONAPI
1016
1086
  nil
1017
1087
  end
1018
1088
 
1089
+ def _included_strategy
1090
+ @_included_strategy || JSONAPI.configuration.default_included_strategy
1091
+ end
1092
+
1093
+ def included_strategy(included_strategy)
1094
+ @_included_strategy = included_strategy
1095
+ end
1096
+
1097
+ def _related_strategy
1098
+ @_related_strategy || JSONAPI.configuration.default_related_strategy
1099
+ end
1100
+
1101
+ def related_strategy(related_strategy)
1102
+ @_related_strategy = related_strategy
1103
+ end
1104
+
1019
1105
  # Generate a hashcode from the value to be used as part of the cache lookup
1020
1106
  def hash_cache_field(value)
1021
1107
  value.hash
@@ -1054,7 +1140,7 @@ module JSONAPI
1054
1140
  end
1055
1141
 
1056
1142
  def default_sort
1057
- [{field: 'id', direction: :asc}]
1143
+ [{field: _primary_key, direction: :asc}]
1058
1144
  end
1059
1145
 
1060
1146
  def construct_order_options(sort_params)
@@ -1083,11 +1169,23 @@ module JSONAPI
1083
1169
  end
1084
1170
  end
1085
1171
 
1086
- # ResourceBuilder methods
1172
+ def _setup_relationship(klass, *attrs)
1173
+ _clear_fields_cache
1174
+
1175
+ options = attrs.extract_options!
1176
+ options[:parent_resource] = self
1177
+
1178
+ relationship_name = attrs[0].to_sym
1179
+ check_duplicate_relationship_name(relationship_name)
1180
+
1181
+ define_relationship_methods(relationship_name.to_sym, klass, options)
1182
+ end
1183
+
1184
+ # ResourceBuilder methods
1087
1185
  def define_relationship_methods(relationship_name, relationship_klass, options)
1088
1186
  relationship = register_relationship(
1089
- relationship_name,
1090
- relationship_klass.new(relationship_name, options)
1187
+ relationship_name,
1188
+ relationship_klass.new(relationship_name, options)
1091
1189
  )
1092
1190
 
1093
1191
  define_foreign_key_setter(relationship)
@@ -1104,10 +1202,11 @@ module JSONAPI
1104
1202
  _model.method("#{relationship.foreign_key}=").call(value)
1105
1203
  end
1106
1204
  end
1205
+ relationship.foreign_key
1107
1206
  end
1108
1207
 
1109
1208
  def define_on_resource(method_name, &block)
1110
- return if method_defined?(method_name)
1209
+ return method_name if method_defined?(method_name)
1111
1210
  define_method(method_name, block)
1112
1211
  end
1113
1212
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  class ResourceController < ActionController::Base
3
5
  include JSONAPI::ActsAsResourceController
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  class ResourceControllerMetal < ActionController::Metal
3
5
  MODULES = [
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
 
3
5
  # A ResourceFragment holds a ResourceIdentity and associated partial resource data.
@@ -6,42 +8,42 @@ module JSONAPI
6
8
  # cache - the value of the cache field for the resource instance
7
9
  # related - a hash of arrays of related resource identities, grouped by relationship name
8
10
  # related_from - a set of related resource identities that loaded the fragment
11
+ # resource - a resource instance
9
12
  #
10
- # Todo: optionally use these for faster responses by bypassing model instantiation)
11
- # attributes - resource attributes
12
13
 
13
14
  class ResourceFragment
14
- attr_reader :identity, :attributes, :related_from, :related
15
+ attr_reader :identity, :related_from, :related, :resource
15
16
 
16
17
  attr_accessor :primary, :cache
17
18
 
18
19
  alias :cache_field :cache #ToDo: Rename one or the other
19
20
 
20
- def initialize(identity)
21
+ def initialize(identity, resource: nil, cache: nil, primary: false)
21
22
  @identity = identity
22
- @cache = nil
23
- @attributes = {}
23
+ @cache = cache
24
+ @resource = resource
25
+ @primary = primary
26
+
24
27
  @related = {}
25
- @primary = false
26
- @related_from = Set.new
28
+ @related_from = SortedSet.new
27
29
  end
28
30
 
29
31
  def initialize_related(relationship_name)
30
- @related ||= {}
31
32
  @related[relationship_name.to_sym] ||= Set.new
32
33
  end
33
34
 
34
35
  def add_related_identity(relationship_name, identity)
35
36
  initialize_related(relationship_name)
36
- @related[relationship_name.to_sym] << identity
37
+ @related[relationship_name.to_sym] << identity if identity
37
38
  end
38
39
 
39
- def add_related_from(identity)
40
- @related_from << identity
40
+ def merge_related_identities(relationship_name, identities)
41
+ initialize_related(relationship_name)
42
+ @related[relationship_name.to_sym].merge(identities) if identities
41
43
  end
42
44
 
43
- def add_attribute(name, value)
44
- @attributes[name] = value
45
+ def add_related_from(identity)
46
+ @related_from << identity
45
47
  end
46
48
  end
47
- end
49
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
 
3
5
  # ResourceIdentity describes a unique identity of a resource in the system.
@@ -32,6 +34,10 @@ module JSONAPI
32
34
  [@resource_klass, @id].hash
33
35
  end
34
36
 
37
+ def <=>(other_identity)
38
+ self.id <=> other_identity.id
39
+ end
40
+
35
41
  # Creates a string representation of the identifier.
36
42
  def to_s
37
43
  # :nocov:
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  class ResourceSerializer
3
5
 
4
6
  attr_reader :link_builder, :key_formatter, :serialization_options,
5
7
  :fields, :include_directives, :always_include_to_one_linkage_data,
6
- :always_include_to_many_linkage_data
8
+ :always_include_to_many_linkage_data, :options
7
9
 
8
10
  # initialize
9
11
  # Options can include
@@ -18,11 +20,12 @@ module JSONAPI
18
20
  # serialization_options: additional options that will be passed to resource meta and links lambdas
19
21
 
20
22
  def initialize(primary_resource_klass, options = {})
23
+ @options = options
21
24
  @primary_resource_klass = primary_resource_klass
22
25
  @fields = options.fetch(:fields, {})
23
26
  @include = options.fetch(:include, [])
24
- @include_directives = options[:include_directives]
25
- @include_directives ||= JSONAPI::IncludeDirectives.new(@primary_resource_klass, @include)
27
+ @include_directives = options.fetch(:include_directives,
28
+ JSONAPI::IncludeDirectives.new(@primary_resource_klass, @include))
26
29
  @key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
27
30
  @id_formatter = ValueFormatter.value_formatter_for(:id)
28
31
  @link_builder = generate_link_builder(primary_resource_klass, options)
@@ -41,6 +44,19 @@ module JSONAPI
41
44
  @_supplying_relationship_fields = {}
42
45
  end
43
46
 
47
+ # Converts a single resource, or an array of resources to a hash, conforming to the JSONAPI structure
48
+ def serialize_to_hash(source)
49
+ include_related = include_directives[:include_related]
50
+ resource_set = JSONAPI::ResourceSet.new(source, include_related, options)
51
+ resource_set.populate!(self, options[:context], options)
52
+
53
+ if source.is_a?(Array)
54
+ serialize_resource_set_to_hash_plural(resource_set)
55
+ else
56
+ serialize_resource_set_to_hash_single(resource_set)
57
+ end
58
+ end
59
+
44
60
  # Converts a resource_set to a hash, conforming to the JSONAPI structure
45
61
  def serialize_resource_set_to_hash_single(resource_set)
46
62
 
@@ -291,7 +307,7 @@ module JSONAPI
291
307
  if field_set.include?(name)
292
308
 
293
309
  relationship_name = unformat_key(name).to_sym
294
- relationship_klass = source.resource_klass._relationships[relationship_name]
310
+ relationship_klass = source.resource_klass._relationship(relationship_name)
295
311
 
296
312
  if relationship_klass.is_a?(JSONAPI::Relationship::ToOne)
297
313
  # include_linkage = @always_include_to_one_linkage_data | relationship_klass.always_include_linkage_data