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.
- 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 -0
- data/lib/jsonapi/path.rb +2 -0
- data/lib/jsonapi/path_segment.rb +4 -2
- data/lib/jsonapi/processor.rb +95 -140
- 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
|