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.
Files changed (232) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +29 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  4. data/.gitignore +17 -0
  5. data/.rubocop.yml +104 -0
  6. data/.rubocop_todo.yml +167 -0
  7. data/.simplecov +110 -0
  8. data/.travis.yml +39 -24
  9. data/CHANGELOG.md +465 -6
  10. data/CONTRIBUTING.md +105 -0
  11. data/Gemfile +50 -1
  12. data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
  13. data/README.md +102 -590
  14. data/Rakefile +93 -8
  15. data/active_model_serializers.gemspec +65 -23
  16. data/appveyor.yml +24 -0
  17. data/bin/bench +171 -0
  18. data/bin/bench_regression +316 -0
  19. data/bin/serve_benchmark +39 -0
  20. data/docs/ARCHITECTURE.md +126 -0
  21. data/docs/README.md +40 -0
  22. data/docs/STYLE.md +58 -0
  23. data/docs/general/adapters.md +245 -0
  24. data/docs/general/caching.md +52 -0
  25. data/docs/general/configuration_options.md +100 -0
  26. data/docs/general/deserialization.md +100 -0
  27. data/docs/general/getting_started.md +133 -0
  28. data/docs/general/instrumentation.md +40 -0
  29. data/docs/general/key_transforms.md +40 -0
  30. data/docs/general/logging.md +14 -0
  31. data/docs/general/rendering.md +255 -0
  32. data/docs/general/serializers.md +372 -0
  33. data/docs/how-open-source-maintained.jpg +0 -0
  34. data/docs/howto/add_pagination_links.md +139 -0
  35. data/docs/howto/add_root_key.md +51 -0
  36. data/docs/howto/outside_controller_use.md +58 -0
  37. data/docs/howto/passing_arbitrary_options.md +27 -0
  38. data/docs/howto/serialize_poro.md +32 -0
  39. data/docs/howto/test.md +152 -0
  40. data/docs/integrations/ember-and-json-api.md +112 -0
  41. data/docs/integrations/grape.md +19 -0
  42. data/docs/jsonapi/errors.md +56 -0
  43. data/docs/jsonapi/schema/schema.json +366 -0
  44. data/docs/jsonapi/schema.md +151 -0
  45. data/docs/rfcs/0000-namespace.md +106 -0
  46. data/docs/rfcs/template.md +15 -0
  47. data/lib/action_controller/serialization.rb +31 -36
  48. data/lib/active_model/serializable_resource.rb +11 -0
  49. data/lib/active_model/serializer/adapter/attributes.rb +15 -0
  50. data/lib/active_model/serializer/adapter/base.rb +16 -0
  51. data/lib/active_model/serializer/adapter/json.rb +15 -0
  52. data/lib/active_model/serializer/adapter/json_api.rb +15 -0
  53. data/lib/active_model/serializer/adapter/null.rb +15 -0
  54. data/lib/active_model/serializer/adapter.rb +24 -0
  55. data/lib/active_model/serializer/array_serializer.rb +9 -0
  56. data/lib/active_model/serializer/association.rb +19 -0
  57. data/lib/active_model/serializer/associations.rb +87 -220
  58. data/lib/active_model/serializer/attribute.rb +25 -0
  59. data/lib/active_model/serializer/attributes.rb +82 -0
  60. data/lib/active_model/serializer/belongs_to_reflection.rb +10 -0
  61. data/lib/active_model/serializer/caching.rb +333 -0
  62. data/lib/active_model/serializer/collection_reflection.rb +7 -0
  63. data/lib/active_model/serializer/collection_serializer.rb +64 -0
  64. data/lib/active_model/serializer/configuration.rb +35 -0
  65. data/lib/active_model/serializer/error_serializer.rb +10 -0
  66. data/lib/active_model/serializer/errors_serializer.rb +27 -0
  67. data/lib/active_model/serializer/field.rb +90 -0
  68. data/lib/active_model/serializer/fieldset.rb +31 -0
  69. data/lib/active_model/serializer/has_many_reflection.rb +10 -0
  70. data/lib/active_model/serializer/has_one_reflection.rb +10 -0
  71. data/lib/active_model/serializer/include_tree.rb +111 -0
  72. data/lib/active_model/serializer/links.rb +35 -0
  73. data/lib/active_model/serializer/lint.rb +146 -0
  74. data/lib/active_model/serializer/meta.rb +29 -0
  75. data/lib/active_model/serializer/null.rb +17 -0
  76. data/lib/active_model/serializer/reflection.rb +147 -0
  77. data/lib/active_model/serializer/singular_reflection.rb +7 -0
  78. data/lib/active_model/serializer/type.rb +25 -0
  79. data/lib/active_model/{serializers → serializer}/version.rb +1 -1
  80. data/lib/active_model/serializer.rb +158 -481
  81. data/lib/active_model_serializers/adapter/attributes.rb +76 -0
  82. data/lib/active_model_serializers/adapter/base.rb +83 -0
  83. data/lib/active_model_serializers/adapter/json.rb +21 -0
  84. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +213 -0
  85. data/lib/active_model_serializers/adapter/json_api/error.rb +96 -0
  86. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +49 -0
  87. data/lib/active_model_serializers/adapter/json_api/link.rb +83 -0
  88. data/lib/active_model_serializers/adapter/json_api/meta.rb +37 -0
  89. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +62 -0
  90. data/lib/active_model_serializers/adapter/json_api/relationship.rb +52 -0
  91. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +37 -0
  92. data/lib/active_model_serializers/adapter/json_api.rb +516 -0
  93. data/lib/active_model_serializers/adapter/null.rb +9 -0
  94. data/lib/active_model_serializers/adapter.rb +92 -0
  95. data/lib/active_model_serializers/callbacks.rb +55 -0
  96. data/lib/active_model_serializers/deprecate.rb +55 -0
  97. data/lib/active_model_serializers/deserialization.rb +13 -0
  98. data/lib/active_model_serializers/json_pointer.rb +14 -0
  99. data/lib/active_model_serializers/key_transform.rb +70 -0
  100. data/lib/active_model_serializers/logging.rb +122 -0
  101. data/lib/active_model_serializers/model.rb +49 -0
  102. data/lib/active_model_serializers/railtie.rb +46 -0
  103. data/lib/active_model_serializers/register_jsonapi_renderer.rb +65 -0
  104. data/lib/active_model_serializers/serializable_resource.rb +81 -0
  105. data/lib/active_model_serializers/serialization_context.rb +32 -0
  106. data/lib/active_model_serializers/test/schema.rb +138 -0
  107. data/lib/active_model_serializers/test/serializer.rb +125 -0
  108. data/lib/active_model_serializers/test.rb +7 -0
  109. data/lib/active_model_serializers.rb +32 -89
  110. data/lib/generators/rails/USAGE +6 -0
  111. data/lib/generators/rails/resource_override.rb +10 -0
  112. data/lib/generators/rails/serializer_generator.rb +36 -0
  113. data/lib/generators/rails/templates/serializer.rb.erb +8 -0
  114. data/lib/grape/active_model_serializers.rb +14 -0
  115. data/lib/grape/formatters/active_model_serializers.rb +15 -0
  116. data/lib/grape/helpers/active_model_serializers.rb +16 -0
  117. data/test/action_controller/adapter_selector_test.rb +53 -0
  118. data/test/action_controller/explicit_serializer_test.rb +134 -0
  119. data/test/action_controller/json/include_test.rb +167 -0
  120. data/test/action_controller/json_api/deserialization_test.rb +112 -0
  121. data/test/action_controller/json_api/errors_test.rb +41 -0
  122. data/test/action_controller/json_api/linked_test.rb +197 -0
  123. data/test/action_controller/json_api/pagination_test.rb +116 -0
  124. data/test/action_controller/json_api/transform_test.rb +181 -0
  125. data/test/action_controller/serialization_scope_name_test.rb +229 -0
  126. data/test/action_controller/serialization_test.rb +469 -0
  127. data/test/active_model_serializers/adapter_for_test.rb +208 -0
  128. data/test/active_model_serializers/json_pointer_test.rb +20 -0
  129. data/test/active_model_serializers/key_transform_test.rb +263 -0
  130. data/test/active_model_serializers/logging_test.rb +77 -0
  131. data/test/active_model_serializers/model_test.rb +9 -0
  132. data/test/active_model_serializers/railtie_test_isolated.rb +63 -0
  133. data/test/active_model_serializers/serialization_context_test_isolated.rb +58 -0
  134. data/test/active_model_serializers/test/schema_test.rb +130 -0
  135. data/test/active_model_serializers/test/serializer_test.rb +62 -0
  136. data/test/active_record_test.rb +9 -0
  137. data/test/adapter/deprecation_test.rb +100 -0
  138. data/test/adapter/json/belongs_to_test.rb +45 -0
  139. data/test/adapter/json/collection_test.rb +90 -0
  140. data/test/adapter/json/has_many_test.rb +45 -0
  141. data/test/adapter/json/transform_test.rb +93 -0
  142. data/test/adapter/json_api/belongs_to_test.rb +155 -0
  143. data/test/adapter/json_api/collection_test.rb +95 -0
  144. data/test/adapter/json_api/errors_test.rb +78 -0
  145. data/test/adapter/json_api/fields_test.rb +87 -0
  146. data/test/adapter/json_api/has_many_embed_ids_test.rb +43 -0
  147. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +96 -0
  148. data/test/adapter/json_api/has_many_test.rb +144 -0
  149. data/test/adapter/json_api/has_one_test.rb +80 -0
  150. data/test/adapter/json_api/json_api_test.rb +35 -0
  151. data/test/adapter/json_api/linked_test.rb +392 -0
  152. data/test/adapter/json_api/links_test.rb +93 -0
  153. data/test/adapter/json_api/pagination_links_test.rb +166 -0
  154. data/test/adapter/json_api/parse_test.rb +137 -0
  155. data/test/adapter/json_api/relationship_test.rb +161 -0
  156. data/test/adapter/json_api/relationships_test.rb +199 -0
  157. data/test/adapter/json_api/resource_identifier_test.rb +85 -0
  158. data/test/adapter/json_api/resource_meta_test.rb +100 -0
  159. data/test/adapter/json_api/toplevel_jsonapi_test.rb +82 -0
  160. data/test/adapter/json_api/transform_test.rb +502 -0
  161. data/test/adapter/json_api/type_test.rb +61 -0
  162. data/test/adapter/json_test.rb +45 -0
  163. data/test/adapter/null_test.rb +23 -0
  164. data/test/adapter/polymorphic_test.rb +171 -0
  165. data/test/adapter_test.rb +67 -0
  166. data/test/array_serializer_test.rb +20 -73
  167. data/test/benchmark/app.rb +65 -0
  168. data/test/benchmark/benchmarking_support.rb +67 -0
  169. data/test/benchmark/bm_caching.rb +119 -0
  170. data/test/benchmark/bm_transform.rb +34 -0
  171. data/test/benchmark/config.ru +3 -0
  172. data/test/benchmark/controllers.rb +84 -0
  173. data/test/benchmark/fixtures.rb +219 -0
  174. data/test/cache_test.rb +485 -0
  175. data/test/collection_serializer_test.rb +110 -0
  176. data/test/fixtures/active_record.rb +78 -0
  177. data/test/fixtures/poro.rb +282 -0
  178. data/test/generators/scaffold_controller_generator_test.rb +24 -0
  179. data/test/generators/serializer_generator_test.rb +57 -0
  180. data/test/grape_test.rb +82 -0
  181. data/test/include_tree/from_include_args_test.rb +26 -0
  182. data/test/include_tree/from_string_test.rb +94 -0
  183. data/test/include_tree/include_args_to_hash_test.rb +64 -0
  184. data/test/lint_test.rb +49 -0
  185. data/test/logger_test.rb +18 -0
  186. data/test/poro_test.rb +9 -0
  187. data/test/serializable_resource_test.rb +83 -0
  188. data/test/serializers/association_macros_test.rb +36 -0
  189. data/test/serializers/associations_test.rb +295 -0
  190. data/test/serializers/attribute_test.rb +151 -0
  191. data/test/serializers/attributes_test.rb +52 -0
  192. data/test/serializers/caching_configuration_test_isolated.rb +170 -0
  193. data/test/serializers/configuration_test.rb +32 -0
  194. data/test/serializers/fieldset_test.rb +14 -0
  195. data/test/serializers/meta_test.rb +196 -0
  196. data/test/serializers/options_test.rb +21 -0
  197. data/test/serializers/read_attribute_for_serialization_test.rb +79 -0
  198. data/test/serializers/root_test.rb +21 -0
  199. data/test/serializers/serialization_test.rb +55 -0
  200. data/test/serializers/serializer_for_test.rb +134 -0
  201. data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  202. data/test/support/isolated_unit.rb +79 -0
  203. data/test/support/rails5_shims.rb +47 -0
  204. data/test/support/rails_app.rb +45 -0
  205. data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  206. data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
  207. data/test/support/schemas/custom/show.json +7 -0
  208. data/test/support/schemas/hyper_schema.json +93 -0
  209. data/test/support/schemas/render_using_json_api.json +43 -0
  210. data/test/support/schemas/simple_json_pointers.json +10 -0
  211. data/test/support/serialization_testing.rb +53 -0
  212. data/test/test_helper.rb +48 -23
  213. metadata +449 -43
  214. data/DESIGN.textile +0 -586
  215. data/Gemfile.edge +0 -9
  216. data/bench/perf.rb +0 -43
  217. data/cruft.md +0 -19
  218. data/lib/active_model/array_serializer.rb +0 -104
  219. data/lib/active_record/serializer_override.rb +0 -16
  220. data/lib/generators/resource_override.rb +0 -13
  221. data/lib/generators/serializer/USAGE +0 -9
  222. data/lib/generators/serializer/serializer_generator.rb +0 -42
  223. data/lib/generators/serializer/templates/serializer.rb +0 -19
  224. data/test/association_test.rb +0 -592
  225. data/test/caching_test.rb +0 -96
  226. data/test/generators_test.rb +0 -85
  227. data/test/no_serialization_scope_test.rb +0 -34
  228. data/test/serialization_scope_name_test.rb +0 -67
  229. data/test/serialization_test.rb +0 -392
  230. data/test/serializer_support_test.rb +0 -51
  231. data/test/serializer_test.rb +0 -1465
  232. 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,7 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ # @api private
4
+ class CollectionReflection < Reflection
5
+ end
6
+ end
7
+ 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,10 @@
1
+ class ActiveModel::Serializer::ErrorSerializer < ActiveModel::Serializer
2
+ # @return [Hash<field_name,Array<error_message>>]
3
+ def as_json
4
+ object.errors.messages
5
+ end
6
+
7
+ def success?
8
+ false
9
+ end
10
+ 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
@@ -0,0 +1,10 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ # @api private
4
+ class HasManyReflection < CollectionReflection
5
+ def macro
6
+ :has_many
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ # @api private
4
+ class HasOneReflection < SingularReflection
5
+ def macro
6
+ :has_one
7
+ end
8
+ end
9
+ end
10
+ end