active_model_serializers_custom 0.10.90
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 +7 -0
- data/.github/ISSUE_TEMPLATE.md +29 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- data/.gitignore +35 -0
- data/.rubocop.yml +109 -0
- data/.simplecov +110 -0
- data/.travis.yml +63 -0
- data/CHANGELOG.md +727 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +105 -0
- data/Gemfile +74 -0
- data/MIT-LICENSE +22 -0
- data/README.md +305 -0
- data/Rakefile +76 -0
- data/active_model_serializers.gemspec +64 -0
- data/appveyor.yml +28 -0
- data/bin/bench +171 -0
- data/bin/bench_regression +316 -0
- data/bin/rubocop +38 -0
- data/bin/serve_benchmark +39 -0
- data/docs/README.md +41 -0
- data/docs/STYLE.md +58 -0
- data/docs/general/adapters.md +269 -0
- data/docs/general/caching.md +58 -0
- data/docs/general/configuration_options.md +185 -0
- data/docs/general/deserialization.md +100 -0
- data/docs/general/fields.md +31 -0
- data/docs/general/getting_started.md +133 -0
- data/docs/general/instrumentation.md +40 -0
- data/docs/general/key_transforms.md +40 -0
- data/docs/general/logging.md +21 -0
- data/docs/general/rendering.md +293 -0
- data/docs/general/serializers.md +495 -0
- data/docs/how-open-source-maintained.jpg +0 -0
- data/docs/howto/add_pagination_links.md +138 -0
- data/docs/howto/add_relationship_links.md +140 -0
- data/docs/howto/add_root_key.md +62 -0
- data/docs/howto/grape_integration.md +42 -0
- data/docs/howto/outside_controller_use.md +66 -0
- data/docs/howto/passing_arbitrary_options.md +27 -0
- data/docs/howto/serialize_poro.md +73 -0
- data/docs/howto/test.md +154 -0
- data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
- data/docs/integrations/ember-and-json-api.md +147 -0
- data/docs/integrations/grape.md +19 -0
- data/docs/jsonapi/errors.md +56 -0
- data/docs/jsonapi/schema.md +151 -0
- data/docs/jsonapi/schema/schema.json +366 -0
- data/docs/rfcs/0000-namespace.md +106 -0
- data/docs/rfcs/template.md +15 -0
- data/lib/action_controller/serialization.rb +76 -0
- data/lib/active_model/serializable_resource.rb +13 -0
- data/lib/active_model/serializer.rb +418 -0
- data/lib/active_model/serializer/adapter.rb +26 -0
- data/lib/active_model/serializer/adapter/attributes.rb +17 -0
- data/lib/active_model/serializer/adapter/base.rb +20 -0
- data/lib/active_model/serializer/adapter/json.rb +17 -0
- data/lib/active_model/serializer/adapter/json_api.rb +17 -0
- data/lib/active_model/serializer/adapter/null.rb +17 -0
- data/lib/active_model/serializer/array_serializer.rb +14 -0
- data/lib/active_model/serializer/association.rb +91 -0
- data/lib/active_model/serializer/attribute.rb +27 -0
- data/lib/active_model/serializer/belongs_to_reflection.rb +13 -0
- data/lib/active_model/serializer/collection_serializer.rb +90 -0
- data/lib/active_model/serializer/concerns/caching.rb +304 -0
- data/lib/active_model/serializer/error_serializer.rb +16 -0
- data/lib/active_model/serializer/errors_serializer.rb +34 -0
- data/lib/active_model/serializer/field.rb +92 -0
- data/lib/active_model/serializer/fieldset.rb +33 -0
- data/lib/active_model/serializer/has_many_reflection.rb +12 -0
- data/lib/active_model/serializer/has_one_reflection.rb +9 -0
- data/lib/active_model/serializer/lazy_association.rb +99 -0
- data/lib/active_model/serializer/link.rb +23 -0
- data/lib/active_model/serializer/lint.rb +152 -0
- data/lib/active_model/serializer/null.rb +19 -0
- data/lib/active_model/serializer/reflection.rb +212 -0
- data/lib/active_model/serializer/version.rb +7 -0
- data/lib/active_model_serializers.rb +63 -0
- data/lib/active_model_serializers/adapter.rb +100 -0
- data/lib/active_model_serializers/adapter/attributes.rb +15 -0
- data/lib/active_model_serializers/adapter/base.rb +85 -0
- data/lib/active_model_serializers/adapter/json.rb +23 -0
- data/lib/active_model_serializers/adapter/json_api.rb +535 -0
- data/lib/active_model_serializers/adapter/json_api/deserialization.rb +215 -0
- data/lib/active_model_serializers/adapter/json_api/error.rb +98 -0
- data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +51 -0
- data/lib/active_model_serializers/adapter/json_api/link.rb +85 -0
- data/lib/active_model_serializers/adapter/json_api/meta.rb +39 -0
- data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +90 -0
- data/lib/active_model_serializers/adapter/json_api/relationship.rb +106 -0
- data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +68 -0
- data/lib/active_model_serializers/adapter/null.rb +11 -0
- data/lib/active_model_serializers/callbacks.rb +57 -0
- data/lib/active_model_serializers/deprecate.rb +56 -0
- data/lib/active_model_serializers/deserialization.rb +17 -0
- data/lib/active_model_serializers/json_pointer.rb +16 -0
- data/lib/active_model_serializers/logging.rb +124 -0
- data/lib/active_model_serializers/lookup_chain.rb +82 -0
- data/lib/active_model_serializers/model.rb +132 -0
- data/lib/active_model_serializers/railtie.rb +52 -0
- data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
- data/lib/active_model_serializers/serializable_resource.rb +84 -0
- data/lib/active_model_serializers/serialization_context.rb +41 -0
- data/lib/active_model_serializers/test.rb +9 -0
- data/lib/active_model_serializers/test/schema.rb +140 -0
- data/lib/active_model_serializers/test/serializer.rb +127 -0
- data/lib/generators/rails/USAGE +6 -0
- data/lib/generators/rails/resource_override.rb +12 -0
- data/lib/generators/rails/serializer_generator.rb +38 -0
- data/lib/generators/rails/templates/serializer.rb.erb +8 -0
- data/lib/grape/active_model_serializers.rb +18 -0
- data/lib/grape/formatters/active_model_serializers.rb +34 -0
- data/lib/grape/helpers/active_model_serializers.rb +19 -0
- data/lib/tasks/rubocop.rake +55 -0
- data/test/action_controller/adapter_selector_test.rb +64 -0
- data/test/action_controller/explicit_serializer_test.rb +137 -0
- data/test/action_controller/json/include_test.rb +248 -0
- data/test/action_controller/json_api/deserialization_test.rb +114 -0
- data/test/action_controller/json_api/errors_test.rb +42 -0
- data/test/action_controller/json_api/fields_test.rb +68 -0
- data/test/action_controller/json_api/linked_test.rb +204 -0
- data/test/action_controller/json_api/pagination_test.rb +126 -0
- data/test/action_controller/json_api/transform_test.rb +191 -0
- data/test/action_controller/lookup_proc_test.rb +51 -0
- data/test/action_controller/namespace_lookup_test.rb +239 -0
- data/test/action_controller/serialization_scope_name_test.rb +237 -0
- data/test/action_controller/serialization_test.rb +480 -0
- data/test/active_model_serializers/adapter_for_test.rb +210 -0
- data/test/active_model_serializers/json_pointer_test.rb +24 -0
- data/test/active_model_serializers/logging_test.rb +79 -0
- data/test/active_model_serializers/model_test.rb +144 -0
- data/test/active_model_serializers/railtie_test_isolated.rb +70 -0
- data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +163 -0
- data/test/active_model_serializers/serialization_context_test_isolated.rb +73 -0
- data/test/active_model_serializers/test/schema_test.rb +133 -0
- data/test/active_model_serializers/test/serializer_test.rb +64 -0
- data/test/active_record_test.rb +11 -0
- data/test/adapter/attributes_test.rb +42 -0
- data/test/adapter/deprecation_test.rb +102 -0
- data/test/adapter/json/belongs_to_test.rb +47 -0
- data/test/adapter/json/collection_test.rb +106 -0
- data/test/adapter/json/has_many_test.rb +55 -0
- data/test/adapter/json/transform_test.rb +95 -0
- data/test/adapter/json_api/belongs_to_test.rb +157 -0
- data/test/adapter/json_api/collection_test.rb +98 -0
- data/test/adapter/json_api/errors_test.rb +78 -0
- data/test/adapter/json_api/fields_test.rb +98 -0
- data/test/adapter/json_api/has_many_explicit_serializer_test.rb +98 -0
- data/test/adapter/json_api/has_many_test.rb +175 -0
- data/test/adapter/json_api/has_one_test.rb +82 -0
- data/test/adapter/json_api/include_data_if_sideloaded_test.rb +215 -0
- data/test/adapter/json_api/json_api_test.rb +35 -0
- data/test/adapter/json_api/linked_test.rb +415 -0
- data/test/adapter/json_api/links_test.rb +112 -0
- data/test/adapter/json_api/pagination_links_test.rb +208 -0
- data/test/adapter/json_api/parse_test.rb +139 -0
- data/test/adapter/json_api/relationship_test.rb +399 -0
- data/test/adapter/json_api/resource_meta_test.rb +102 -0
- data/test/adapter/json_api/toplevel_jsonapi_test.rb +84 -0
- data/test/adapter/json_api/transform_test.rb +514 -0
- data/test/adapter/json_api/type_test.rb +195 -0
- data/test/adapter/json_test.rb +48 -0
- data/test/adapter/null_test.rb +24 -0
- data/test/adapter/polymorphic_test.rb +220 -0
- data/test/adapter_test.rb +69 -0
- data/test/array_serializer_test.rb +24 -0
- data/test/benchmark/app.rb +67 -0
- data/test/benchmark/benchmarking_support.rb +69 -0
- data/test/benchmark/bm_active_record.rb +83 -0
- data/test/benchmark/bm_adapter.rb +40 -0
- data/test/benchmark/bm_caching.rb +121 -0
- data/test/benchmark/bm_lookup_chain.rb +85 -0
- data/test/benchmark/bm_transform.rb +47 -0
- data/test/benchmark/config.ru +3 -0
- data/test/benchmark/controllers.rb +85 -0
- data/test/benchmark/fixtures.rb +221 -0
- data/test/cache_test.rb +717 -0
- data/test/collection_serializer_test.rb +129 -0
- data/test/fixtures/active_record.rb +115 -0
- data/test/fixtures/poro.rb +227 -0
- data/test/generators/scaffold_controller_generator_test.rb +26 -0
- data/test/generators/serializer_generator_test.rb +77 -0
- data/test/grape_test.rb +198 -0
- data/test/lint_test.rb +51 -0
- data/test/logger_test.rb +22 -0
- data/test/poro_test.rb +11 -0
- data/test/serializable_resource_test.rb +81 -0
- data/test/serializers/association_macros_test.rb +39 -0
- data/test/serializers/associations_test.rb +520 -0
- data/test/serializers/attribute_test.rb +155 -0
- data/test/serializers/attributes_test.rb +54 -0
- data/test/serializers/caching_configuration_test_isolated.rb +172 -0
- data/test/serializers/configuration_test.rb +34 -0
- data/test/serializers/fieldset_test.rb +16 -0
- data/test/serializers/meta_test.rb +204 -0
- data/test/serializers/options_test.rb +34 -0
- data/test/serializers/read_attribute_for_serialization_test.rb +81 -0
- data/test/serializers/reflection_test.rb +481 -0
- data/test/serializers/root_test.rb +23 -0
- data/test/serializers/serialization_test.rb +57 -0
- data/test/serializers/serializer_for_test.rb +138 -0
- data/test/serializers/serializer_for_with_namespace_test.rb +90 -0
- data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
- data/test/support/isolated_unit.rb +86 -0
- data/test/support/rails5_shims.rb +55 -0
- data/test/support/rails_app.rb +40 -0
- data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
- data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
- data/test/support/schemas/custom/show.json +7 -0
- data/test/support/schemas/hyper_schema.json +93 -0
- data/test/support/schemas/render_using_json_api.json +43 -0
- data/test/support/schemas/simple_json_pointers.json +10 -0
- data/test/support/serialization_testing.rb +81 -0
- data/test/test_helper.rb +72 -0
- metadata +622 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_model_serializers/adapter'
|
|
4
|
+
require 'active_model_serializers/deprecate'
|
|
5
|
+
|
|
6
|
+
module ActiveModel
|
|
7
|
+
class Serializer
|
|
8
|
+
# @deprecated Use ActiveModelSerializers::Adapter instead
|
|
9
|
+
module Adapter
|
|
10
|
+
class << self
|
|
11
|
+
extend ActiveModelSerializers::Deprecate
|
|
12
|
+
|
|
13
|
+
DEPRECATED_METHODS = [:create, :adapter_class, :adapter_map, :adapters, :register, :lookup].freeze
|
|
14
|
+
DEPRECATED_METHODS.each do |method|
|
|
15
|
+
delegate_and_deprecate method, ActiveModelSerializers::Adapter
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
require 'active_model/serializer/adapter/base'
|
|
23
|
+
require 'active_model/serializer/adapter/null'
|
|
24
|
+
require 'active_model/serializer/adapter/attributes'
|
|
25
|
+
require 'active_model/serializer/adapter/json'
|
|
26
|
+
require 'active_model/serializer/adapter/json_api'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveModel
|
|
4
|
+
class Serializer
|
|
5
|
+
module Adapter
|
|
6
|
+
class Attributes < DelegateClass(ActiveModelSerializers::Adapter::Attributes)
|
|
7
|
+
def initialize(serializer, options = {})
|
|
8
|
+
super(ActiveModelSerializers::Adapter::Attributes.new(serializer, options))
|
|
9
|
+
end
|
|
10
|
+
class << self
|
|
11
|
+
extend ActiveModelSerializers::Deprecate
|
|
12
|
+
deprecate :new, 'ActiveModelSerializers::Adapter::Json.'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveModel
|
|
4
|
+
class Serializer
|
|
5
|
+
module Adapter
|
|
6
|
+
class Base < DelegateClass(ActiveModelSerializers::Adapter::Base)
|
|
7
|
+
class << self
|
|
8
|
+
extend ActiveModelSerializers::Deprecate
|
|
9
|
+
deprecate :inherited, 'ActiveModelSerializers::Adapter::Base.'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# :nocov:
|
|
13
|
+
def initialize(serializer, options = {})
|
|
14
|
+
super(ActiveModelSerializers::Adapter::Base.new(serializer, options))
|
|
15
|
+
end
|
|
16
|
+
# :nocov:
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveModel
|
|
4
|
+
class Serializer
|
|
5
|
+
module Adapter
|
|
6
|
+
class Json < DelegateClass(ActiveModelSerializers::Adapter::Json)
|
|
7
|
+
def initialize(serializer, options = {})
|
|
8
|
+
super(ActiveModelSerializers::Adapter::Json.new(serializer, options))
|
|
9
|
+
end
|
|
10
|
+
class << self
|
|
11
|
+
extend ActiveModelSerializers::Deprecate
|
|
12
|
+
deprecate :new, 'ActiveModelSerializers::Adapter::Json.new'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveModel
|
|
4
|
+
class Serializer
|
|
5
|
+
module Adapter
|
|
6
|
+
class JsonApi < DelegateClass(ActiveModelSerializers::Adapter::JsonApi)
|
|
7
|
+
def initialize(serializer, options = {})
|
|
8
|
+
super(ActiveModelSerializers::Adapter::JsonApi.new(serializer, options))
|
|
9
|
+
end
|
|
10
|
+
class << self
|
|
11
|
+
extend ActiveModelSerializers::Deprecate
|
|
12
|
+
deprecate :new, 'ActiveModelSerializers::Adapter::JsonApi.new'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveModel
|
|
4
|
+
class Serializer
|
|
5
|
+
module Adapter
|
|
6
|
+
class Null < DelegateClass(ActiveModelSerializers::Adapter::Null)
|
|
7
|
+
def initialize(serializer, options = {})
|
|
8
|
+
super(ActiveModelSerializers::Adapter::Null.new(serializer, options))
|
|
9
|
+
end
|
|
10
|
+
class << self
|
|
11
|
+
extend ActiveModelSerializers::Deprecate
|
|
12
|
+
deprecate :new, 'ActiveModelSerializers::Adapter::Null.new'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_model/serializer/collection_serializer'
|
|
4
|
+
|
|
5
|
+
module ActiveModel
|
|
6
|
+
class Serializer
|
|
7
|
+
class ArraySerializer < CollectionSerializer
|
|
8
|
+
class << self
|
|
9
|
+
extend ActiveModelSerializers::Deprecate
|
|
10
|
+
deprecate :new, 'ActiveModel::Serializer::CollectionSerializer.'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_model/serializer/lazy_association'
|
|
4
|
+
|
|
5
|
+
module ActiveModel
|
|
6
|
+
class Serializer
|
|
7
|
+
# This class holds all information about serializer's association.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
Association = Struct.new(:reflection, :association_options) do
|
|
11
|
+
attr_reader :lazy_association
|
|
12
|
+
delegate :object, :include_data?, :virtual_value, :collection?, to: :lazy_association
|
|
13
|
+
|
|
14
|
+
def initialize(*)
|
|
15
|
+
super
|
|
16
|
+
@lazy_association = LazyAssociation.new(reflection, association_options)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Symbol]
|
|
20
|
+
delegate :name, to: :reflection
|
|
21
|
+
|
|
22
|
+
# @return [Symbol]
|
|
23
|
+
def key
|
|
24
|
+
reflection_options.fetch(:key, name)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [True,False]
|
|
28
|
+
def key?
|
|
29
|
+
reflection_options.key?(:key)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Hash]
|
|
33
|
+
def links
|
|
34
|
+
reflection_options.fetch(:links) || {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @return [Hash, nil]
|
|
38
|
+
# This gets mutated, so cannot use the cached reflection_options
|
|
39
|
+
def meta
|
|
40
|
+
reflection.options[:meta]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def belongs_to?
|
|
44
|
+
reflection.foreign_key_on == :self
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def polymorphic?
|
|
48
|
+
true == reflection_options[:polymorphic]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @api private
|
|
52
|
+
def serializable_hash(adapter_options, adapter_instance)
|
|
53
|
+
association_serializer = lazy_association.serializer
|
|
54
|
+
return virtual_value if virtual_value
|
|
55
|
+
association_object = association_serializer && association_serializer.object
|
|
56
|
+
return unless association_object
|
|
57
|
+
|
|
58
|
+
adapter_sub_options = adapter_options.deep_dup
|
|
59
|
+
fields = adapter_options.fetch(:fields, nil)
|
|
60
|
+
sub_fields = []
|
|
61
|
+
if fields.is_a?(Hash)
|
|
62
|
+
sub_fields = fields.fetch(association_serializer.json_key, nil)
|
|
63
|
+
else
|
|
64
|
+
fields.each do |f|
|
|
65
|
+
sub_fields = f.fetch(association_serializer.json_key, nil) if f.is_a?(Hash)
|
|
66
|
+
break if sub_fields.present?
|
|
67
|
+
end unless fields.nil?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if sub_fields.present?
|
|
71
|
+
adapter_sub_options[:fields] = sub_fields.collect { |f| f.to_sym }
|
|
72
|
+
else
|
|
73
|
+
adapter_sub_options = {}
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
serialization = association_serializer.serializable_hash(adapter_options, adapter_sub_options, adapter_instance)
|
|
77
|
+
|
|
78
|
+
if polymorphic? && serialization
|
|
79
|
+
polymorphic_type = association_object.class.name.underscore
|
|
80
|
+
serialization = { type: polymorphic_type, polymorphic_type.to_sym => serialization }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
serialization
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
delegate :reflection_options, to: :lazy_association
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_model/serializer/field'
|
|
4
|
+
|
|
5
|
+
module ActiveModel
|
|
6
|
+
class Serializer
|
|
7
|
+
# Holds all the meta-data about an attribute as it was specified in the
|
|
8
|
+
# ActiveModel::Serializer class.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# class PostSerializer < ActiveModel::Serializer
|
|
12
|
+
# attribute :content
|
|
13
|
+
# attribute :name, key: :title
|
|
14
|
+
# attribute :email, key: :author_email, if: :user_logged_in?
|
|
15
|
+
# attribute :preview do
|
|
16
|
+
# truncate(object.content)
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# def user_logged_in?
|
|
20
|
+
# current_user.logged_in?
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
class Attribute < Field
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveModel
|
|
4
|
+
class Serializer
|
|
5
|
+
class CollectionSerializer
|
|
6
|
+
include Enumerable
|
|
7
|
+
delegate :each, to: :@serializers
|
|
8
|
+
|
|
9
|
+
attr_reader :object, :root
|
|
10
|
+
|
|
11
|
+
def initialize(resources, options = {})
|
|
12
|
+
@object = resources
|
|
13
|
+
@options = options
|
|
14
|
+
@root = options[:root]
|
|
15
|
+
@serializers = serializers_from_resources
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def success?
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @api private
|
|
23
|
+
def serializable_hash(adapter_options, options, adapter_instance)
|
|
24
|
+
options[:include_directive] ||= ActiveModel::Serializer.include_directive_from_options(adapter_options)
|
|
25
|
+
options[:cached_attributes] ||= ActiveModel::Serializer.cache_read_multi(self, adapter_instance, options[:include_directive])
|
|
26
|
+
serializers.map do |serializer|
|
|
27
|
+
serializer.serializable_hash(adapter_options, options, adapter_instance)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# TODO: unify naming of root, json_key, and _type. Right now, a serializer's
|
|
32
|
+
# json_key comes from the root option or the object's model name, by default.
|
|
33
|
+
# But, if a dev defines a custom `json_key` method with an explicit value,
|
|
34
|
+
# we have no simple way to know that it is safe to call that instance method.
|
|
35
|
+
# (which is really a class property at this point, anyhow).
|
|
36
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
37
|
+
# Disabling cop since it's good to highlight the complexity of this method by
|
|
38
|
+
# including all the logic right here.
|
|
39
|
+
def json_key
|
|
40
|
+
return root if root
|
|
41
|
+
# 1. get from options[:serializer] for empty resource collection
|
|
42
|
+
key = object.empty? &&
|
|
43
|
+
(explicit_serializer_class = options[:serializer]) &&
|
|
44
|
+
explicit_serializer_class._type
|
|
45
|
+
# 2. get from first serializer instance in collection
|
|
46
|
+
key ||= (serializer = serializers.first) && serializer.json_key
|
|
47
|
+
# 3. get from collection name, if a named collection
|
|
48
|
+
key ||= object.respond_to?(:name) ? object.name && object.name.underscore : nil
|
|
49
|
+
# 4. key may be nil for empty collection and no serializer option
|
|
50
|
+
key &&= key.pluralize
|
|
51
|
+
# 5. fail if the key cannot be determined
|
|
52
|
+
key || fail(ArgumentError, 'Cannot infer root key from collection type. Please specify the root or each_serializer option, or render a JSON String')
|
|
53
|
+
end
|
|
54
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
55
|
+
|
|
56
|
+
def paginated?
|
|
57
|
+
ActiveModelSerializers.config.jsonapi_pagination_links_enabled &&
|
|
58
|
+
object.respond_to?(:current_page) &&
|
|
59
|
+
object.respond_to?(:total_pages) &&
|
|
60
|
+
object.respond_to?(:size)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
protected
|
|
64
|
+
|
|
65
|
+
attr_reader :serializers, :options
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def serializers_from_resources
|
|
70
|
+
serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer)
|
|
71
|
+
object.map do |resource|
|
|
72
|
+
serializer_from_resource(resource, serializer_context_class, options)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def serializer_from_resource(resource, serializer_context_class, options)
|
|
77
|
+
serializer_class = options.fetch(:serializer) do
|
|
78
|
+
serializer_context_class.serializer_for(resource, namespace: options[:namespace])
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if serializer_class.nil?
|
|
82
|
+
ActiveModelSerializers.logger.debug "No serializer found for resource: #{resource.inspect}"
|
|
83
|
+
throw :no_serializer
|
|
84
|
+
else
|
|
85
|
+
serializer_class.new(resource, options.except(:serializer))
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveModel
|
|
4
|
+
class Serializer
|
|
5
|
+
UndefinedCacheKey = Class.new(StandardError)
|
|
6
|
+
module Caching
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
included do
|
|
10
|
+
with_options instance_writer: false, instance_reader: false do |serializer|
|
|
11
|
+
serializer.class_attribute :_cache # @api private : the cache store
|
|
12
|
+
serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key. Ignored if the serializable object defines #cache_key.
|
|
13
|
+
serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists fetch_attributes. Cannot combine with except
|
|
14
|
+
serializer.class_attribute :_cache_except # @api private : when fragment caching, blacklists fetch_attributes. Cannot combine with only
|
|
15
|
+
serializer.class_attribute :_cache_options # @api private : used by CachedSerializer, passed to _cache.fetch
|
|
16
|
+
# _cache_options include:
|
|
17
|
+
# expires_in
|
|
18
|
+
# compress
|
|
19
|
+
# force
|
|
20
|
+
# race_condition_ttl
|
|
21
|
+
# Passed to ::_cache as
|
|
22
|
+
# serializer.cache_store.fetch(cache_key, @klass._cache_options)
|
|
23
|
+
# Passed as second argument to serializer.cache_store.fetch(cache_key, serializer_class._cache_options)
|
|
24
|
+
serializer.class_attribute :_cache_digest_file_path # @api private : Derived at inheritance
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Matches
|
|
29
|
+
# "c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
|
|
30
|
+
# AND
|
|
31
|
+
# "/c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
|
|
32
|
+
# AS
|
|
33
|
+
# c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb
|
|
34
|
+
CALLER_FILE = /
|
|
35
|
+
\A # start of string
|
|
36
|
+
.+ # file path (one or more characters)
|
|
37
|
+
(?= # stop previous match when
|
|
38
|
+
:\d+ # a colon is followed by one or more digits
|
|
39
|
+
:in # followed by a colon followed by in
|
|
40
|
+
)
|
|
41
|
+
/x
|
|
42
|
+
|
|
43
|
+
module ClassMethods
|
|
44
|
+
def inherited(base)
|
|
45
|
+
caller_line = caller[1]
|
|
46
|
+
base._cache_digest_file_path = caller_line
|
|
47
|
+
super
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def _cache_digest
|
|
51
|
+
return @_cache_digest if defined?(@_cache_digest)
|
|
52
|
+
@_cache_digest = digest_caller_file(_cache_digest_file_path)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Hashes contents of file for +_cache_digest+
|
|
56
|
+
def digest_caller_file(caller_line)
|
|
57
|
+
serializer_file_path = caller_line[CALLER_FILE]
|
|
58
|
+
serializer_file_contents = IO.read(serializer_file_path)
|
|
59
|
+
Digest::MD5.hexdigest(serializer_file_contents)
|
|
60
|
+
rescue TypeError, Errno::ENOENT
|
|
61
|
+
warn <<-EOF.strip_heredoc
|
|
62
|
+
Cannot digest non-existent file: '#{caller_line}'.
|
|
63
|
+
Please set `::_cache_digest` of the serializer
|
|
64
|
+
if you'd like to cache it.
|
|
65
|
+
EOF
|
|
66
|
+
''.freeze
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def _skip_digest?
|
|
70
|
+
_cache_options && _cache_options[:skip_digest]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# @api private
|
|
74
|
+
# maps attribute value to explicit key name
|
|
75
|
+
# @see Serializer::attribute
|
|
76
|
+
# @see Serializer::fragmented_attributes
|
|
77
|
+
def _attributes_keys
|
|
78
|
+
_attributes_data
|
|
79
|
+
.each_with_object({}) do |(key, attr), hash|
|
|
80
|
+
next if key == attr.name
|
|
81
|
+
hash[attr.name] = { key: key }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def fragmented_attributes
|
|
86
|
+
cached = _cache_only ? _cache_only : _attributes - _cache_except
|
|
87
|
+
cached = cached.map! { |field| _attributes_keys.fetch(field, field) }
|
|
88
|
+
non_cached = _attributes - cached
|
|
89
|
+
non_cached = non_cached.map! { |field| _attributes_keys.fetch(field, field) }
|
|
90
|
+
{
|
|
91
|
+
cached: cached,
|
|
92
|
+
non_cached: non_cached
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Enables a serializer to be automatically cached
|
|
97
|
+
#
|
|
98
|
+
# Sets +::_cache+ object to <tt>ActionController::Base.cache_store</tt>
|
|
99
|
+
# when Rails.configuration.action_controller.perform_caching
|
|
100
|
+
#
|
|
101
|
+
# @param options [Hash] with valid keys:
|
|
102
|
+
# cache_store : @see ::_cache
|
|
103
|
+
# key : @see ::_cache_key
|
|
104
|
+
# only : @see ::_cache_only
|
|
105
|
+
# except : @see ::_cache_except
|
|
106
|
+
# skip_digest : does not include digest in cache_key
|
|
107
|
+
# all else : @see ::_cache_options
|
|
108
|
+
#
|
|
109
|
+
# @example
|
|
110
|
+
# class PostSerializer < ActiveModel::Serializer
|
|
111
|
+
# cache key: 'post', expires_in: 3.hours
|
|
112
|
+
# attributes :title, :body
|
|
113
|
+
#
|
|
114
|
+
# has_many :comments
|
|
115
|
+
# end
|
|
116
|
+
#
|
|
117
|
+
# @todo require less code comments. See
|
|
118
|
+
# https://github.com/rails-api/active_model_serializers/pull/1249#issuecomment-146567837
|
|
119
|
+
def cache(options = {})
|
|
120
|
+
self._cache =
|
|
121
|
+
options.delete(:cache_store) ||
|
|
122
|
+
ActiveModelSerializers.config.cache_store ||
|
|
123
|
+
ActiveSupport::Cache.lookup_store(:null_store)
|
|
124
|
+
self._cache_key = options.delete(:key)
|
|
125
|
+
self._cache_only = options.delete(:only)
|
|
126
|
+
self._cache_except = options.delete(:except)
|
|
127
|
+
self._cache_options = options.empty? ? nil : options
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Value is from ActiveModelSerializers.config.perform_caching. Is used to
|
|
131
|
+
# globally enable or disable all serializer caching, just like
|
|
132
|
+
# Rails.configuration.action_controller.perform_caching, which is its
|
|
133
|
+
# default value in a Rails application.
|
|
134
|
+
# @return [true, false]
|
|
135
|
+
# Memoizes value of config first time it is called with a non-nil value.
|
|
136
|
+
# rubocop:disable Style/ClassVars
|
|
137
|
+
def perform_caching
|
|
138
|
+
return @@perform_caching if defined?(@@perform_caching) && !@@perform_caching.nil?
|
|
139
|
+
@@perform_caching = ActiveModelSerializers.config.perform_caching
|
|
140
|
+
end
|
|
141
|
+
alias perform_caching? perform_caching
|
|
142
|
+
# rubocop:enable Style/ClassVars
|
|
143
|
+
|
|
144
|
+
# The canonical method for getting the cache store for the serializer.
|
|
145
|
+
#
|
|
146
|
+
# @return [nil] when _cache is not set (i.e. when `cache` has not been called)
|
|
147
|
+
# @return [._cache] when _cache is not the NullStore
|
|
148
|
+
# @return [ActiveModelSerializers.config.cache_store] when _cache is the NullStore.
|
|
149
|
+
# This is so we can use `cache` being called to mean the serializer should be cached
|
|
150
|
+
# even if ActiveModelSerializers.config.cache_store has not yet been set.
|
|
151
|
+
# That means that when _cache is the NullStore and ActiveModelSerializers.config.cache_store
|
|
152
|
+
# is configured, `cache_store` becomes `ActiveModelSerializers.config.cache_store`.
|
|
153
|
+
# @return [nil] when _cache is the NullStore and ActiveModelSerializers.config.cache_store is nil.
|
|
154
|
+
def cache_store
|
|
155
|
+
return nil if _cache.nil?
|
|
156
|
+
return _cache if _cache.class != ActiveSupport::Cache::NullStore
|
|
157
|
+
if ActiveModelSerializers.config.cache_store
|
|
158
|
+
self._cache = ActiveModelSerializers.config.cache_store
|
|
159
|
+
else
|
|
160
|
+
nil
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def cache_enabled?
|
|
165
|
+
perform_caching? && cache_store && !_cache_only && !_cache_except
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def fragment_cache_enabled?
|
|
169
|
+
perform_caching? && cache_store &&
|
|
170
|
+
(_cache_only && !_cache_except || !_cache_only && _cache_except)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Read cache from cache_store
|
|
174
|
+
# @return [Hash]
|
|
175
|
+
# Used in CollectionSerializer to set :cached_attributes
|
|
176
|
+
def cache_read_multi(collection_serializer, adapter_instance, include_directive)
|
|
177
|
+
return {} if ActiveModelSerializers.config.cache_store.blank?
|
|
178
|
+
|
|
179
|
+
keys = object_cache_keys(collection_serializer, adapter_instance, include_directive)
|
|
180
|
+
|
|
181
|
+
return {} if keys.blank?
|
|
182
|
+
|
|
183
|
+
ActiveModelSerializers.config.cache_store.read_multi(*keys)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Find all cache_key for the collection_serializer
|
|
187
|
+
# @param serializers [ActiveModel::Serializer::CollectionSerializer]
|
|
188
|
+
# @param adapter_instance [ActiveModelSerializers::Adapter::Base]
|
|
189
|
+
# @param include_directive [JSONAPI::IncludeDirective]
|
|
190
|
+
# @return [Array] all cache_key of collection_serializer
|
|
191
|
+
def object_cache_keys(collection_serializer, adapter_instance, include_directive)
|
|
192
|
+
cache_keys = []
|
|
193
|
+
|
|
194
|
+
collection_serializer.each do |serializer|
|
|
195
|
+
cache_keys << object_cache_key(serializer, adapter_instance)
|
|
196
|
+
|
|
197
|
+
serializer.associations(include_directive).each do |association|
|
|
198
|
+
# TODO(BF): Process relationship without evaluating lazy_association
|
|
199
|
+
association_serializer = association.lazy_association.serializer
|
|
200
|
+
if association_serializer.respond_to?(:each)
|
|
201
|
+
association_serializer.each do |sub_serializer|
|
|
202
|
+
cache_keys << object_cache_key(sub_serializer, adapter_instance)
|
|
203
|
+
end
|
|
204
|
+
else
|
|
205
|
+
cache_keys << object_cache_key(association_serializer, adapter_instance)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
cache_keys.compact.uniq
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# @return [String, nil] the cache_key of the serializer or nil
|
|
214
|
+
def object_cache_key(serializer, adapter_instance)
|
|
215
|
+
return unless serializer.present? && serializer.object.present?
|
|
216
|
+
|
|
217
|
+
(serializer.class.cache_enabled? || serializer.class.fragment_cache_enabled?) ? serializer.cache_key(adapter_instance) : nil
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
### INSTANCE METHODS
|
|
222
|
+
def fetch_attributes(fields, cached_attributes, adapter_instance)
|
|
223
|
+
key = cache_key(adapter_instance)
|
|
224
|
+
cached_attributes.fetch(key) do
|
|
225
|
+
fetch(adapter_instance, serializer_class._cache_options, key) do
|
|
226
|
+
attributes(fields, true)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def fetch(adapter_instance, cache_options = serializer_class._cache_options, key = nil)
|
|
232
|
+
if serializer_class.cache_store
|
|
233
|
+
key ||= cache_key(adapter_instance)
|
|
234
|
+
serializer_class.cache_store.fetch(key, cache_options) do
|
|
235
|
+
yield
|
|
236
|
+
end
|
|
237
|
+
else
|
|
238
|
+
yield
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# 1. Determine cached fields from serializer class options
|
|
243
|
+
# 2. Get non_cached_fields and fetch cache_fields
|
|
244
|
+
# 3. Merge the two hashes using adapter_instance#fragment_cache
|
|
245
|
+
def fetch_attributes_fragment(adapter_instance, cached_attributes = {})
|
|
246
|
+
serializer_class._cache_options ||= {}
|
|
247
|
+
serializer_class._cache_options[:key] = serializer_class._cache_key if serializer_class._cache_key
|
|
248
|
+
fields = serializer_class.fragmented_attributes
|
|
249
|
+
|
|
250
|
+
non_cached_fields = fields[:non_cached].dup
|
|
251
|
+
non_cached_hash = attributes(non_cached_fields, true)
|
|
252
|
+
include_directive = JSONAPI::IncludeDirective.new(non_cached_fields - non_cached_hash.keys)
|
|
253
|
+
non_cached_hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
|
|
254
|
+
|
|
255
|
+
cached_fields = fields[:cached].dup
|
|
256
|
+
key = cache_key(adapter_instance)
|
|
257
|
+
cached_hash =
|
|
258
|
+
cached_attributes.fetch(key) do
|
|
259
|
+
fetch(adapter_instance, serializer_class._cache_options, key) do
|
|
260
|
+
hash = attributes(cached_fields, true)
|
|
261
|
+
include_directive = JSONAPI::IncludeDirective.new(cached_fields - hash.keys)
|
|
262
|
+
hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
# Merge both results
|
|
266
|
+
adapter_instance.fragment_cache(cached_hash, non_cached_hash)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def cache_key(adapter_instance)
|
|
270
|
+
return @cache_key if defined?(@cache_key)
|
|
271
|
+
|
|
272
|
+
parts = []
|
|
273
|
+
parts << object_cache_key
|
|
274
|
+
parts << adapter_instance.cache_key
|
|
275
|
+
parts << serializer_class._cache_digest unless serializer_class._skip_digest?
|
|
276
|
+
@cache_key = expand_cache_key(parts)
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def expand_cache_key(parts)
|
|
280
|
+
ActiveSupport::Cache.expand_cache_key(parts)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Use object's cache_key if available, else derive a key from the object
|
|
284
|
+
# Pass the `key` option to the `cache` declaration or override this method to customize the cache key
|
|
285
|
+
def object_cache_key
|
|
286
|
+
if object.respond_to?(:cache_key_with_version)
|
|
287
|
+
object.cache_key_with_version
|
|
288
|
+
elsif object.respond_to?(:cache_key)
|
|
289
|
+
object.cache_key
|
|
290
|
+
elsif (serializer_cache_key = (serializer_class._cache_key || serializer_class._cache_options[:key]))
|
|
291
|
+
object_time_safe = object.updated_at
|
|
292
|
+
object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
|
|
293
|
+
"#{serializer_cache_key}/#{object.id}-#{object_time_safe}"
|
|
294
|
+
else
|
|
295
|
+
fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{serializer_class}.cache'"
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def serializer_class
|
|
300
|
+
@serializer_class ||= self.class
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
end
|