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.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +39 -2
- data/lib/generators/jsonapi/controller_generator.rb +2 -0
- data/lib/generators/jsonapi/resource_generator.rb +2 -0
- data/lib/jsonapi/active_relation/adapters/join_left_active_record_adapter.rb +3 -2
- data/lib/jsonapi/active_relation/join_manager.rb +30 -18
- data/lib/jsonapi/active_relation/join_manager_v10.rb +305 -0
- data/lib/jsonapi/active_relation_retrieval.rb +885 -0
- data/lib/jsonapi/active_relation_retrieval_v09.rb +715 -0
- data/lib/jsonapi/{active_relation_resource.rb → active_relation_retrieval_v10.rb} +113 -135
- data/lib/jsonapi/acts_as_resource_controller.rb +49 -49
- data/lib/jsonapi/cached_response_fragment.rb +4 -2
- data/lib/jsonapi/callbacks.rb +2 -0
- data/lib/jsonapi/compiled_json.rb +2 -0
- data/lib/jsonapi/configuration.rb +35 -15
- data/lib/jsonapi/error.rb +2 -0
- data/lib/jsonapi/error_codes.rb +2 -0
- data/lib/jsonapi/exceptions.rb +2 -0
- data/lib/jsonapi/formatter.rb +2 -0
- data/lib/jsonapi/include_directives.rb +77 -19
- data/lib/jsonapi/link_builder.rb +2 -0
- data/lib/jsonapi/mime_types.rb +6 -10
- data/lib/jsonapi/naive_cache.rb +2 -0
- data/lib/jsonapi/operation.rb +2 -0
- data/lib/jsonapi/operation_result.rb +2 -0
- data/lib/jsonapi/paginator.rb +2 -17
- data/lib/jsonapi/path.rb +2 -0
- data/lib/jsonapi/path_segment.rb +4 -2
- data/lib/jsonapi/processor.rb +100 -153
- data/lib/jsonapi/relationship.rb +89 -35
- data/lib/jsonapi/{request_parser.rb → request.rb} +157 -164
- data/lib/jsonapi/resource.rb +7 -2
- data/lib/jsonapi/{basic_resource.rb → resource_common.rb} +187 -88
- data/lib/jsonapi/resource_controller.rb +2 -0
- data/lib/jsonapi/resource_controller_metal.rb +2 -0
- data/lib/jsonapi/resource_fragment.rb +17 -15
- data/lib/jsonapi/resource_identity.rb +6 -0
- data/lib/jsonapi/resource_serializer.rb +20 -4
- data/lib/jsonapi/resource_set.rb +36 -16
- data/lib/jsonapi/resource_tree.rb +191 -0
- data/lib/jsonapi/resources/railtie.rb +3 -1
- data/lib/jsonapi/resources/version.rb +3 -1
- data/lib/jsonapi/response_document.rb +4 -2
- data/lib/jsonapi/routing_ext.rb +4 -2
- data/lib/jsonapi/simple_resource.rb +13 -0
- data/lib/jsonapi-resources.rb +10 -4
- data/lib/tasks/check_upgrade.rake +3 -1
- metadata +47 -15
- 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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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(
|
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.
|
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.
|
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.
|
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
|
-
|
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
|
-
|
308
|
-
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
-
|
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
|
-
|
572
|
-
|
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
|
-
|
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
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
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
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
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)) && (
|
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 =
|
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
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
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
|
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
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
|
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:
|
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
|
-
|
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
|
-
|
1090
|
-
|
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
|
|
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, :
|
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 =
|
23
|
-
@
|
23
|
+
@cache = cache
|
24
|
+
@resource = resource
|
25
|
+
@primary = primary
|
26
|
+
|
24
27
|
@related = {}
|
25
|
-
@
|
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
|
40
|
-
|
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
|
44
|
-
@
|
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
|
25
|
-
|
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.
|
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
|