active_model_serializers 0.10.0 → 0.10.7
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 +5 -5
- data/.rubocop.yml +6 -5
- data/.travis.yml +30 -21
- data/CHANGELOG.md +172 -2
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +23 -4
- data/README.md +166 -28
- data/Rakefile +3 -32
- data/active_model_serializers.gemspec +22 -25
- data/appveyor.yml +10 -6
- data/bin/rubocop +38 -0
- data/docs/README.md +2 -1
- data/docs/general/adapters.md +35 -11
- data/docs/general/caching.md +7 -1
- data/docs/general/configuration_options.md +86 -1
- data/docs/general/deserialization.md +1 -1
- data/docs/general/fields.md +31 -0
- data/docs/general/getting_started.md +1 -1
- data/docs/general/logging.md +7 -0
- data/docs/general/rendering.md +63 -25
- data/docs/general/serializers.md +125 -14
- data/docs/howto/add_pagination_links.md +16 -17
- data/docs/howto/add_relationship_links.md +140 -0
- data/docs/howto/add_root_key.md +11 -0
- data/docs/howto/grape_integration.md +42 -0
- data/docs/howto/outside_controller_use.md +12 -4
- data/docs/howto/passing_arbitrary_options.md +2 -2
- data/docs/howto/serialize_poro.md +46 -5
- data/docs/howto/test.md +2 -0
- data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
- data/docs/integrations/ember-and-json-api.md +67 -32
- data/docs/jsonapi/schema.md +1 -1
- data/lib/action_controller/serialization.rb +13 -3
- data/lib/active_model/serializer/adapter/base.rb +2 -0
- data/lib/active_model/serializer/array_serializer.rb +8 -5
- data/lib/active_model/serializer/association.rb +62 -10
- data/lib/active_model/serializer/belongs_to_reflection.rb +4 -3
- data/lib/active_model/serializer/collection_serializer.rb +39 -13
- data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +82 -115
- data/lib/active_model/serializer/error_serializer.rb +11 -7
- data/lib/active_model/serializer/errors_serializer.rb +25 -20
- data/lib/active_model/serializer/has_many_reflection.rb +3 -3
- data/lib/active_model/serializer/has_one_reflection.rb +1 -4
- data/lib/active_model/serializer/lazy_association.rb +95 -0
- data/lib/active_model/serializer/lint.rb +134 -130
- data/lib/active_model/serializer/reflection.rb +127 -67
- data/lib/active_model/serializer/version.rb +1 -1
- data/lib/active_model/serializer.rb +297 -79
- data/lib/active_model_serializers/adapter/attributes.rb +3 -66
- data/lib/active_model_serializers/adapter/base.rb +39 -39
- data/lib/active_model_serializers/adapter/json_api/deserialization.rb +2 -2
- data/lib/active_model_serializers/adapter/json_api/link.rb +1 -1
- data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +47 -21
- data/lib/active_model_serializers/adapter/json_api/relationship.rb +75 -23
- data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +39 -10
- data/lib/active_model_serializers/adapter/json_api.rb +71 -57
- data/lib/active_model_serializers/adapter.rb +6 -0
- data/lib/active_model_serializers/deprecate.rb +1 -2
- data/lib/active_model_serializers/deserialization.rb +2 -0
- data/lib/active_model_serializers/lookup_chain.rb +80 -0
- data/lib/active_model_serializers/model.rb +109 -28
- data/lib/active_model_serializers/railtie.rb +3 -1
- data/lib/active_model_serializers/register_jsonapi_renderer.rb +44 -31
- data/lib/active_model_serializers/serializable_resource.rb +6 -5
- data/lib/active_model_serializers/serialization_context.rb +10 -3
- data/lib/active_model_serializers/test/schema.rb +2 -2
- data/lib/active_model_serializers.rb +16 -1
- data/lib/generators/rails/resource_override.rb +1 -1
- data/lib/generators/rails/serializer_generator.rb +4 -4
- data/lib/grape/active_model_serializers.rb +7 -5
- data/lib/grape/formatters/active_model_serializers.rb +19 -2
- data/lib/grape/helpers/active_model_serializers.rb +1 -0
- data/lib/tasks/rubocop.rake +53 -0
- data/test/action_controller/adapter_selector_test.rb +14 -5
- data/test/action_controller/explicit_serializer_test.rb +5 -4
- data/test/action_controller/json/include_test.rb +106 -27
- data/test/action_controller/json_api/deserialization_test.rb +1 -1
- data/test/action_controller/json_api/errors_test.rb +8 -9
- data/test/action_controller/json_api/fields_test.rb +66 -0
- data/test/action_controller/json_api/linked_test.rb +29 -24
- data/test/action_controller/json_api/pagination_test.rb +31 -23
- data/test/action_controller/json_api/transform_test.rb +11 -3
- data/test/action_controller/lookup_proc_test.rb +49 -0
- data/test/action_controller/namespace_lookup_test.rb +232 -0
- data/test/action_controller/serialization_scope_name_test.rb +12 -6
- data/test/action_controller/serialization_test.rb +12 -9
- data/test/active_model_serializers/json_pointer_test.rb +15 -13
- data/test/active_model_serializers/model_test.rb +137 -4
- data/test/active_model_serializers/railtie_test_isolated.rb +12 -7
- data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
- data/test/active_model_serializers/serialization_context_test_isolated.rb +23 -10
- data/test/active_model_serializers/test/schema_test.rb +3 -2
- data/test/adapter/attributes_test.rb +40 -0
- data/test/adapter/json/collection_test.rb +14 -0
- data/test/adapter/json/has_many_test.rb +10 -2
- data/test/adapter/json/transform_test.rb +15 -15
- data/test/adapter/json_api/collection_test.rb +4 -3
- data/test/adapter/json_api/errors_test.rb +17 -19
- data/test/adapter/json_api/fields_test.rb +12 -3
- data/test/adapter/json_api/has_many_test.rb +49 -20
- data/test/adapter/json_api/include_data_if_sideloaded_test.rb +213 -0
- data/test/adapter/json_api/json_api_test.rb +5 -7
- data/test/adapter/json_api/linked_test.rb +33 -12
- data/test/adapter/json_api/links_test.rb +4 -2
- data/test/adapter/json_api/pagination_links_test.rb +53 -13
- data/test/adapter/json_api/parse_test.rb +1 -1
- data/test/adapter/json_api/relationship_test.rb +309 -73
- data/test/adapter/json_api/resource_meta_test.rb +3 -3
- data/test/adapter/json_api/transform_test.rb +263 -253
- data/test/adapter/json_api/type_test.rb +168 -36
- data/test/adapter/json_test.rb +8 -7
- data/test/adapter/null_test.rb +1 -2
- data/test/adapter/polymorphic_test.rb +52 -5
- data/test/adapter_test.rb +1 -1
- data/test/benchmark/app.rb +1 -1
- data/test/benchmark/benchmarking_support.rb +1 -1
- data/test/benchmark/bm_active_record.rb +81 -0
- data/test/benchmark/bm_adapter.rb +38 -0
- data/test/benchmark/bm_caching.rb +16 -16
- data/test/benchmark/bm_lookup_chain.rb +83 -0
- data/test/benchmark/bm_transform.rb +21 -10
- data/test/benchmark/controllers.rb +16 -17
- data/test/benchmark/fixtures.rb +72 -72
- data/test/cache_test.rb +235 -69
- data/test/collection_serializer_test.rb +31 -14
- data/test/fixtures/active_record.rb +45 -10
- data/test/fixtures/poro.rb +124 -181
- data/test/generators/serializer_generator_test.rb +23 -5
- data/test/grape_test.rb +170 -56
- data/test/lint_test.rb +1 -1
- data/test/logger_test.rb +13 -11
- data/test/serializable_resource_test.rb +18 -22
- data/test/serializers/association_macros_test.rb +3 -2
- data/test/serializers/associations_test.rb +222 -49
- data/test/serializers/attribute_test.rb +5 -3
- data/test/serializers/attributes_test.rb +1 -1
- data/test/serializers/caching_configuration_test_isolated.rb +6 -6
- data/test/serializers/fieldset_test.rb +1 -1
- data/test/serializers/meta_test.rb +12 -6
- data/test/serializers/options_test.rb +17 -6
- data/test/serializers/read_attribute_for_serialization_test.rb +3 -3
- data/test/serializers/reflection_test.rb +427 -0
- data/test/serializers/root_test.rb +1 -1
- data/test/serializers/serialization_test.rb +2 -2
- data/test/serializers/serializer_for_test.rb +12 -10
- data/test/serializers/serializer_for_with_namespace_test.rb +88 -0
- data/test/support/isolated_unit.rb +9 -4
- data/test/support/rails5_shims.rb +8 -2
- data/test/support/rails_app.rb +2 -9
- data/test/support/serialization_testing.rb +31 -5
- data/test/test_helper.rb +13 -0
- metadata +130 -71
- data/.rubocop_todo.yml +0 -167
- data/docs/ARCHITECTURE.md +0 -126
- data/lib/active_model/serializer/associations.rb +0 -100
- data/lib/active_model/serializer/attributes.rb +0 -82
- data/lib/active_model/serializer/collection_reflection.rb +0 -7
- data/lib/active_model/serializer/configuration.rb +0 -35
- data/lib/active_model/serializer/include_tree.rb +0 -111
- data/lib/active_model/serializer/links.rb +0 -35
- data/lib/active_model/serializer/meta.rb +0 -29
- data/lib/active_model/serializer/singular_reflection.rb +0 -7
- data/lib/active_model/serializer/type.rb +0 -25
- data/lib/active_model_serializers/key_transform.rb +0 -70
- data/test/active_model_serializers/key_transform_test.rb +0 -263
- data/test/adapter/json_api/has_many_embed_ids_test.rb +0 -43
- data/test/adapter/json_api/relationships_test.rb +0 -199
- data/test/adapter/json_api/resource_identifier_test.rb +0 -85
- data/test/include_tree/from_include_args_test.rb +0 -26
- data/test/include_tree/from_string_test.rb +0 -94
- data/test/include_tree/include_args_to_hash_test.rb +0 -64
|
@@ -2,6 +2,7 @@ module ActiveModelSerializers
|
|
|
2
2
|
module Adapter
|
|
3
3
|
class JsonApi < Base
|
|
4
4
|
class PaginationLinks
|
|
5
|
+
MissingSerializationContextError = Class.new(KeyError)
|
|
5
6
|
FIRST_PAGE = 1
|
|
6
7
|
|
|
7
8
|
attr_reader :collection, :context
|
|
@@ -9,16 +10,23 @@ module ActiveModelSerializers
|
|
|
9
10
|
def initialize(collection, adapter_options)
|
|
10
11
|
@collection = collection
|
|
11
12
|
@adapter_options = adapter_options
|
|
12
|
-
@context = adapter_options.fetch(:serialization_context)
|
|
13
|
+
@context = adapter_options.fetch(:serialization_context) do
|
|
14
|
+
fail MissingSerializationContextError, <<-EOF.freeze
|
|
15
|
+
JsonApi::PaginationLinks requires a ActiveModelSerializers::SerializationContext.
|
|
16
|
+
Please pass a ':serialization_context' option or
|
|
17
|
+
override CollectionSerializer#paginated? to return 'false'.
|
|
18
|
+
EOF
|
|
19
|
+
end
|
|
13
20
|
end
|
|
14
21
|
|
|
15
22
|
def as_json
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
{
|
|
24
|
+
self: location_url,
|
|
25
|
+
first: first_page_url,
|
|
26
|
+
prev: prev_page_url,
|
|
27
|
+
next: next_page_url,
|
|
28
|
+
last: last_page_url
|
|
29
|
+
}
|
|
22
30
|
end
|
|
23
31
|
|
|
24
32
|
protected
|
|
@@ -27,25 +35,39 @@ module ActiveModelSerializers
|
|
|
27
35
|
|
|
28
36
|
private
|
|
29
37
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{}.tap do |pages|
|
|
34
|
-
pages[:self] = collection.current_page
|
|
38
|
+
def location_url
|
|
39
|
+
url_for_page(collection.current_page)
|
|
40
|
+
end
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
end
|
|
42
|
+
def first_page_url
|
|
43
|
+
url_for_page(1)
|
|
44
|
+
end
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
def last_page_url
|
|
47
|
+
if collection.total_pages == 0
|
|
48
|
+
url_for_page(FIRST_PAGE)
|
|
49
|
+
else
|
|
50
|
+
url_for_page(collection.total_pages)
|
|
45
51
|
end
|
|
46
52
|
end
|
|
47
53
|
|
|
48
|
-
def
|
|
54
|
+
def prev_page_url
|
|
55
|
+
return nil if collection.current_page == FIRST_PAGE
|
|
56
|
+
url_for_page(collection.current_page - FIRST_PAGE)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def next_page_url
|
|
60
|
+
return nil if collection.total_pages == 0 || collection.current_page == collection.total_pages
|
|
61
|
+
url_for_page(collection.next_page)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def url_for_page(number)
|
|
65
|
+
params = query_parameters.dup
|
|
66
|
+
params[:page] = { size: per_page, number: number }
|
|
67
|
+
"#{url(adapter_options)}?#{params.to_query}"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def url(options = {})
|
|
49
71
|
@url ||= options.fetch(:links, {}).fetch(:self, nil) || request_url
|
|
50
72
|
end
|
|
51
73
|
|
|
@@ -56,6 +78,10 @@ module ActiveModelSerializers
|
|
|
56
78
|
def query_parameters
|
|
57
79
|
@query_parameters ||= context.query_parameters
|
|
58
80
|
end
|
|
81
|
+
|
|
82
|
+
def per_page
|
|
83
|
+
@per_page ||= collection.try(:per_page) || collection.try(:limit_value) || collection.size
|
|
84
|
+
end
|
|
59
85
|
end
|
|
60
86
|
end
|
|
61
87
|
end
|
|
@@ -5,47 +5,99 @@ module ActiveModelSerializers
|
|
|
5
5
|
# {http://jsonapi.org/format/#document-resource-object-related-resource-links Document Resource Object Related Resource Links}
|
|
6
6
|
# {http://jsonapi.org/format/#document-links Document Links}
|
|
7
7
|
# {http://jsonapi.org/format/#document-resource-object-linkage Document Resource Relationship Linkage}
|
|
8
|
-
# {http://jsonapi.org/format/#document-meta
|
|
9
|
-
def initialize(parent_serializer,
|
|
10
|
-
@
|
|
11
|
-
@
|
|
12
|
-
@association_options = args.fetch(:options, {})
|
|
8
|
+
# {http://jsonapi.org/format/#document-meta Document Meta}
|
|
9
|
+
def initialize(parent_serializer, serializable_resource_options, association)
|
|
10
|
+
@parent_serializer = parent_serializer
|
|
11
|
+
@association = association
|
|
13
12
|
@serializable_resource_options = serializable_resource_options
|
|
14
|
-
@data = data_for(serializer)
|
|
15
|
-
@links = args.fetch(:links, {}).each_with_object({}) do |(key, value), hash|
|
|
16
|
-
hash[key] = ActiveModelSerializers::Adapter::JsonApi::Link.new(parent_serializer, value).as_json
|
|
17
|
-
end
|
|
18
|
-
meta = args.fetch(:meta, nil)
|
|
19
|
-
@meta = meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
|
|
20
13
|
end
|
|
21
14
|
|
|
22
15
|
def as_json
|
|
23
16
|
hash = {}
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
|
|
18
|
+
hash[:data] = data_for(association) if association.include_data?
|
|
19
|
+
|
|
20
|
+
links = links_for(association)
|
|
26
21
|
hash[:links] = links if links.any?
|
|
27
|
-
|
|
22
|
+
|
|
23
|
+
meta = meta_for(association)
|
|
28
24
|
hash[:meta] = meta if meta
|
|
25
|
+
hash[:meta] = {} if hash.empty?
|
|
29
26
|
|
|
30
27
|
hash
|
|
31
28
|
end
|
|
32
29
|
|
|
33
30
|
protected
|
|
34
31
|
|
|
35
|
-
attr_reader :
|
|
36
|
-
:association_options, :links, :meta
|
|
32
|
+
attr_reader :parent_serializer, :serializable_resource_options, :association
|
|
37
33
|
|
|
38
34
|
private
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
ResourceIdentifier.new(serializer, serializable_resource_options).as_json
|
|
36
|
+
# TODO(BF): Avoid db hit on belong_to_ releationship by using foreign_key on self
|
|
37
|
+
def data_for(association)
|
|
38
|
+
if association.collection?
|
|
39
|
+
data_for_many(association)
|
|
40
|
+
else
|
|
41
|
+
data_for_one(association)
|
|
47
42
|
end
|
|
48
43
|
end
|
|
44
|
+
|
|
45
|
+
def data_for_one(association)
|
|
46
|
+
if belongs_to_id_on_self?(association)
|
|
47
|
+
id = parent_serializer.read_attribute_for_serialization(association.reflection.foreign_key)
|
|
48
|
+
type =
|
|
49
|
+
if association.polymorphic?
|
|
50
|
+
# We can't infer resource type for polymorphic relationships from the serializer.
|
|
51
|
+
# We can ONLY know a polymorphic resource type by inspecting each resource.
|
|
52
|
+
association.lazy_association.serializer.json_key
|
|
53
|
+
else
|
|
54
|
+
association.reflection.type.to_s
|
|
55
|
+
end
|
|
56
|
+
ResourceIdentifier.for_type_with_id(type, id, serializable_resource_options)
|
|
57
|
+
else
|
|
58
|
+
# TODO(BF): Process relationship without evaluating lazy_association
|
|
59
|
+
serializer = association.lazy_association.serializer
|
|
60
|
+
if (virtual_value = association.virtual_value)
|
|
61
|
+
virtual_value
|
|
62
|
+
elsif serializer && association.object
|
|
63
|
+
ResourceIdentifier.new(serializer, serializable_resource_options).as_json
|
|
64
|
+
else
|
|
65
|
+
nil
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def data_for_many(association)
|
|
71
|
+
# TODO(BF): Process relationship without evaluating lazy_association
|
|
72
|
+
collection_serializer = association.lazy_association.serializer
|
|
73
|
+
if collection_serializer.respond_to?(:each)
|
|
74
|
+
collection_serializer.map do |serializer|
|
|
75
|
+
ResourceIdentifier.new(serializer, serializable_resource_options).as_json
|
|
76
|
+
end
|
|
77
|
+
elsif (virtual_value = association.virtual_value)
|
|
78
|
+
virtual_value
|
|
79
|
+
else
|
|
80
|
+
[]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def links_for(association)
|
|
85
|
+
association.links.each_with_object({}) do |(key, value), hash|
|
|
86
|
+
result = Link.new(parent_serializer, value).as_json
|
|
87
|
+
hash[key] = result if result
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def meta_for(association)
|
|
92
|
+
meta = association.meta
|
|
93
|
+
meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def belongs_to_id_on_self?(association)
|
|
97
|
+
parent_serializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship &&
|
|
98
|
+
association.belongs_to? &&
|
|
99
|
+
parent_serializer.object.respond_to?(association.reflection.foreign_key)
|
|
100
|
+
end
|
|
49
101
|
end
|
|
50
102
|
end
|
|
51
103
|
end
|
|
@@ -2,15 +2,48 @@ module ActiveModelSerializers
|
|
|
2
2
|
module Adapter
|
|
3
3
|
class JsonApi
|
|
4
4
|
class ResourceIdentifier
|
|
5
|
+
def self.type_for(serializer, serializer_type = nil, transform_options = {})
|
|
6
|
+
raw_type = serializer_type ? serializer_type : raw_type_from_serializer_object(serializer.object)
|
|
7
|
+
JsonApi.send(:transform_key_casing!, raw_type, transform_options)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.for_type_with_id(type, id, options)
|
|
11
|
+
type = inflect_type(type)
|
|
12
|
+
type = type_for(:no_class_needed, type, options)
|
|
13
|
+
if id.blank?
|
|
14
|
+
{ type: type }
|
|
15
|
+
else
|
|
16
|
+
{ id: id.to_s, type: type }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.raw_type_from_serializer_object(object)
|
|
21
|
+
class_name = object.class.name # should use model_name
|
|
22
|
+
raw_type = class_name.underscore
|
|
23
|
+
raw_type = inflect_type(raw_type)
|
|
24
|
+
raw_type
|
|
25
|
+
.gsub!('/'.freeze, ActiveModelSerializers.config.jsonapi_namespace_separator)
|
|
26
|
+
raw_type
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.inflect_type(type)
|
|
30
|
+
singularize = ActiveModelSerializers.config.jsonapi_resource_type == :singular
|
|
31
|
+
inflection = singularize ? :singularize : :pluralize
|
|
32
|
+
ActiveSupport::Inflector.public_send(inflection, type)
|
|
33
|
+
end
|
|
34
|
+
|
|
5
35
|
# {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects}
|
|
6
36
|
def initialize(serializer, options)
|
|
7
37
|
@id = id_for(serializer)
|
|
8
|
-
@type =
|
|
9
|
-
options)
|
|
38
|
+
@type = type_for(serializer, options)
|
|
10
39
|
end
|
|
11
40
|
|
|
12
41
|
def as_json
|
|
13
|
-
|
|
42
|
+
if id.blank?
|
|
43
|
+
{ type: type }
|
|
44
|
+
else
|
|
45
|
+
{ id: id.to_s, type: type }
|
|
46
|
+
end
|
|
14
47
|
end
|
|
15
48
|
|
|
16
49
|
protected
|
|
@@ -19,13 +52,9 @@ module ActiveModelSerializers
|
|
|
19
52
|
|
|
20
53
|
private
|
|
21
54
|
|
|
22
|
-
def type_for(serializer)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
serializer.object.class.model_name.singular
|
|
26
|
-
else
|
|
27
|
-
serializer.object.class.model_name.plural
|
|
28
|
-
end
|
|
55
|
+
def type_for(serializer, transform_options)
|
|
56
|
+
serializer_type = serializer._type
|
|
57
|
+
self.class.type_for(serializer, serializer_type, transform_options)
|
|
29
58
|
end
|
|
30
59
|
|
|
31
60
|
def id_for(serializer)
|
|
@@ -31,16 +31,27 @@ module ActiveModelSerializers
|
|
|
31
31
|
autoload :Error
|
|
32
32
|
autoload :Deserialization
|
|
33
33
|
|
|
34
|
+
def self.default_key_transform
|
|
35
|
+
:dash
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.fragment_cache(cached_hash, non_cached_hash, root = true)
|
|
39
|
+
core_cached = cached_hash.first
|
|
40
|
+
core_non_cached = non_cached_hash.first
|
|
41
|
+
no_root_cache = cached_hash.delete_if { |key, _value| key == core_cached[0] }
|
|
42
|
+
no_root_non_cache = non_cached_hash.delete_if { |key, _value| key == core_non_cached[0] }
|
|
43
|
+
cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
|
|
44
|
+
hash = root ? { root => cached_resource } : cached_resource
|
|
45
|
+
|
|
46
|
+
hash.deep_merge no_root_non_cache.deep_merge no_root_cache
|
|
47
|
+
end
|
|
48
|
+
|
|
34
49
|
def initialize(serializer, options = {})
|
|
35
50
|
super
|
|
36
|
-
@
|
|
51
|
+
@include_directive = JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
|
|
37
52
|
@fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
|
|
38
53
|
end
|
|
39
54
|
|
|
40
|
-
def self.default_key_transform
|
|
41
|
-
:dash
|
|
42
|
-
end
|
|
43
|
-
|
|
44
55
|
# {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
|
|
45
56
|
# {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.}
|
|
46
57
|
def serializable_hash(*)
|
|
@@ -52,6 +63,11 @@ module ActiveModelSerializers
|
|
|
52
63
|
self.class.transform_key_casing!(document, instance_options)
|
|
53
64
|
end
|
|
54
65
|
|
|
66
|
+
def fragment_cache(cached_hash, non_cached_hash)
|
|
67
|
+
root = !instance_options.include?(:include)
|
|
68
|
+
self.class.fragment_cache(cached_hash, non_cached_hash, root)
|
|
69
|
+
end
|
|
70
|
+
|
|
55
71
|
# {http://jsonapi.org/format/#document-top-level Primary data}
|
|
56
72
|
# definition:
|
|
57
73
|
# ☐ toplevel_data (required)
|
|
@@ -174,18 +190,6 @@ module ActiveModelSerializers
|
|
|
174
190
|
hash
|
|
175
191
|
end
|
|
176
192
|
|
|
177
|
-
def fragment_cache(cached_hash, non_cached_hash)
|
|
178
|
-
root = false if instance_options.include?(:include)
|
|
179
|
-
core_cached = cached_hash.first
|
|
180
|
-
core_non_cached = non_cached_hash.first
|
|
181
|
-
no_root_cache = cached_hash.delete_if { |key, _value| key == core_cached[0] }
|
|
182
|
-
no_root_non_cache = non_cached_hash.delete_if { |key, _value| key == core_non_cached[0] }
|
|
183
|
-
cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
|
|
184
|
-
hash = root ? { root => cached_resource } : cached_resource
|
|
185
|
-
|
|
186
|
-
hash.deep_merge no_root_non_cache.deep_merge no_root_cache
|
|
187
|
-
end
|
|
188
|
-
|
|
189
193
|
protected
|
|
190
194
|
|
|
191
195
|
attr_reader :fieldset
|
|
@@ -231,17 +235,17 @@ module ActiveModelSerializers
|
|
|
231
235
|
@primary = []
|
|
232
236
|
@included = []
|
|
233
237
|
@resource_identifiers = Set.new
|
|
234
|
-
serializers.each { |serializer| process_resource(serializer, true) }
|
|
235
|
-
serializers.each { |serializer| process_relationships(serializer, @
|
|
238
|
+
serializers.each { |serializer| process_resource(serializer, true, @include_directive) }
|
|
239
|
+
serializers.each { |serializer| process_relationships(serializer, @include_directive) }
|
|
236
240
|
|
|
237
241
|
[@primary, @included]
|
|
238
242
|
end
|
|
239
243
|
|
|
240
|
-
def process_resource(serializer, primary)
|
|
244
|
+
def process_resource(serializer, primary, include_slice = {})
|
|
241
245
|
resource_identifier = ResourceIdentifier.new(serializer, instance_options).as_json
|
|
242
246
|
return false unless @resource_identifiers.add?(resource_identifier)
|
|
243
247
|
|
|
244
|
-
resource_object = resource_object_for(serializer)
|
|
248
|
+
resource_object = resource_object_for(serializer, include_slice)
|
|
245
249
|
if primary
|
|
246
250
|
@primary << resource_object
|
|
247
251
|
else
|
|
@@ -251,21 +255,22 @@ module ActiveModelSerializers
|
|
|
251
255
|
true
|
|
252
256
|
end
|
|
253
257
|
|
|
254
|
-
def process_relationships(serializer,
|
|
255
|
-
serializer.associations(
|
|
256
|
-
|
|
258
|
+
def process_relationships(serializer, include_slice)
|
|
259
|
+
serializer.associations(include_slice).each do |association|
|
|
260
|
+
# TODO(BF): Process relationship without evaluating lazy_association
|
|
261
|
+
process_relationship(association.lazy_association.serializer, include_slice[association.key])
|
|
257
262
|
end
|
|
258
263
|
end
|
|
259
264
|
|
|
260
|
-
def process_relationship(serializer,
|
|
265
|
+
def process_relationship(serializer, include_slice)
|
|
261
266
|
if serializer.respond_to?(:each)
|
|
262
|
-
serializer.each { |s| process_relationship(s,
|
|
267
|
+
serializer.each { |s| process_relationship(s, include_slice) }
|
|
263
268
|
return
|
|
264
269
|
end
|
|
265
270
|
return unless serializer && serializer.object
|
|
266
|
-
return unless process_resource(serializer, false)
|
|
271
|
+
return unless process_resource(serializer, false, include_slice)
|
|
267
272
|
|
|
268
|
-
process_relationships(serializer,
|
|
273
|
+
process_relationships(serializer, include_slice)
|
|
269
274
|
end
|
|
270
275
|
|
|
271
276
|
# {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
|
|
@@ -289,21 +294,9 @@ module ActiveModelSerializers
|
|
|
289
294
|
end
|
|
290
295
|
|
|
291
296
|
# {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
|
|
292
|
-
def resource_object_for(serializer)
|
|
293
|
-
resource_object =
|
|
294
|
-
resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
|
|
295
|
-
|
|
296
|
-
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
|
|
297
|
-
attributes = attributes_for(serializer, requested_fields)
|
|
298
|
-
resource_object[:attributes] = attributes if attributes.any?
|
|
299
|
-
resource_object
|
|
300
|
-
end
|
|
297
|
+
def resource_object_for(serializer, include_slice = {})
|
|
298
|
+
resource_object = data_for(serializer, include_slice)
|
|
301
299
|
|
|
302
|
-
requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
|
|
303
|
-
relationships = relationships_for(serializer, requested_associations)
|
|
304
|
-
resource_object[:relationships] = relationships if relationships.any?
|
|
305
|
-
|
|
306
|
-
links = links_for(serializer)
|
|
307
300
|
# toplevel_links
|
|
308
301
|
# definition:
|
|
309
302
|
# allOf
|
|
@@ -317,7 +310,10 @@ module ActiveModelSerializers
|
|
|
317
310
|
# prs:
|
|
318
311
|
# https://github.com/rails-api/active_model_serializers/pull/1247
|
|
319
312
|
# https://github.com/rails-api/active_model_serializers/pull/1018
|
|
320
|
-
|
|
313
|
+
if (links = links_for(serializer)).any?
|
|
314
|
+
resource_object ||= {}
|
|
315
|
+
resource_object[:links] = links
|
|
316
|
+
end
|
|
321
317
|
|
|
322
318
|
# toplevel_meta
|
|
323
319
|
# alias meta
|
|
@@ -327,12 +323,33 @@ module ActiveModelSerializers
|
|
|
327
323
|
# {
|
|
328
324
|
# :'git-ref' => 'abc123'
|
|
329
325
|
# }
|
|
330
|
-
meta = meta_for(serializer)
|
|
331
|
-
|
|
326
|
+
if (meta = meta_for(serializer)).present?
|
|
327
|
+
resource_object ||= {}
|
|
328
|
+
resource_object[:meta] = meta
|
|
329
|
+
end
|
|
332
330
|
|
|
333
331
|
resource_object
|
|
334
332
|
end
|
|
335
333
|
|
|
334
|
+
def data_for(serializer, include_slice)
|
|
335
|
+
data = serializer.fetch(self) do
|
|
336
|
+
resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
|
|
337
|
+
break nil if resource_object.nil?
|
|
338
|
+
|
|
339
|
+
requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
|
|
340
|
+
attributes = attributes_for(serializer, requested_fields)
|
|
341
|
+
resource_object[:attributes] = attributes if attributes.any?
|
|
342
|
+
resource_object
|
|
343
|
+
end
|
|
344
|
+
data.tap do |resource_object|
|
|
345
|
+
next if resource_object.nil?
|
|
346
|
+
# NOTE(BF): the attributes are cached above, separately from the relationships, below.
|
|
347
|
+
requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
|
|
348
|
+
relationships = relationships_for(serializer, requested_associations, include_slice)
|
|
349
|
+
resource_object[:relationships] = relationships if relationships.any?
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
336
353
|
# {http://jsonapi.org/format/#document-resource-object-relationships Document Resource Object Relationship}
|
|
337
354
|
# relationships
|
|
338
355
|
# definition:
|
|
@@ -428,17 +445,13 @@ module ActiveModelSerializers
|
|
|
428
445
|
# id: 'required-id',
|
|
429
446
|
# meta: meta
|
|
430
447
|
# }.reject! {|_,v| v.nil? }
|
|
431
|
-
def relationships_for(serializer, requested_associations)
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
options: association.options,
|
|
439
|
-
links: association.links,
|
|
440
|
-
meta: association.meta
|
|
441
|
-
).as_json
|
|
448
|
+
def relationships_for(serializer, requested_associations, include_slice)
|
|
449
|
+
include_directive = JSONAPI::IncludeDirective.new(
|
|
450
|
+
requested_associations,
|
|
451
|
+
allow_wildcard: true
|
|
452
|
+
)
|
|
453
|
+
serializer.associations(include_directive, include_slice).each_with_object({}) do |association, hash|
|
|
454
|
+
hash[association.key] = Relationship.new(serializer, instance_options, association).as_json
|
|
442
455
|
end
|
|
443
456
|
end
|
|
444
457
|
|
|
@@ -467,7 +480,8 @@ module ActiveModelSerializers
|
|
|
467
480
|
# }.reject! {|_,v| v.nil? }
|
|
468
481
|
def links_for(serializer)
|
|
469
482
|
serializer._links.each_with_object({}) do |(name, value), hash|
|
|
470
|
-
|
|
483
|
+
result = Link.new(serializer, value).as_json
|
|
484
|
+
hash[name] = result if result
|
|
471
485
|
end
|
|
472
486
|
end
|
|
473
487
|
|
|
@@ -5,11 +5,13 @@ module ActiveModelSerializers
|
|
|
5
5
|
private_constant :ADAPTER_MAP if defined?(private_constant)
|
|
6
6
|
|
|
7
7
|
class << self # All methods are class functions
|
|
8
|
+
# :nocov:
|
|
8
9
|
def new(*args)
|
|
9
10
|
fail ArgumentError, 'Adapters inherit from Adapter::Base.' \
|
|
10
11
|
"Adapter.new called with args: '#{args.inspect}', from" \
|
|
11
12
|
"'caller[0]'."
|
|
12
13
|
end
|
|
14
|
+
# :nocov:
|
|
13
15
|
|
|
14
16
|
def configured_adapter
|
|
15
17
|
lookup(ActiveModelSerializers.config.adapter)
|
|
@@ -51,6 +53,10 @@ module ActiveModelSerializers
|
|
|
51
53
|
self
|
|
52
54
|
end
|
|
53
55
|
|
|
56
|
+
def registered_name(adapter_class)
|
|
57
|
+
ADAPTER_MAP.key adapter_class
|
|
58
|
+
end
|
|
59
|
+
|
|
54
60
|
# @param adapter [String, Symbol, Class] name to fetch adapter by
|
|
55
61
|
# @return [ActiveModelSerializers::Adapter] subclass of Adapter
|
|
56
62
|
# @raise [UnknownAdapterError]
|
|
@@ -36,8 +36,7 @@ module ActiveModelSerializers
|
|
|
36
36
|
target = is_a?(Module) ? "#{self}." : "#{self.class}#"
|
|
37
37
|
msg = ["NOTE: #{target}#{name} is deprecated",
|
|
38
38
|
replacement == :none ? ' with no replacement' : "; use #{replacement} instead",
|
|
39
|
-
"\n#{target}#{name} called from #{ActiveModelSerializers.location_of_caller.join(
|
|
40
|
-
]
|
|
39
|
+
"\n#{target}#{name} called from #{ActiveModelSerializers.location_of_caller.join(':')}"]
|
|
41
40
|
warn "#{msg.join}."
|
|
42
41
|
send old, *args, &block
|
|
43
42
|
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module ActiveModelSerializers
|
|
2
|
+
module LookupChain
|
|
3
|
+
# Standard appending of Serializer to the resource name.
|
|
4
|
+
#
|
|
5
|
+
# Example:
|
|
6
|
+
# Author => AuthorSerializer
|
|
7
|
+
BY_RESOURCE = lambda do |resource_class, _serializer_class, _namespace|
|
|
8
|
+
serializer_from(resource_class)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Uses the namespace of the resource to find the serializer
|
|
12
|
+
#
|
|
13
|
+
# Example:
|
|
14
|
+
# British::Author => British::AuthorSerializer
|
|
15
|
+
BY_RESOURCE_NAMESPACE = lambda do |resource_class, _serializer_class, _namespace|
|
|
16
|
+
resource_namespace = namespace_for(resource_class)
|
|
17
|
+
serializer_name = serializer_from(resource_class)
|
|
18
|
+
|
|
19
|
+
"#{resource_namespace}::#{serializer_name}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Uses the controller namespace of the resource to find the serializer
|
|
23
|
+
#
|
|
24
|
+
# Example:
|
|
25
|
+
# Api::V3::AuthorsController => Api::V3::AuthorSerializer
|
|
26
|
+
BY_NAMESPACE = lambda do |resource_class, _serializer_class, namespace|
|
|
27
|
+
resource_name = resource_class_name(resource_class)
|
|
28
|
+
namespace ? "#{namespace}::#{resource_name}Serializer" : nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Allows for serializers to be defined in parent serializers
|
|
32
|
+
# - useful if a relationship only needs a different set of attributes
|
|
33
|
+
# than if it were rendered independently.
|
|
34
|
+
#
|
|
35
|
+
# Example:
|
|
36
|
+
# class BlogSerializer < ActiveModel::Serializer
|
|
37
|
+
# class AuthorSerialier < ActiveModel::Serializer
|
|
38
|
+
# ...
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# belongs_to :author
|
|
42
|
+
# ...
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# The belongs_to relationship would be rendered with
|
|
46
|
+
# BlogSerializer::AuthorSerialier
|
|
47
|
+
BY_PARENT_SERIALIZER = lambda do |resource_class, serializer_class, _namespace|
|
|
48
|
+
return if serializer_class == ActiveModel::Serializer
|
|
49
|
+
|
|
50
|
+
serializer_name = serializer_from(resource_class)
|
|
51
|
+
"#{serializer_class}::#{serializer_name}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
DEFAULT = [
|
|
55
|
+
BY_PARENT_SERIALIZER,
|
|
56
|
+
BY_NAMESPACE,
|
|
57
|
+
BY_RESOURCE_NAMESPACE,
|
|
58
|
+
BY_RESOURCE
|
|
59
|
+
].freeze
|
|
60
|
+
|
|
61
|
+
module_function
|
|
62
|
+
|
|
63
|
+
def namespace_for(klass)
|
|
64
|
+
klass.name.deconstantize
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def resource_class_name(klass)
|
|
68
|
+
klass.name.demodulize
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def serializer_from_resource_name(name)
|
|
72
|
+
"#{name}Serializer"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def serializer_from(klass)
|
|
76
|
+
name = resource_class_name(klass)
|
|
77
|
+
serializer_from_resource_name(name)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|