jsonapi-resources 0.10.7 → 0.11.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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 -17
  28. data/lib/jsonapi/path.rb +2 -0
  29. data/lib/jsonapi/path_segment.rb +4 -2
  30. data/lib/jsonapi/processor.rb +100 -153
  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