active_model_serializers 0.8.3 → 0.10.8

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 (235) hide show
  1. checksums.yaml +5 -5
  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 +105 -0
  6. data/.simplecov +110 -0
  7. data/.travis.yml +50 -24
  8. data/CHANGELOG.md +650 -6
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/CONTRIBUTING.md +105 -0
  11. data/Gemfile +69 -1
  12. data/{MIT-LICENSE.txt → MIT-LICENSE} +3 -2
  13. data/README.md +195 -545
  14. data/Rakefile +64 -8
  15. data/active_model_serializers.gemspec +62 -23
  16. data/appveyor.yml +28 -0
  17. data/bin/bench +171 -0
  18. data/bin/bench_regression +316 -0
  19. data/bin/rubocop +38 -0
  20. data/bin/serve_benchmark +39 -0
  21. data/docs/README.md +41 -0
  22. data/docs/STYLE.md +58 -0
  23. data/docs/general/adapters.md +269 -0
  24. data/docs/general/caching.md +58 -0
  25. data/docs/general/configuration_options.md +185 -0
  26. data/docs/general/deserialization.md +100 -0
  27. data/docs/general/fields.md +31 -0
  28. data/docs/general/getting_started.md +133 -0
  29. data/docs/general/instrumentation.md +40 -0
  30. data/docs/general/key_transforms.md +40 -0
  31. data/docs/general/logging.md +21 -0
  32. data/docs/general/rendering.md +293 -0
  33. data/docs/general/serializers.md +495 -0
  34. data/docs/how-open-source-maintained.jpg +0 -0
  35. data/docs/howto/add_pagination_links.md +138 -0
  36. data/docs/howto/add_relationship_links.md +140 -0
  37. data/docs/howto/add_root_key.md +62 -0
  38. data/docs/howto/grape_integration.md +42 -0
  39. data/docs/howto/outside_controller_use.md +66 -0
  40. data/docs/howto/passing_arbitrary_options.md +27 -0
  41. data/docs/howto/serialize_poro.md +73 -0
  42. data/docs/howto/test.md +154 -0
  43. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  44. data/docs/integrations/ember-and-json-api.md +147 -0
  45. data/docs/integrations/grape.md +19 -0
  46. data/docs/jsonapi/errors.md +56 -0
  47. data/docs/jsonapi/schema/schema.json +366 -0
  48. data/docs/jsonapi/schema.md +151 -0
  49. data/docs/rfcs/0000-namespace.md +106 -0
  50. data/docs/rfcs/template.md +15 -0
  51. data/lib/action_controller/serialization.rb +43 -38
  52. data/lib/active_model/serializable_resource.rb +11 -0
  53. data/lib/active_model/serializer/adapter/attributes.rb +15 -0
  54. data/lib/active_model/serializer/adapter/base.rb +18 -0
  55. data/lib/active_model/serializer/adapter/json.rb +15 -0
  56. data/lib/active_model/serializer/adapter/json_api.rb +15 -0
  57. data/lib/active_model/serializer/adapter/null.rb +15 -0
  58. data/lib/active_model/serializer/adapter.rb +24 -0
  59. data/lib/active_model/serializer/array_serializer.rb +12 -0
  60. data/lib/active_model/serializer/association.rb +71 -0
  61. data/lib/active_model/serializer/attribute.rb +25 -0
  62. data/lib/active_model/serializer/belongs_to_reflection.rb +11 -0
  63. data/lib/active_model/serializer/collection_serializer.rb +88 -0
  64. data/lib/active_model/serializer/concerns/caching.rb +300 -0
  65. data/lib/active_model/serializer/error_serializer.rb +14 -0
  66. data/lib/active_model/serializer/errors_serializer.rb +32 -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 +7 -0
  71. data/lib/active_model/serializer/lazy_association.rb +96 -0
  72. data/lib/active_model/serializer/link.rb +21 -0
  73. data/lib/active_model/serializer/lint.rb +150 -0
  74. data/lib/active_model/serializer/null.rb +17 -0
  75. data/lib/active_model/serializer/reflection.rb +210 -0
  76. data/lib/active_model/{serializers → serializer}/version.rb +1 -1
  77. data/lib/active_model/serializer.rb +343 -442
  78. data/lib/active_model_serializers/adapter/attributes.rb +13 -0
  79. data/lib/active_model_serializers/adapter/base.rb +83 -0
  80. data/lib/active_model_serializers/adapter/json.rb +21 -0
  81. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +213 -0
  82. data/lib/active_model_serializers/adapter/json_api/error.rb +96 -0
  83. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +49 -0
  84. data/lib/active_model_serializers/adapter/json_api/link.rb +83 -0
  85. data/lib/active_model_serializers/adapter/json_api/meta.rb +37 -0
  86. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +88 -0
  87. data/lib/active_model_serializers/adapter/json_api/relationship.rb +104 -0
  88. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +66 -0
  89. data/lib/active_model_serializers/adapter/json_api.rb +533 -0
  90. data/lib/active_model_serializers/adapter/null.rb +9 -0
  91. data/lib/active_model_serializers/adapter.rb +98 -0
  92. data/lib/active_model_serializers/callbacks.rb +55 -0
  93. data/lib/active_model_serializers/deprecate.rb +54 -0
  94. data/lib/active_model_serializers/deserialization.rb +15 -0
  95. data/lib/active_model_serializers/json_pointer.rb +14 -0
  96. data/lib/active_model_serializers/logging.rb +122 -0
  97. data/lib/active_model_serializers/lookup_chain.rb +80 -0
  98. data/lib/active_model_serializers/model.rb +130 -0
  99. data/lib/active_model_serializers/railtie.rb +50 -0
  100. data/lib/active_model_serializers/register_jsonapi_renderer.rb +78 -0
  101. data/lib/active_model_serializers/serializable_resource.rb +82 -0
  102. data/lib/active_model_serializers/serialization_context.rb +39 -0
  103. data/lib/active_model_serializers/test/schema.rb +138 -0
  104. data/lib/active_model_serializers/test/serializer.rb +125 -0
  105. data/lib/active_model_serializers/test.rb +7 -0
  106. data/lib/active_model_serializers.rb +47 -81
  107. data/lib/generators/rails/USAGE +6 -0
  108. data/lib/generators/rails/resource_override.rb +10 -0
  109. data/lib/generators/rails/serializer_generator.rb +36 -0
  110. data/lib/generators/rails/templates/serializer.rb.erb +8 -0
  111. data/lib/grape/active_model_serializers.rb +16 -0
  112. data/lib/grape/formatters/active_model_serializers.rb +32 -0
  113. data/lib/grape/helpers/active_model_serializers.rb +17 -0
  114. data/lib/tasks/rubocop.rake +53 -0
  115. data/test/action_controller/adapter_selector_test.rb +62 -0
  116. data/test/action_controller/explicit_serializer_test.rb +135 -0
  117. data/test/action_controller/json/include_test.rb +246 -0
  118. data/test/action_controller/json_api/deserialization_test.rb +112 -0
  119. data/test/action_controller/json_api/errors_test.rb +40 -0
  120. data/test/action_controller/json_api/fields_test.rb +66 -0
  121. data/test/action_controller/json_api/linked_test.rb +202 -0
  122. data/test/action_controller/json_api/pagination_test.rb +124 -0
  123. data/test/action_controller/json_api/transform_test.rb +189 -0
  124. data/test/action_controller/lookup_proc_test.rb +49 -0
  125. data/test/action_controller/namespace_lookup_test.rb +232 -0
  126. data/test/action_controller/serialization_scope_name_test.rb +235 -0
  127. data/test/action_controller/serialization_test.rb +478 -0
  128. data/test/active_model_serializers/adapter_for_test.rb +208 -0
  129. data/test/active_model_serializers/json_pointer_test.rb +22 -0
  130. data/test/active_model_serializers/logging_test.rb +77 -0
  131. data/test/active_model_serializers/model_test.rb +142 -0
  132. data/test/active_model_serializers/railtie_test_isolated.rb +68 -0
  133. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
  134. data/test/active_model_serializers/serialization_context_test_isolated.rb +71 -0
  135. data/test/active_model_serializers/test/schema_test.rb +131 -0
  136. data/test/active_model_serializers/test/serializer_test.rb +62 -0
  137. data/test/active_record_test.rb +9 -0
  138. data/test/adapter/attributes_test.rb +40 -0
  139. data/test/adapter/deprecation_test.rb +100 -0
  140. data/test/adapter/json/belongs_to_test.rb +45 -0
  141. data/test/adapter/json/collection_test.rb +104 -0
  142. data/test/adapter/json/has_many_test.rb +53 -0
  143. data/test/adapter/json/transform_test.rb +93 -0
  144. data/test/adapter/json_api/belongs_to_test.rb +155 -0
  145. data/test/adapter/json_api/collection_test.rb +96 -0
  146. data/test/adapter/json_api/errors_test.rb +76 -0
  147. data/test/adapter/json_api/fields_test.rb +96 -0
  148. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +96 -0
  149. data/test/adapter/json_api/has_many_test.rb +173 -0
  150. data/test/adapter/json_api/has_one_test.rb +80 -0
  151. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +213 -0
  152. data/test/adapter/json_api/json_api_test.rb +33 -0
  153. data/test/adapter/json_api/linked_test.rb +413 -0
  154. data/test/adapter/json_api/links_test.rb +110 -0
  155. data/test/adapter/json_api/pagination_links_test.rb +206 -0
  156. data/test/adapter/json_api/parse_test.rb +137 -0
  157. data/test/adapter/json_api/relationship_test.rb +397 -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 +512 -0
  161. data/test/adapter/json_api/type_test.rb +193 -0
  162. data/test/adapter/json_test.rb +46 -0
  163. data/test/adapter/null_test.rb +22 -0
  164. data/test/adapter/polymorphic_test.rb +218 -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_active_record.rb +81 -0
  170. data/test/benchmark/bm_adapter.rb +38 -0
  171. data/test/benchmark/bm_caching.rb +119 -0
  172. data/test/benchmark/bm_lookup_chain.rb +83 -0
  173. data/test/benchmark/bm_transform.rb +45 -0
  174. data/test/benchmark/config.ru +3 -0
  175. data/test/benchmark/controllers.rb +83 -0
  176. data/test/benchmark/fixtures.rb +219 -0
  177. data/test/cache_test.rb +651 -0
  178. data/test/collection_serializer_test.rb +127 -0
  179. data/test/fixtures/active_record.rb +113 -0
  180. data/test/fixtures/poro.rb +225 -0
  181. data/test/generators/scaffold_controller_generator_test.rb +24 -0
  182. data/test/generators/serializer_generator_test.rb +75 -0
  183. data/test/grape_test.rb +196 -0
  184. data/test/lint_test.rb +49 -0
  185. data/test/logger_test.rb +20 -0
  186. data/test/poro_test.rb +9 -0
  187. data/test/serializable_resource_test.rb +79 -0
  188. data/test/serializers/association_macros_test.rb +37 -0
  189. data/test/serializers/associations_test.rb +518 -0
  190. data/test/serializers/attribute_test.rb +153 -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 +202 -0
  196. data/test/serializers/options_test.rb +32 -0
  197. data/test/serializers/read_attribute_for_serialization_test.rb +79 -0
  198. data/test/serializers/reflection_test.rb +479 -0
  199. data/test/serializers/root_test.rb +21 -0
  200. data/test/serializers/serialization_test.rb +55 -0
  201. data/test/serializers/serializer_for_test.rb +136 -0
  202. data/test/serializers/serializer_for_with_namespace_test.rb +88 -0
  203. data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  204. data/test/support/isolated_unit.rb +84 -0
  205. data/test/support/rails5_shims.rb +53 -0
  206. data/test/support/rails_app.rb +38 -0
  207. data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  208. data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
  209. data/test/support/schemas/custom/show.json +7 -0
  210. data/test/support/schemas/hyper_schema.json +93 -0
  211. data/test/support/schemas/render_using_json_api.json +43 -0
  212. data/test/support/schemas/simple_json_pointers.json +10 -0
  213. data/test/support/serialization_testing.rb +79 -0
  214. data/test/test_helper.rb +59 -21
  215. metadata +529 -43
  216. data/DESIGN.textile +0 -586
  217. data/Gemfile.edge +0 -9
  218. data/bench/perf.rb +0 -43
  219. data/cruft.md +0 -19
  220. data/lib/active_model/array_serializer.rb +0 -104
  221. data/lib/active_model/serializer/associations.rb +0 -233
  222. data/lib/active_record/serializer_override.rb +0 -16
  223. data/lib/generators/resource_override.rb +0 -13
  224. data/lib/generators/serializer/USAGE +0 -9
  225. data/lib/generators/serializer/serializer_generator.rb +0 -42
  226. data/lib/generators/serializer/templates/serializer.rb +0 -19
  227. data/test/association_test.rb +0 -592
  228. data/test/caching_test.rb +0 -96
  229. data/test/generators_test.rb +0 -85
  230. data/test/no_serialization_scope_test.rb +0 -34
  231. data/test/serialization_scope_name_test.rb +0 -67
  232. data/test/serialization_test.rb +0 -392
  233. data/test/serializer_support_test.rb +0 -51
  234. data/test/serializer_test.rb +0 -1465
  235. data/test/test_fakes.rb +0 -217
@@ -0,0 +1,300 @@
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 :_cache_key # @api private : when present, is first item in cache_key. Ignored if the serializable object defines #cache_key.
11
+ serializer.class_attribute :_cache_only # @api private : when fragment caching, whitelists fetch_attributes. Cannot combine with except
12
+ serializer.class_attribute :_cache_except # @api private : when fragment caching, blacklists fetch_attributes. Cannot combine with only
13
+ serializer.class_attribute :_cache_options # @api private : used by CachedSerializer, passed to _cache.fetch
14
+ # _cache_options include:
15
+ # expires_in
16
+ # compress
17
+ # force
18
+ # race_condition_ttl
19
+ # Passed to ::_cache as
20
+ # serializer.cache_store.fetch(cache_key, @klass._cache_options)
21
+ # Passed as second argument to serializer.cache_store.fetch(cache_key, serializer_class._cache_options)
22
+ serializer.class_attribute :_cache_digest_file_path # @api private : Derived at inheritance
23
+ end
24
+ end
25
+
26
+ # Matches
27
+ # "c:/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
28
+ # AND
29
+ # "/c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb:1:in `<top (required)>'"
30
+ # AS
31
+ # c/git/emberjs/ember-crm-backend/app/serializers/lead_serializer.rb
32
+ CALLER_FILE = /
33
+ \A # start of string
34
+ .+ # file path (one or more characters)
35
+ (?= # stop previous match when
36
+ :\d+ # a colon is followed by one or more digits
37
+ :in # followed by a colon followed by in
38
+ )
39
+ /x
40
+
41
+ module ClassMethods
42
+ def inherited(base)
43
+ caller_line = caller[1]
44
+ base._cache_digest_file_path = caller_line
45
+ super
46
+ end
47
+
48
+ def _cache_digest
49
+ return @_cache_digest if defined?(@_cache_digest)
50
+ @_cache_digest = digest_caller_file(_cache_digest_file_path)
51
+ end
52
+
53
+ # Hashes contents of file for +_cache_digest+
54
+ def digest_caller_file(caller_line)
55
+ serializer_file_path = caller_line[CALLER_FILE]
56
+ serializer_file_contents = IO.read(serializer_file_path)
57
+ Digest::MD5.hexdigest(serializer_file_contents)
58
+ rescue TypeError, Errno::ENOENT
59
+ warn <<-EOF.strip_heredoc
60
+ Cannot digest non-existent file: '#{caller_line}'.
61
+ Please set `::_cache_digest` of the serializer
62
+ if you'd like to cache it.
63
+ EOF
64
+ ''.freeze
65
+ end
66
+
67
+ def _skip_digest?
68
+ _cache_options && _cache_options[:skip_digest]
69
+ end
70
+
71
+ # @api private
72
+ # maps attribute value to explicit key name
73
+ # @see Serializer::attribute
74
+ # @see Serializer::fragmented_attributes
75
+ def _attributes_keys
76
+ _attributes_data
77
+ .each_with_object({}) do |(key, attr), hash|
78
+ next if key == attr.name
79
+ hash[attr.name] = { key: key }
80
+ end
81
+ end
82
+
83
+ def fragmented_attributes
84
+ cached = _cache_only ? _cache_only : _attributes - _cache_except
85
+ cached = cached.map! { |field| _attributes_keys.fetch(field, field) }
86
+ non_cached = _attributes - cached
87
+ non_cached = non_cached.map! { |field| _attributes_keys.fetch(field, field) }
88
+ {
89
+ cached: cached,
90
+ non_cached: non_cached
91
+ }
92
+ end
93
+
94
+ # Enables a serializer to be automatically cached
95
+ #
96
+ # Sets +::_cache+ object to <tt>ActionController::Base.cache_store</tt>
97
+ # when Rails.configuration.action_controller.perform_caching
98
+ #
99
+ # @param options [Hash] with valid keys:
100
+ # cache_store : @see ::_cache
101
+ # key : @see ::_cache_key
102
+ # only : @see ::_cache_only
103
+ # except : @see ::_cache_except
104
+ # skip_digest : does not include digest in cache_key
105
+ # all else : @see ::_cache_options
106
+ #
107
+ # @example
108
+ # class PostSerializer < ActiveModel::Serializer
109
+ # cache key: 'post', expires_in: 3.hours
110
+ # attributes :title, :body
111
+ #
112
+ # has_many :comments
113
+ # end
114
+ #
115
+ # @todo require less code comments. See
116
+ # https://github.com/rails-api/active_model_serializers/pull/1249#issuecomment-146567837
117
+ def cache(options = {})
118
+ self._cache =
119
+ options.delete(:cache_store) ||
120
+ ActiveModelSerializers.config.cache_store ||
121
+ ActiveSupport::Cache.lookup_store(:null_store)
122
+ self._cache_key = options.delete(:key)
123
+ self._cache_only = options.delete(:only)
124
+ self._cache_except = options.delete(:except)
125
+ self._cache_options = options.empty? ? nil : options
126
+ end
127
+
128
+ # Value is from ActiveModelSerializers.config.perform_caching. Is used to
129
+ # globally enable or disable all serializer caching, just like
130
+ # Rails.configuration.action_controller.perform_caching, which is its
131
+ # default value in a Rails application.
132
+ # @return [true, false]
133
+ # Memoizes value of config first time it is called with a non-nil value.
134
+ # rubocop:disable Style/ClassVars
135
+ def perform_caching
136
+ return @@perform_caching if defined?(@@perform_caching) && !@@perform_caching.nil?
137
+ @@perform_caching = ActiveModelSerializers.config.perform_caching
138
+ end
139
+ alias perform_caching? perform_caching
140
+ # rubocop:enable Style/ClassVars
141
+
142
+ # The canonical method for getting the cache store for the serializer.
143
+ #
144
+ # @return [nil] when _cache is not set (i.e. when `cache` has not been called)
145
+ # @return [._cache] when _cache is not the NullStore
146
+ # @return [ActiveModelSerializers.config.cache_store] when _cache is the NullStore.
147
+ # This is so we can use `cache` being called to mean the serializer should be cached
148
+ # even if ActiveModelSerializers.config.cache_store has not yet been set.
149
+ # That means that when _cache is the NullStore and ActiveModelSerializers.config.cache_store
150
+ # is configured, `cache_store` becomes `ActiveModelSerializers.config.cache_store`.
151
+ # @return [nil] when _cache is the NullStore and ActiveModelSerializers.config.cache_store is nil.
152
+ def cache_store
153
+ return nil if _cache.nil?
154
+ return _cache if _cache.class != ActiveSupport::Cache::NullStore
155
+ if ActiveModelSerializers.config.cache_store
156
+ self._cache = ActiveModelSerializers.config.cache_store
157
+ else
158
+ nil
159
+ end
160
+ end
161
+
162
+ def cache_enabled?
163
+ perform_caching? && cache_store && !_cache_only && !_cache_except
164
+ end
165
+
166
+ def fragment_cache_enabled?
167
+ perform_caching? && cache_store &&
168
+ (_cache_only && !_cache_except || !_cache_only && _cache_except)
169
+ end
170
+
171
+ # Read cache from cache_store
172
+ # @return [Hash]
173
+ # Used in CollectionSerializer to set :cached_attributes
174
+ def cache_read_multi(collection_serializer, adapter_instance, include_directive)
175
+ return {} if ActiveModelSerializers.config.cache_store.blank?
176
+
177
+ keys = object_cache_keys(collection_serializer, adapter_instance, include_directive)
178
+
179
+ return {} if keys.blank?
180
+
181
+ ActiveModelSerializers.config.cache_store.read_multi(*keys)
182
+ end
183
+
184
+ # Find all cache_key for the collection_serializer
185
+ # @param serializers [ActiveModel::Serializer::CollectionSerializer]
186
+ # @param adapter_instance [ActiveModelSerializers::Adapter::Base]
187
+ # @param include_directive [JSONAPI::IncludeDirective]
188
+ # @return [Array] all cache_key of collection_serializer
189
+ def object_cache_keys(collection_serializer, adapter_instance, include_directive)
190
+ cache_keys = []
191
+
192
+ collection_serializer.each do |serializer|
193
+ cache_keys << object_cache_key(serializer, adapter_instance)
194
+
195
+ serializer.associations(include_directive).each do |association|
196
+ # TODO(BF): Process relationship without evaluating lazy_association
197
+ association_serializer = association.lazy_association.serializer
198
+ if association_serializer.respond_to?(:each)
199
+ association_serializer.each do |sub_serializer|
200
+ cache_keys << object_cache_key(sub_serializer, adapter_instance)
201
+ end
202
+ else
203
+ cache_keys << object_cache_key(association_serializer, adapter_instance)
204
+ end
205
+ end
206
+ end
207
+
208
+ cache_keys.compact.uniq
209
+ end
210
+
211
+ # @return [String, nil] the cache_key of the serializer or nil
212
+ def object_cache_key(serializer, adapter_instance)
213
+ return unless serializer.present? && serializer.object.present?
214
+
215
+ (serializer.class.cache_enabled? || serializer.class.fragment_cache_enabled?) ? serializer.cache_key(adapter_instance) : nil
216
+ end
217
+ end
218
+
219
+ ### INSTANCE METHODS
220
+ def fetch_attributes(fields, cached_attributes, adapter_instance)
221
+ key = cache_key(adapter_instance)
222
+ cached_attributes.fetch(key) do
223
+ fetch(adapter_instance, serializer_class._cache_options, key) do
224
+ attributes(fields, true)
225
+ end
226
+ end
227
+ end
228
+
229
+ def fetch(adapter_instance, cache_options = serializer_class._cache_options, key = nil)
230
+ if serializer_class.cache_store
231
+ key ||= cache_key(adapter_instance)
232
+ serializer_class.cache_store.fetch(key, cache_options) do
233
+ yield
234
+ end
235
+ else
236
+ yield
237
+ end
238
+ end
239
+
240
+ # 1. Determine cached fields from serializer class options
241
+ # 2. Get non_cached_fields and fetch cache_fields
242
+ # 3. Merge the two hashes using adapter_instance#fragment_cache
243
+ def fetch_attributes_fragment(adapter_instance, cached_attributes = {})
244
+ serializer_class._cache_options ||= {}
245
+ serializer_class._cache_options[:key] = serializer_class._cache_key if serializer_class._cache_key
246
+ fields = serializer_class.fragmented_attributes
247
+
248
+ non_cached_fields = fields[:non_cached].dup
249
+ non_cached_hash = attributes(non_cached_fields, true)
250
+ include_directive = JSONAPI::IncludeDirective.new(non_cached_fields - non_cached_hash.keys)
251
+ non_cached_hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
252
+
253
+ cached_fields = fields[:cached].dup
254
+ key = cache_key(adapter_instance)
255
+ cached_hash =
256
+ cached_attributes.fetch(key) do
257
+ fetch(adapter_instance, serializer_class._cache_options, key) do
258
+ hash = attributes(cached_fields, true)
259
+ include_directive = JSONAPI::IncludeDirective.new(cached_fields - hash.keys)
260
+ hash.merge! associations_hash({}, { include_directive: include_directive }, adapter_instance)
261
+ end
262
+ end
263
+ # Merge both results
264
+ adapter_instance.fragment_cache(cached_hash, non_cached_hash)
265
+ end
266
+
267
+ def cache_key(adapter_instance)
268
+ return @cache_key if defined?(@cache_key)
269
+
270
+ parts = []
271
+ parts << object_cache_key
272
+ parts << adapter_instance.cache_key
273
+ parts << serializer_class._cache_digest unless serializer_class._skip_digest?
274
+ @cache_key = expand_cache_key(parts)
275
+ end
276
+
277
+ def expand_cache_key(parts)
278
+ ActiveSupport::Cache.expand_cache_key(parts)
279
+ end
280
+
281
+ # Use object's cache_key if available, else derive a key from the object
282
+ # Pass the `key` option to the `cache` declaration or override this method to customize the cache key
283
+ def object_cache_key
284
+ if object.respond_to?(:cache_key)
285
+ object.cache_key
286
+ elsif (serializer_cache_key = (serializer_class._cache_key || serializer_class._cache_options[:key]))
287
+ object_time_safe = object.updated_at
288
+ object_time_safe = object_time_safe.strftime('%Y%m%d%H%M%S%9N') if object_time_safe.respond_to?(:strftime)
289
+ "#{serializer_cache_key}/#{object.id}-#{object_time_safe}"
290
+ else
291
+ fail UndefinedCacheKey, "#{object.class} must define #cache_key, or the 'key:' option must be passed into '#{serializer_class}.cache'"
292
+ end
293
+ end
294
+
295
+ def serializer_class
296
+ @serializer_class ||= self.class
297
+ end
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,14 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ class ErrorSerializer < ActiveModel::Serializer
4
+ # @return [Hash<field_name,Array<error_message>>]
5
+ def as_json
6
+ object.errors.messages
7
+ end
8
+
9
+ def success?
10
+ false
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ require 'active_model/serializer/error_serializer'
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ class ErrorsSerializer
6
+ include Enumerable
7
+ delegate :each, to: :@serializers
8
+ attr_reader :object, :root
9
+
10
+ def initialize(resources, options = {})
11
+ @root = options[:root]
12
+ @object = resources
13
+ @serializers = resources.map do |resource|
14
+ serializer_class = options.fetch(:serializer) { ActiveModel::Serializer::ErrorSerializer }
15
+ serializer_class.new(resource, options.except(:serializer))
16
+ end
17
+ end
18
+
19
+ def success?
20
+ false
21
+ end
22
+
23
+ def json_key
24
+ nil
25
+ end
26
+
27
+ protected
28
+
29
+ attr_reader :serializers
30
+ end
31
+ end
32
+ 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 < Reflection
5
+ def collection?
6
+ true
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ # @api private
4
+ class HasOneReflection < Reflection
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,96 @@
1
+ module ActiveModel
2
+ class Serializer
3
+ # @api private
4
+ LazyAssociation = Struct.new(:reflection, :association_options) do
5
+ REFLECTION_OPTIONS = %i(key links polymorphic meta serializer virtual_value namespace).freeze
6
+
7
+ delegate :collection?, to: :reflection
8
+
9
+ def reflection_options
10
+ @reflection_options ||= reflection.options.dup.reject { |k, _| !REFLECTION_OPTIONS.include?(k) }
11
+ end
12
+
13
+ def object
14
+ @object ||= reflection.value(
15
+ association_options.fetch(:parent_serializer),
16
+ association_options.fetch(:include_slice)
17
+ )
18
+ end
19
+ alias_method :eval_reflection_block, :object
20
+
21
+ def include_data?
22
+ eval_reflection_block if reflection.block
23
+ reflection.include_data?(
24
+ association_options.fetch(:include_slice)
25
+ )
26
+ end
27
+
28
+ # @return [ActiveModel::Serializer, nil]
29
+ def serializer
30
+ return @serializer if defined?(@serializer)
31
+ if serializer_class
32
+ serialize_object!(object)
33
+ elsif !object.nil? && !object.instance_of?(Object)
34
+ cached_result[:virtual_value] = object
35
+ end
36
+ @serializer = cached_result[:serializer]
37
+ end
38
+
39
+ def virtual_value
40
+ cached_result[:virtual_value] || reflection_options[:virtual_value]
41
+ end
42
+
43
+ def serializer_class
44
+ return @serializer_class if defined?(@serializer_class)
45
+ serializer_for_options = { namespace: namespace }
46
+ serializer_for_options[:serializer] = reflection_options[:serializer] if reflection_options.key?(:serializer)
47
+ @serializer_class = association_options.fetch(:parent_serializer).class.serializer_for(object, serializer_for_options)
48
+ end
49
+
50
+ private
51
+
52
+ def cached_result
53
+ @cached_result ||= {}
54
+ end
55
+
56
+ def serialize_object!(object)
57
+ if collection?
58
+ if (serializer = instantiate_collection_serializer(object)).nil?
59
+ # BUG: per #2027, JSON API resource relationships are only id and type, and hence either
60
+ # *require* a serializer or we need to be a little clever about figuring out the id/type.
61
+ # In either case, returning the raw virtual value will almost always be incorrect.
62
+ #
63
+ # Should be reflection_options[:virtual_value] or adapter needs to figure out what to do
64
+ # with an object that is non-nil and has no defined serializer.
65
+ cached_result[:virtual_value] = object.try(:as_json) || object
66
+ else
67
+ cached_result[:serializer] = serializer
68
+ end
69
+ else
70
+ cached_result[:serializer] = instantiate_serializer(object)
71
+ end
72
+ end
73
+
74
+ def instantiate_serializer(object)
75
+ serializer_options = association_options.fetch(:parent_serializer_options).except(:serializer)
76
+ serializer_options[:serializer_context_class] = association_options.fetch(:parent_serializer).class
77
+ serializer = reflection_options.fetch(:serializer, nil)
78
+ serializer_options[:serializer] = serializer if serializer
79
+ serializer_options[:namespace] = reflection_options[:namespace] if reflection_options[:namespace]
80
+ serializer_class.new(object, serializer_options)
81
+ end
82
+
83
+ def instantiate_collection_serializer(object)
84
+ serializer = catch(:no_serializer) do
85
+ instantiate_serializer(object)
86
+ end
87
+ serializer
88
+ end
89
+
90
+ def namespace
91
+ reflection_options[:namespace] ||
92
+ association_options.fetch(:parent_serializer_options)[:namespace]
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_model/serializer/field'
2
+
3
+ module ActiveModel
4
+ class Serializer
5
+ # Holds all the data about a serializer link
6
+ #
7
+ # @example
8
+ # class PostSerializer < ActiveModel::Serializer
9
+ # link :callback, if: :internal? do
10
+ # object.callback_link
11
+ # end
12
+ #
13
+ # def internal?
14
+ # instance_options[:internal] == true
15
+ # end
16
+ # end
17
+ #
18
+ class Link < Field
19
+ end
20
+ end
21
+ end