active_model_serializers 0.8.3 → 0.10.0
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/.github/ISSUE_TEMPLATE.md +29 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +104 -0
- data/.rubocop_todo.yml +167 -0
- data/.simplecov +110 -0
- data/.travis.yml +39 -24
- data/CHANGELOG.md +465 -6
- data/CONTRIBUTING.md +105 -0
- data/Gemfile +50 -1
- data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
- data/README.md +102 -590
- data/Rakefile +93 -8
- data/active_model_serializers.gemspec +65 -23
- data/appveyor.yml +24 -0
- data/bin/bench +171 -0
- data/bin/bench_regression +316 -0
- data/bin/serve_benchmark +39 -0
- data/docs/ARCHITECTURE.md +126 -0
- data/docs/README.md +40 -0
- data/docs/STYLE.md +58 -0
- data/docs/general/adapters.md +245 -0
- data/docs/general/caching.md +52 -0
- data/docs/general/configuration_options.md +100 -0
- data/docs/general/deserialization.md +100 -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 +14 -0
- data/docs/general/rendering.md +255 -0
- data/docs/general/serializers.md +372 -0
- data/docs/how-open-source-maintained.jpg +0 -0
- data/docs/howto/add_pagination_links.md +139 -0
- data/docs/howto/add_root_key.md +51 -0
- data/docs/howto/outside_controller_use.md +58 -0
- data/docs/howto/passing_arbitrary_options.md +27 -0
- data/docs/howto/serialize_poro.md +32 -0
- data/docs/howto/test.md +152 -0
- data/docs/integrations/ember-and-json-api.md +112 -0
- data/docs/integrations/grape.md +19 -0
- data/docs/jsonapi/errors.md +56 -0
- data/docs/jsonapi/schema/schema.json +366 -0
- data/docs/jsonapi/schema.md +151 -0
- data/docs/rfcs/0000-namespace.md +106 -0
- data/docs/rfcs/template.md +15 -0
- data/lib/action_controller/serialization.rb +31 -36
- data/lib/active_model/serializable_resource.rb +11 -0
- data/lib/active_model/serializer/adapter/attributes.rb +15 -0
- data/lib/active_model/serializer/adapter/base.rb +16 -0
- data/lib/active_model/serializer/adapter/json.rb +15 -0
- data/lib/active_model/serializer/adapter/json_api.rb +15 -0
- data/lib/active_model/serializer/adapter/null.rb +15 -0
- data/lib/active_model/serializer/adapter.rb +24 -0
- data/lib/active_model/serializer/array_serializer.rb +9 -0
- data/lib/active_model/serializer/association.rb +19 -0
- data/lib/active_model/serializer/associations.rb +87 -220
- data/lib/active_model/serializer/attribute.rb +25 -0
- data/lib/active_model/serializer/attributes.rb +82 -0
- data/lib/active_model/serializer/belongs_to_reflection.rb +10 -0
- data/lib/active_model/serializer/caching.rb +333 -0
- data/lib/active_model/serializer/collection_reflection.rb +7 -0
- data/lib/active_model/serializer/collection_serializer.rb +64 -0
- data/lib/active_model/serializer/configuration.rb +35 -0
- data/lib/active_model/serializer/error_serializer.rb +10 -0
- data/lib/active_model/serializer/errors_serializer.rb +27 -0
- data/lib/active_model/serializer/field.rb +90 -0
- data/lib/active_model/serializer/fieldset.rb +31 -0
- data/lib/active_model/serializer/has_many_reflection.rb +10 -0
- data/lib/active_model/serializer/has_one_reflection.rb +10 -0
- data/lib/active_model/serializer/include_tree.rb +111 -0
- data/lib/active_model/serializer/links.rb +35 -0
- data/lib/active_model/serializer/lint.rb +146 -0
- data/lib/active_model/serializer/meta.rb +29 -0
- data/lib/active_model/serializer/null.rb +17 -0
- data/lib/active_model/serializer/reflection.rb +147 -0
- data/lib/active_model/serializer/singular_reflection.rb +7 -0
- data/lib/active_model/serializer/type.rb +25 -0
- data/lib/active_model/{serializers → serializer}/version.rb +1 -1
- data/lib/active_model/serializer.rb +158 -481
- data/lib/active_model_serializers/adapter/attributes.rb +76 -0
- data/lib/active_model_serializers/adapter/base.rb +83 -0
- data/lib/active_model_serializers/adapter/json.rb +21 -0
- data/lib/active_model_serializers/adapter/json_api/deserialization.rb +213 -0
- data/lib/active_model_serializers/adapter/json_api/error.rb +96 -0
- data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +49 -0
- data/lib/active_model_serializers/adapter/json_api/link.rb +83 -0
- data/lib/active_model_serializers/adapter/json_api/meta.rb +37 -0
- data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +62 -0
- data/lib/active_model_serializers/adapter/json_api/relationship.rb +52 -0
- data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +37 -0
- data/lib/active_model_serializers/adapter/json_api.rb +516 -0
- data/lib/active_model_serializers/adapter/null.rb +9 -0
- data/lib/active_model_serializers/adapter.rb +92 -0
- data/lib/active_model_serializers/callbacks.rb +55 -0
- data/lib/active_model_serializers/deprecate.rb +55 -0
- data/lib/active_model_serializers/deserialization.rb +13 -0
- data/lib/active_model_serializers/json_pointer.rb +14 -0
- data/lib/active_model_serializers/key_transform.rb +70 -0
- data/lib/active_model_serializers/logging.rb +122 -0
- data/lib/active_model_serializers/model.rb +49 -0
- data/lib/active_model_serializers/railtie.rb +46 -0
- data/lib/active_model_serializers/register_jsonapi_renderer.rb +65 -0
- data/lib/active_model_serializers/serializable_resource.rb +81 -0
- data/lib/active_model_serializers/serialization_context.rb +32 -0
- data/lib/active_model_serializers/test/schema.rb +138 -0
- data/lib/active_model_serializers/test/serializer.rb +125 -0
- data/lib/active_model_serializers/test.rb +7 -0
- data/lib/active_model_serializers.rb +32 -89
- data/lib/generators/rails/USAGE +6 -0
- data/lib/generators/rails/resource_override.rb +10 -0
- data/lib/generators/rails/serializer_generator.rb +36 -0
- data/lib/generators/rails/templates/serializer.rb.erb +8 -0
- data/lib/grape/active_model_serializers.rb +14 -0
- data/lib/grape/formatters/active_model_serializers.rb +15 -0
- data/lib/grape/helpers/active_model_serializers.rb +16 -0
- data/test/action_controller/adapter_selector_test.rb +53 -0
- data/test/action_controller/explicit_serializer_test.rb +134 -0
- data/test/action_controller/json/include_test.rb +167 -0
- data/test/action_controller/json_api/deserialization_test.rb +112 -0
- data/test/action_controller/json_api/errors_test.rb +41 -0
- data/test/action_controller/json_api/linked_test.rb +197 -0
- data/test/action_controller/json_api/pagination_test.rb +116 -0
- data/test/action_controller/json_api/transform_test.rb +181 -0
- data/test/action_controller/serialization_scope_name_test.rb +229 -0
- data/test/action_controller/serialization_test.rb +469 -0
- data/test/active_model_serializers/adapter_for_test.rb +208 -0
- data/test/active_model_serializers/json_pointer_test.rb +20 -0
- data/test/active_model_serializers/key_transform_test.rb +263 -0
- data/test/active_model_serializers/logging_test.rb +77 -0
- data/test/active_model_serializers/model_test.rb +9 -0
- data/test/active_model_serializers/railtie_test_isolated.rb +63 -0
- data/test/active_model_serializers/serialization_context_test_isolated.rb +58 -0
- data/test/active_model_serializers/test/schema_test.rb +130 -0
- data/test/active_model_serializers/test/serializer_test.rb +62 -0
- data/test/active_record_test.rb +9 -0
- data/test/adapter/deprecation_test.rb +100 -0
- data/test/adapter/json/belongs_to_test.rb +45 -0
- data/test/adapter/json/collection_test.rb +90 -0
- data/test/adapter/json/has_many_test.rb +45 -0
- data/test/adapter/json/transform_test.rb +93 -0
- data/test/adapter/json_api/belongs_to_test.rb +155 -0
- data/test/adapter/json_api/collection_test.rb +95 -0
- data/test/adapter/json_api/errors_test.rb +78 -0
- data/test/adapter/json_api/fields_test.rb +87 -0
- data/test/adapter/json_api/has_many_embed_ids_test.rb +43 -0
- data/test/adapter/json_api/has_many_explicit_serializer_test.rb +96 -0
- data/test/adapter/json_api/has_many_test.rb +144 -0
- data/test/adapter/json_api/has_one_test.rb +80 -0
- data/test/adapter/json_api/json_api_test.rb +35 -0
- data/test/adapter/json_api/linked_test.rb +392 -0
- data/test/adapter/json_api/links_test.rb +93 -0
- data/test/adapter/json_api/pagination_links_test.rb +166 -0
- data/test/adapter/json_api/parse_test.rb +137 -0
- data/test/adapter/json_api/relationship_test.rb +161 -0
- data/test/adapter/json_api/relationships_test.rb +199 -0
- data/test/adapter/json_api/resource_identifier_test.rb +85 -0
- data/test/adapter/json_api/resource_meta_test.rb +100 -0
- data/test/adapter/json_api/toplevel_jsonapi_test.rb +82 -0
- data/test/adapter/json_api/transform_test.rb +502 -0
- data/test/adapter/json_api/type_test.rb +61 -0
- data/test/adapter/json_test.rb +45 -0
- data/test/adapter/null_test.rb +23 -0
- data/test/adapter/polymorphic_test.rb +171 -0
- data/test/adapter_test.rb +67 -0
- data/test/array_serializer_test.rb +20 -73
- data/test/benchmark/app.rb +65 -0
- data/test/benchmark/benchmarking_support.rb +67 -0
- data/test/benchmark/bm_caching.rb +119 -0
- data/test/benchmark/bm_transform.rb +34 -0
- data/test/benchmark/config.ru +3 -0
- data/test/benchmark/controllers.rb +84 -0
- data/test/benchmark/fixtures.rb +219 -0
- data/test/cache_test.rb +485 -0
- data/test/collection_serializer_test.rb +110 -0
- data/test/fixtures/active_record.rb +78 -0
- data/test/fixtures/poro.rb +282 -0
- data/test/generators/scaffold_controller_generator_test.rb +24 -0
- data/test/generators/serializer_generator_test.rb +57 -0
- data/test/grape_test.rb +82 -0
- data/test/include_tree/from_include_args_test.rb +26 -0
- data/test/include_tree/from_string_test.rb +94 -0
- data/test/include_tree/include_args_to_hash_test.rb +64 -0
- data/test/lint_test.rb +49 -0
- data/test/logger_test.rb +18 -0
- data/test/poro_test.rb +9 -0
- data/test/serializable_resource_test.rb +83 -0
- data/test/serializers/association_macros_test.rb +36 -0
- data/test/serializers/associations_test.rb +295 -0
- data/test/serializers/attribute_test.rb +151 -0
- data/test/serializers/attributes_test.rb +52 -0
- data/test/serializers/caching_configuration_test_isolated.rb +170 -0
- data/test/serializers/configuration_test.rb +32 -0
- data/test/serializers/fieldset_test.rb +14 -0
- data/test/serializers/meta_test.rb +196 -0
- data/test/serializers/options_test.rb +21 -0
- data/test/serializers/read_attribute_for_serialization_test.rb +79 -0
- data/test/serializers/root_test.rb +21 -0
- data/test/serializers/serialization_test.rb +55 -0
- data/test/serializers/serializer_for_test.rb +134 -0
- data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
- data/test/support/isolated_unit.rb +79 -0
- data/test/support/rails5_shims.rb +47 -0
- data/test/support/rails_app.rb +45 -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 +53 -0
- data/test/test_helper.rb +48 -23
- metadata +449 -43
- data/DESIGN.textile +0 -586
- data/Gemfile.edge +0 -9
- data/bench/perf.rb +0 -43
- data/cruft.md +0 -19
- data/lib/active_model/array_serializer.rb +0 -104
- data/lib/active_record/serializer_override.rb +0 -16
- data/lib/generators/resource_override.rb +0 -13
- data/lib/generators/serializer/USAGE +0 -9
- data/lib/generators/serializer/serializer_generator.rb +0 -42
- data/lib/generators/serializer/templates/serializer.rb +0 -19
- data/test/association_test.rb +0 -592
- data/test/caching_test.rb +0 -96
- data/test/generators_test.rb +0 -85
- data/test/no_serialization_scope_test.rb +0 -34
- data/test/serialization_scope_name_test.rb +0 -67
- data/test/serialization_test.rb +0 -392
- data/test/serializer_support_test.rb +0 -51
- data/test/serializer_test.rb +0 -1465
- data/test/test_fakes.rb +0 -217
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
class Serializer
|
|
3
|
+
UndefinedCacheKey = Class.new(StandardError)
|
|
4
|
+
module Caching
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
with_options instance_writer: false, instance_reader: false do |serializer|
|
|
9
|
+
serializer.class_attribute :_cache # @api private : the cache store
|
|
10
|
+
serializer.class_attribute :_fragmented # @api private : @see ::fragmented
|
|
11
|
+
serializer.class_attribute :_cache_key # @api private : when present, is first item in cache_key. Ignored if the serializable object defines #cache_key.
|
|
12
|
+
serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists cached_attributes. Cannot combine with except
|
|
13
|
+
serializer.class_attribute :_cache_except # @api private : when fragment caching, blacklists cached_attributes. Cannot combine with only
|
|
14
|
+
serializer.class_attribute :_cache_options # @api private : used by CachedSerializer, passed to _cache.fetch
|
|
15
|
+
# _cache_options include:
|
|
16
|
+
# expires_in
|
|
17
|
+
# compress
|
|
18
|
+
# force
|
|
19
|
+
# race_condition_ttl
|
|
20
|
+
# Passed to ::_cache as
|
|
21
|
+
# serializer.cache_store.fetch(cache_key, @klass._cache_options)
|
|
22
|
+
# Passed as second argument to serializer.cache_store.fetch(cache_key, self.class._cache_options)
|
|
23
|
+
serializer.class_attribute :_cache_digest_file_path # @api private : Derived at inheritance
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Matches
|
|
28
|
+
# "c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
|
|
29
|
+
# AND
|
|
30
|
+
# "/c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
|
|
31
|
+
# AS
|
|
32
|
+
# c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb
|
|
33
|
+
CALLER_FILE = /
|
|
34
|
+
\A # start of string
|
|
35
|
+
.+ # file path (one or more characters)
|
|
36
|
+
(?= # stop previous match when
|
|
37
|
+
:\d+ # a colon is followed by one or more digits
|
|
38
|
+
:in # followed by a colon followed by in
|
|
39
|
+
)
|
|
40
|
+
/x
|
|
41
|
+
|
|
42
|
+
module ClassMethods
|
|
43
|
+
def inherited(base)
|
|
44
|
+
super
|
|
45
|
+
caller_line = caller[1]
|
|
46
|
+
base._cache_digest_file_path = caller_line
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def _cache_digest
|
|
50
|
+
return @_cache_digest if defined?(@_cache_digest)
|
|
51
|
+
@_cache_digest = digest_caller_file(_cache_digest_file_path)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Hashes contents of file for +_cache_digest+
|
|
55
|
+
def digest_caller_file(caller_line)
|
|
56
|
+
serializer_file_path = caller_line[CALLER_FILE]
|
|
57
|
+
serializer_file_contents = IO.read(serializer_file_path)
|
|
58
|
+
Digest::MD5.hexdigest(serializer_file_contents)
|
|
59
|
+
rescue TypeError, Errno::ENOENT
|
|
60
|
+
warn <<-EOF.strip_heredoc
|
|
61
|
+
Cannot digest non-existent file: '#{caller_line}'.
|
|
62
|
+
Please set `::_cache_digest` of the serializer
|
|
63
|
+
if you'd like to cache it.
|
|
64
|
+
EOF
|
|
65
|
+
''.freeze
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def _skip_digest?
|
|
69
|
+
_cache_options && _cache_options[:skip_digest]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def cached_attributes
|
|
73
|
+
_cache_only ? _cache_only : _attributes - _cache_except
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def non_cached_attributes
|
|
77
|
+
_attributes - cached_attributes
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @api private
|
|
81
|
+
# Used by FragmentCache on the CachedSerializer
|
|
82
|
+
# to call attribute methods on the fragmented cached serializer.
|
|
83
|
+
def fragmented(serializer)
|
|
84
|
+
self._fragmented = serializer
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Enables a serializer to be automatically cached
|
|
88
|
+
#
|
|
89
|
+
# Sets +::_cache+ object to <tt>ActionController::Base.cache_store</tt>
|
|
90
|
+
# when Rails.configuration.action_controller.perform_caching
|
|
91
|
+
#
|
|
92
|
+
# @param options [Hash] with valid keys:
|
|
93
|
+
# cache_store : @see ::_cache
|
|
94
|
+
# key : @see ::_cache_key
|
|
95
|
+
# only : @see ::_cache_only
|
|
96
|
+
# except : @see ::_cache_except
|
|
97
|
+
# skip_digest : does not include digest in cache_key
|
|
98
|
+
# all else : @see ::_cache_options
|
|
99
|
+
#
|
|
100
|
+
# @example
|
|
101
|
+
# class PostSerializer < ActiveModel::Serializer
|
|
102
|
+
# cache key: 'post', expires_in: 3.hours
|
|
103
|
+
# attributes :title, :body
|
|
104
|
+
#
|
|
105
|
+
# has_many :comments
|
|
106
|
+
# end
|
|
107
|
+
#
|
|
108
|
+
# @todo require less code comments. See
|
|
109
|
+
# https://github.com/rails-api/active_model_serializers/pull/1249#issuecomment-146567837
|
|
110
|
+
def cache(options = {})
|
|
111
|
+
self._cache =
|
|
112
|
+
options.delete(:cache_store) ||
|
|
113
|
+
ActiveModelSerializers.config.cache_store ||
|
|
114
|
+
ActiveSupport::Cache.lookup_store(:null_store)
|
|
115
|
+
self._cache_key = options.delete(:key)
|
|
116
|
+
self._cache_only = options.delete(:only)
|
|
117
|
+
self._cache_except = options.delete(:except)
|
|
118
|
+
self._cache_options = options.empty? ? nil : options
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Value is from ActiveModelSerializers.config.perform_caching. Is used to
|
|
122
|
+
# globally enable or disable all serializer caching, just like
|
|
123
|
+
# Rails.configuration.action_controller.perform_caching, which is its
|
|
124
|
+
# default value in a Rails application.
|
|
125
|
+
# @return [true, false]
|
|
126
|
+
# Memoizes value of config first time it is called with a non-nil value.
|
|
127
|
+
# rubocop:disable Style/ClassVars
|
|
128
|
+
def perform_caching
|
|
129
|
+
return @@perform_caching if defined?(@@perform_caching) && !@@perform_caching.nil?
|
|
130
|
+
@@perform_caching = ActiveModelSerializers.config.perform_caching
|
|
131
|
+
end
|
|
132
|
+
alias perform_caching? perform_caching
|
|
133
|
+
# rubocop:enable Style/ClassVars
|
|
134
|
+
|
|
135
|
+
# The canonical method for getting the cache store for the serializer.
|
|
136
|
+
#
|
|
137
|
+
# @return [nil] when _cache is not set (i.e. when `cache` has not been called)
|
|
138
|
+
# @return [._cache] when _cache is not the NullStore
|
|
139
|
+
# @return [ActiveModelSerializers.config.cache_store] when _cache is the NullStore.
|
|
140
|
+
# This is so we can use `cache` being called to mean the serializer should be cached
|
|
141
|
+
# even if ActiveModelSerializers.config.cache_store has not yet been set.
|
|
142
|
+
# That means that when _cache is the NullStore and ActiveModelSerializers.config.cache_store
|
|
143
|
+
# is configured, `cache_store` becomes `ActiveModelSerializers.config.cache_store`.
|
|
144
|
+
# @return [nil] when _cache is the NullStore and ActiveModelSerializers.config.cache_store is nil.
|
|
145
|
+
def cache_store
|
|
146
|
+
return nil if _cache.nil?
|
|
147
|
+
return _cache if _cache.class != ActiveSupport::Cache::NullStore
|
|
148
|
+
if ActiveModelSerializers.config.cache_store
|
|
149
|
+
self._cache = ActiveModelSerializers.config.cache_store
|
|
150
|
+
else
|
|
151
|
+
nil
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def cache_enabled?
|
|
156
|
+
perform_caching? && cache_store && !_cache_only && !_cache_except
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def fragment_cache_enabled?
|
|
160
|
+
perform_caching? && cache_store &&
|
|
161
|
+
(_cache_only && !_cache_except || !_cache_only && _cache_except)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Read cache from cache_store
|
|
165
|
+
# @return [Hash]
|
|
166
|
+
def cache_read_multi(collection_serializer, adapter_instance, include_tree)
|
|
167
|
+
return {} if ActiveModelSerializers.config.cache_store.blank?
|
|
168
|
+
|
|
169
|
+
keys = object_cache_keys(collection_serializer, adapter_instance, include_tree)
|
|
170
|
+
|
|
171
|
+
return {} if keys.blank?
|
|
172
|
+
|
|
173
|
+
ActiveModelSerializers.config.cache_store.read_multi(*keys)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Find all cache_key for the collection_serializer
|
|
177
|
+
# @param serializers [ActiveModel::Serializer::CollectionSerializer]
|
|
178
|
+
# @param adapter_instance [ActiveModelSerializers::Adapter::Base]
|
|
179
|
+
# @param include_tree [ActiveModel::Serializer::IncludeTree]
|
|
180
|
+
# @return [Array] all cache_key of collection_serializer
|
|
181
|
+
def object_cache_keys(collection_serializer, adapter_instance, include_tree)
|
|
182
|
+
cache_keys = []
|
|
183
|
+
|
|
184
|
+
collection_serializer.each do |serializer|
|
|
185
|
+
cache_keys << object_cache_key(serializer, adapter_instance)
|
|
186
|
+
|
|
187
|
+
serializer.associations(include_tree).each do |association|
|
|
188
|
+
if association.serializer.respond_to?(:each)
|
|
189
|
+
association.serializer.each do |sub_serializer|
|
|
190
|
+
cache_keys << object_cache_key(sub_serializer, adapter_instance)
|
|
191
|
+
end
|
|
192
|
+
else
|
|
193
|
+
cache_keys << object_cache_key(association.serializer, adapter_instance)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
cache_keys.compact.uniq
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# @return [String, nil] the cache_key of the serializer or nil
|
|
202
|
+
def object_cache_key(serializer, adapter_instance)
|
|
203
|
+
return unless serializer.present? && serializer.object.present?
|
|
204
|
+
|
|
205
|
+
serializer.class.cache_enabled? ? serializer.cache_key(adapter_instance) : nil
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Get attributes from @cached_attributes
|
|
210
|
+
# @return [Hash] cached attributes
|
|
211
|
+
# def cached_attributes(fields, adapter_instance)
|
|
212
|
+
def cached_fields(fields, adapter_instance)
|
|
213
|
+
cache_check(adapter_instance) do
|
|
214
|
+
attributes(fields)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def cache_check(adapter_instance)
|
|
219
|
+
if self.class.cache_enabled?
|
|
220
|
+
self.class.cache_store.fetch(cache_key(adapter_instance), self.class._cache_options) do
|
|
221
|
+
yield
|
|
222
|
+
end
|
|
223
|
+
elsif self.class.fragment_cache_enabled?
|
|
224
|
+
fetch_fragment_cache(adapter_instance)
|
|
225
|
+
else
|
|
226
|
+
yield
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# 1. Create a CachedSerializer and NonCachedSerializer from the serializer class
|
|
231
|
+
# 2. Serialize the above two with the given adapter
|
|
232
|
+
# 3. Pass their serializations to the adapter +::fragment_cache+
|
|
233
|
+
#
|
|
234
|
+
# It will split the serializer into two, one that will be cached and one that will not
|
|
235
|
+
#
|
|
236
|
+
# Given a resource name
|
|
237
|
+
# 1. Dynamically creates a CachedSerializer and NonCachedSerializer
|
|
238
|
+
# for a given class 'name'
|
|
239
|
+
# 2. Call
|
|
240
|
+
# CachedSerializer.cache(serializer._cache_options)
|
|
241
|
+
# CachedSerializer.fragmented(serializer)
|
|
242
|
+
# NonCachedSerializer.cache(serializer._cache_options)
|
|
243
|
+
# 3. Build a hash keyed to the +cached+ and +non_cached+ serializers
|
|
244
|
+
# 4. Call +cached_attributes+ on the serializer class and the above hash
|
|
245
|
+
# 5. Return the hash
|
|
246
|
+
#
|
|
247
|
+
# @example
|
|
248
|
+
# When +name+ is <tt>User::Admin</tt>
|
|
249
|
+
# creates the Serializer classes (if they don't exist).
|
|
250
|
+
# CachedUser_AdminSerializer
|
|
251
|
+
# NonCachedUser_AdminSerializer
|
|
252
|
+
#
|
|
253
|
+
# Given a hash of its cached and non-cached serializers
|
|
254
|
+
# 1. Determine cached attributes from serializer class options
|
|
255
|
+
# 2. Add cached attributes to cached Serializer
|
|
256
|
+
# 3. Add non-cached attributes to non-cached Serializer
|
|
257
|
+
def fetch_fragment_cache(adapter_instance)
|
|
258
|
+
serializer_class_name = self.class.name.gsub('::'.freeze, '_'.freeze)
|
|
259
|
+
self.class._cache_options ||= {}
|
|
260
|
+
self.class._cache_options[:key] = self.class._cache_key if self.class._cache_key
|
|
261
|
+
|
|
262
|
+
cached_serializer = _get_or_create_fragment_cached_serializer(serializer_class_name)
|
|
263
|
+
cached_hash = ActiveModelSerializers::SerializableResource.new(
|
|
264
|
+
object,
|
|
265
|
+
serializer: cached_serializer,
|
|
266
|
+
adapter: adapter_instance.class
|
|
267
|
+
).serializable_hash
|
|
268
|
+
|
|
269
|
+
non_cached_serializer = _get_or_create_fragment_non_cached_serializer(serializer_class_name)
|
|
270
|
+
non_cached_hash = ActiveModelSerializers::SerializableResource.new(
|
|
271
|
+
object,
|
|
272
|
+
serializer: non_cached_serializer,
|
|
273
|
+
adapter: adapter_instance.class
|
|
274
|
+
).serializable_hash
|
|
275
|
+
|
|
276
|
+
# Merge both results
|
|
277
|
+
adapter_instance.fragment_cache(cached_hash, non_cached_hash)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def _get_or_create_fragment_cached_serializer(serializer_class_name)
|
|
281
|
+
cached_serializer = _get_or_create_fragment_serializer "Cached#{serializer_class_name}"
|
|
282
|
+
cached_serializer.cache(self.class._cache_options)
|
|
283
|
+
cached_serializer.type(self.class._type)
|
|
284
|
+
cached_serializer.fragmented(self)
|
|
285
|
+
self.class.cached_attributes.each do |attribute|
|
|
286
|
+
options = self.class._attributes_keys[attribute] || {}
|
|
287
|
+
cached_serializer.attribute(attribute, options)
|
|
288
|
+
end
|
|
289
|
+
cached_serializer
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def _get_or_create_fragment_non_cached_serializer(serializer_class_name)
|
|
293
|
+
non_cached_serializer = _get_or_create_fragment_serializer "NonCached#{serializer_class_name}"
|
|
294
|
+
non_cached_serializer.type(self.class._type)
|
|
295
|
+
non_cached_serializer.fragmented(self)
|
|
296
|
+
self.class.non_cached_attributes.each do |attribute|
|
|
297
|
+
options = self.class._attributes_keys[attribute] || {}
|
|
298
|
+
non_cached_serializer.attribute(attribute, options)
|
|
299
|
+
end
|
|
300
|
+
non_cached_serializer
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def _get_or_create_fragment_serializer(name)
|
|
304
|
+
return Object.const_get(name) if Object.const_defined?(name)
|
|
305
|
+
Object.const_set(name, Class.new(ActiveModel::Serializer))
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def cache_key(adapter_instance)
|
|
309
|
+
return @cache_key if defined?(@cache_key)
|
|
310
|
+
|
|
311
|
+
parts = []
|
|
312
|
+
parts << object_cache_key
|
|
313
|
+
parts << adapter_instance.cached_name
|
|
314
|
+
parts << self.class._cache_digest unless self.class._skip_digest?
|
|
315
|
+
@cache_key = parts.join('/')
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# Use object's cache_key if available, else derive a key from the object
|
|
319
|
+
# Pass the `key` option to the `cache` declaration or override this method to customize the cache key
|
|
320
|
+
def object_cache_key
|
|
321
|
+
if object.respond_to?(:cache_key)
|
|
322
|
+
object.cache_key
|
|
323
|
+
elsif (serializer_cache_key = (self.class._cache_key || self.class._cache_options[:key]))
|
|
324
|
+
object_time_safe = object.updated_at
|
|
325
|
+
object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
|
|
326
|
+
"#{serializer_cache_key}/#{object.id}-#{object_time_safe}"
|
|
327
|
+
else
|
|
328
|
+
fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{self.class}.cache'"
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
class Serializer
|
|
3
|
+
class CollectionSerializer
|
|
4
|
+
NoSerializerError = Class.new(StandardError)
|
|
5
|
+
include Enumerable
|
|
6
|
+
delegate :each, to: :@serializers
|
|
7
|
+
|
|
8
|
+
attr_reader :object, :root
|
|
9
|
+
|
|
10
|
+
def initialize(resources, options = {})
|
|
11
|
+
@object = resources
|
|
12
|
+
@options = options
|
|
13
|
+
@root = options[:root]
|
|
14
|
+
serializer_context_class = options.fetch(:serializer_context_class, ActiveModel::Serializer)
|
|
15
|
+
@serializers = resources.map do |resource|
|
|
16
|
+
serializer_class = options.fetch(:serializer) { serializer_context_class.serializer_for(resource) }
|
|
17
|
+
|
|
18
|
+
if serializer_class.nil? # rubocop:disable Style/GuardClause
|
|
19
|
+
fail NoSerializerError, "No serializer found for resource: #{resource.inspect}"
|
|
20
|
+
else
|
|
21
|
+
serializer_class.new(resource, options.except(:serializer))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def success?
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# TODO: unify naming of root, json_key, and _type. Right now, a serializer's
|
|
31
|
+
# json_key comes from the root option or the object's model name, by default.
|
|
32
|
+
# But, if a dev defines a custom `json_key` method with an explicit value,
|
|
33
|
+
# we have no simple way to know that it is safe to call that instance method.
|
|
34
|
+
# (which is really a class property at this point, anyhow).
|
|
35
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
36
|
+
# Disabling cop since it's good to highlight the complexity of this method by
|
|
37
|
+
# including all the logic right here.
|
|
38
|
+
def json_key
|
|
39
|
+
return root if root
|
|
40
|
+
# 1. get from options[:serializer] for empty resource collection
|
|
41
|
+
key = object.empty? &&
|
|
42
|
+
(explicit_serializer_class = options[:serializer]) &&
|
|
43
|
+
explicit_serializer_class._type
|
|
44
|
+
# 2. get from first serializer instance in collection
|
|
45
|
+
key ||= (serializer = serializers.first) && serializer.json_key
|
|
46
|
+
# 3. get from collection name, if a named collection
|
|
47
|
+
key ||= object.respond_to?(:name) ? object.name && object.name.underscore : nil
|
|
48
|
+
# 4. key may be nil for empty collection and no serializer option
|
|
49
|
+
key && key.pluralize
|
|
50
|
+
end
|
|
51
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
52
|
+
|
|
53
|
+
def paginated?
|
|
54
|
+
object.respond_to?(:current_page) &&
|
|
55
|
+
object.respond_to?(:total_pages) &&
|
|
56
|
+
object.respond_to?(:size)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
protected
|
|
60
|
+
|
|
61
|
+
attr_reader :serializers, :options
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
class Serializer
|
|
3
|
+
module Configuration
|
|
4
|
+
include ActiveSupport::Configurable
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
# Configuration options may also be set in
|
|
8
|
+
# Serializers and Adapters
|
|
9
|
+
included do |base|
|
|
10
|
+
config = base.config
|
|
11
|
+
config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
|
|
12
|
+
config.serializer_lookup_enabled = true
|
|
13
|
+
|
|
14
|
+
def config.array_serializer=(collection_serializer)
|
|
15
|
+
self.collection_serializer = collection_serializer
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def config.array_serializer
|
|
19
|
+
collection_serializer
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
config.adapter = :attributes
|
|
23
|
+
config.jsonapi_resource_type = :plural
|
|
24
|
+
config.jsonapi_version = '1.0'
|
|
25
|
+
config.jsonapi_toplevel_meta = {}
|
|
26
|
+
# Make JSON API top-level jsonapi member opt-in
|
|
27
|
+
# ref: http://jsonapi.org/format/#document-top-level
|
|
28
|
+
config.jsonapi_include_toplevel_object = false
|
|
29
|
+
config.key_transform = nil
|
|
30
|
+
|
|
31
|
+
config.schema_path = 'test/support/schemas'
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'active_model/serializer/error_serializer'
|
|
2
|
+
class ActiveModel::Serializer::ErrorsSerializer
|
|
3
|
+
include Enumerable
|
|
4
|
+
delegate :each, to: :@serializers
|
|
5
|
+
attr_reader :object, :root
|
|
6
|
+
|
|
7
|
+
def initialize(resources, options = {})
|
|
8
|
+
@root = options[:root]
|
|
9
|
+
@object = resources
|
|
10
|
+
@serializers = resources.map do |resource|
|
|
11
|
+
serializer_class = options.fetch(:serializer) { ActiveModel::Serializer::ErrorSerializer }
|
|
12
|
+
serializer_class.new(resource, options.except(:serializer))
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def success?
|
|
17
|
+
false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def json_key
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
attr_reader :serializers
|
|
27
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
class Serializer
|
|
3
|
+
# Holds all the meta-data about a field (i.e. attribute or association) as it was
|
|
4
|
+
# specified in the ActiveModel::Serializer class.
|
|
5
|
+
# Notice that the field block is evaluated in the context of the serializer.
|
|
6
|
+
Field = Struct.new(:name, :options, :block) do
|
|
7
|
+
def initialize(*)
|
|
8
|
+
super
|
|
9
|
+
|
|
10
|
+
validate_condition!
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Compute the actual value of a field for a given serializer instance.
|
|
14
|
+
# @param [Serializer] The serializer instance for which the value is computed.
|
|
15
|
+
# @return [Object] value
|
|
16
|
+
#
|
|
17
|
+
# @api private
|
|
18
|
+
#
|
|
19
|
+
def value(serializer)
|
|
20
|
+
if block
|
|
21
|
+
serializer.instance_eval(&block)
|
|
22
|
+
else
|
|
23
|
+
serializer.read_attribute_for_serialization(name)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Decide whether the field should be serialized by the given serializer instance.
|
|
28
|
+
# @param [Serializer] The serializer instance
|
|
29
|
+
# @return [Bool]
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
#
|
|
33
|
+
def excluded?(serializer)
|
|
34
|
+
case condition_type
|
|
35
|
+
when :if
|
|
36
|
+
!evaluate_condition(serializer)
|
|
37
|
+
when :unless
|
|
38
|
+
evaluate_condition(serializer)
|
|
39
|
+
else
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def validate_condition!
|
|
47
|
+
return if condition_type == :none
|
|
48
|
+
|
|
49
|
+
case condition
|
|
50
|
+
when Symbol, String, Proc
|
|
51
|
+
# noop
|
|
52
|
+
else
|
|
53
|
+
fail TypeError, "#{condition_type.inspect} should be a Symbol, String or Proc"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def evaluate_condition(serializer)
|
|
58
|
+
case condition
|
|
59
|
+
when Symbol
|
|
60
|
+
serializer.public_send(condition)
|
|
61
|
+
when String
|
|
62
|
+
serializer.instance_eval(condition)
|
|
63
|
+
when Proc
|
|
64
|
+
if condition.arity.zero?
|
|
65
|
+
serializer.instance_exec(&condition)
|
|
66
|
+
else
|
|
67
|
+
serializer.instance_exec(serializer, &condition)
|
|
68
|
+
end
|
|
69
|
+
else
|
|
70
|
+
nil
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def condition_type
|
|
75
|
+
@condition_type ||=
|
|
76
|
+
if options.key?(:if)
|
|
77
|
+
:if
|
|
78
|
+
elsif options.key?(:unless)
|
|
79
|
+
:unless
|
|
80
|
+
else
|
|
81
|
+
:none
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def condition
|
|
86
|
+
options[condition_type]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module ActiveModel
|
|
2
|
+
class Serializer
|
|
3
|
+
class Fieldset
|
|
4
|
+
def initialize(fields)
|
|
5
|
+
@raw_fields = fields || {}
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def fields
|
|
9
|
+
@fields ||= parsed_fields
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def fields_for(type)
|
|
13
|
+
fields[type.singularize.to_sym] || fields[type.pluralize.to_sym]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
attr_reader :raw_fields
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def parsed_fields
|
|
23
|
+
if raw_fields.is_a?(Hash)
|
|
24
|
+
raw_fields.each_with_object({}) { |(k, v), h| h[k.to_sym] = v.map(&:to_sym) }
|
|
25
|
+
else
|
|
26
|
+
{}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|