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,104 @@
1
+ module ActiveModelSerializers
2
+ module Adapter
3
+ class JsonApi
4
+ class Relationship
5
+ # {http://jsonapi.org/format/#document-resource-object-related-resource-links Document Resource Object Related Resource Links}
6
+ # {http://jsonapi.org/format/#document-links Document Links}
7
+ # {http://jsonapi.org/format/#document-resource-object-linkage Document Resource Relationship Linkage}
8
+ # {http://jsonapi.org/format/#document-meta Document Meta}
9
+ def initialize(parent_serializer, serializable_resource_options, association)
10
+ @parent_serializer = parent_serializer
11
+ @association = association
12
+ @serializable_resource_options = serializable_resource_options
13
+ end
14
+
15
+ def as_json
16
+ hash = {}
17
+
18
+ hash[:data] = data_for(association) if association.include_data?
19
+
20
+ links = links_for(association)
21
+ hash[:links] = links if links.any?
22
+
23
+ meta = meta_for(association)
24
+ hash[:meta] = meta if meta
25
+ hash[:meta] = {} if hash.empty?
26
+
27
+ hash
28
+ end
29
+
30
+ protected
31
+
32
+ attr_reader :parent_serializer, :serializable_resource_options, :association
33
+
34
+ private
35
+
36
+ # TODO(BF): Avoid db hit on belong_to_ releationship by using foreign_key on self
37
+ def data_for(association)
38
+ if association.collection?
39
+ data_for_many(association)
40
+ else
41
+ data_for_one(association)
42
+ end
43
+ end
44
+
45
+ def data_for_one(association)
46
+ if belongs_to_id_on_self?(association)
47
+ id = parent_serializer.read_attribute_for_serialization(association.reflection.foreign_key)
48
+ type =
49
+ if association.polymorphic?
50
+ # We can't infer resource type for polymorphic relationships from the serializer.
51
+ # We can ONLY know a polymorphic resource type by inspecting each resource.
52
+ association.lazy_association.serializer.json_key
53
+ else
54
+ association.reflection.type.to_s
55
+ end
56
+ ResourceIdentifier.for_type_with_id(type, id, serializable_resource_options)
57
+ else
58
+ # TODO(BF): Process relationship without evaluating lazy_association
59
+ serializer = association.lazy_association.serializer
60
+ if (virtual_value = association.virtual_value)
61
+ virtual_value
62
+ elsif serializer && association.object
63
+ ResourceIdentifier.new(serializer, serializable_resource_options).as_json
64
+ else
65
+ nil
66
+ end
67
+ end
68
+ end
69
+
70
+ def data_for_many(association)
71
+ # TODO(BF): Process relationship without evaluating lazy_association
72
+ collection_serializer = association.lazy_association.serializer
73
+ if collection_serializer.respond_to?(:each)
74
+ collection_serializer.map do |serializer|
75
+ ResourceIdentifier.new(serializer, serializable_resource_options).as_json
76
+ end
77
+ elsif (virtual_value = association.virtual_value)
78
+ virtual_value
79
+ else
80
+ []
81
+ end
82
+ end
83
+
84
+ def links_for(association)
85
+ association.links.each_with_object({}) do |(key, value), hash|
86
+ result = Link.new(parent_serializer, value).as_json
87
+ hash[key] = result if result
88
+ end
89
+ end
90
+
91
+ def meta_for(association)
92
+ meta = association.meta
93
+ meta.respond_to?(:call) ? parent_serializer.instance_eval(&meta) : meta
94
+ end
95
+
96
+ def belongs_to_id_on_self?(association)
97
+ parent_serializer.config.jsonapi_use_foreign_key_on_belongs_to_relationship &&
98
+ association.belongs_to? &&
99
+ parent_serializer.object.respond_to?(association.reflection.foreign_key)
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,66 @@
1
+ module ActiveModelSerializers
2
+ module Adapter
3
+ class JsonApi
4
+ class ResourceIdentifier
5
+ def self.type_for(serializer, serializer_type = nil, transform_options = {})
6
+ raw_type = serializer_type ? serializer_type : raw_type_from_serializer_object(serializer.object)
7
+ JsonApi.send(:transform_key_casing!, raw_type, transform_options)
8
+ end
9
+
10
+ def self.for_type_with_id(type, id, options)
11
+ type = inflect_type(type)
12
+ type = type_for(:no_class_needed, type, options)
13
+ if id.blank?
14
+ { type: type }
15
+ else
16
+ { id: id.to_s, type: type }
17
+ end
18
+ end
19
+
20
+ def self.raw_type_from_serializer_object(object)
21
+ class_name = object.class.name # should use model_name
22
+ raw_type = class_name.underscore
23
+ raw_type = inflect_type(raw_type)
24
+ raw_type
25
+ .gsub!('/'.freeze, ActiveModelSerializers.config.jsonapi_namespace_separator)
26
+ raw_type
27
+ end
28
+
29
+ def self.inflect_type(type)
30
+ singularize = ActiveModelSerializers.config.jsonapi_resource_type == :singular
31
+ inflection = singularize ? :singularize : :pluralize
32
+ ActiveSupport::Inflector.public_send(inflection, type)
33
+ end
34
+
35
+ # {http://jsonapi.org/format/#document-resource-identifier-objects Resource Identifier Objects}
36
+ def initialize(serializer, options)
37
+ @id = id_for(serializer)
38
+ @type = type_for(serializer, options)
39
+ end
40
+
41
+ def as_json
42
+ if id.blank?
43
+ { type: type }
44
+ else
45
+ { id: id.to_s, type: type }
46
+ end
47
+ end
48
+
49
+ protected
50
+
51
+ attr_reader :id, :type
52
+
53
+ private
54
+
55
+ def type_for(serializer, transform_options)
56
+ serializer_type = serializer._type
57
+ self.class.type_for(serializer, serializer_type, transform_options)
58
+ end
59
+
60
+ def id_for(serializer)
61
+ serializer.read_attribute_for_serialization(:id).to_s
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,533 @@
1
+ # {http://jsonapi.org/format/ JSON API specification}
2
+ # rubocop:disable Style/AsciiComments
3
+ # TODO: implement!
4
+ # ☐ https://github.com/rails-api/active_model_serializers/issues/1235
5
+ # TODO: use uri_template in link generation?
6
+ # ☐ https://github.com/rails-api/active_model_serializers/pull/1282#discussion_r42528812
7
+ # see gem https://github.com/hannesg/uri_template
8
+ # spec http://tools.ietf.org/html/rfc6570
9
+ # impl https://developer.github.com/v3/#schema https://api.github.com/
10
+ # TODO: validate against a JSON schema document?
11
+ # ☐ https://github.com/rails-api/active_model_serializers/issues/1162
12
+ # ☑ https://github.com/rails-api/active_model_serializers/pull/1270
13
+ # TODO: Routing
14
+ # ☐ https://github.com/rails-api/active_model_serializers/pull/1476
15
+ # TODO: Query Params
16
+ # ☑ `include` https://github.com/rails-api/active_model_serializers/pull/1131
17
+ # ☑ `fields` https://github.com/rails-api/active_model_serializers/pull/700
18
+ # ☑ `page[number]=3&page[size]=1` https://github.com/rails-api/active_model_serializers/pull/1041
19
+ # ☐ `filter`
20
+ # ☐ `sort`
21
+ module ActiveModelSerializers
22
+ module Adapter
23
+ class JsonApi < Base
24
+ extend ActiveSupport::Autoload
25
+ eager_autoload do
26
+ autoload :Jsonapi
27
+ autoload :ResourceIdentifier
28
+ autoload :Link
29
+ autoload :PaginationLinks
30
+ autoload :Meta
31
+ autoload :Error
32
+ autoload :Deserialization
33
+ autoload :Relationship
34
+ end
35
+
36
+ def self.default_key_transform
37
+ :dash
38
+ end
39
+
40
+ def self.fragment_cache(cached_hash, non_cached_hash, root = true)
41
+ core_cached = cached_hash.first
42
+ core_non_cached = non_cached_hash.first
43
+ no_root_cache = cached_hash.delete_if { |key, _value| key == core_cached[0] }
44
+ no_root_non_cache = non_cached_hash.delete_if { |key, _value| key == core_non_cached[0] }
45
+ cached_resource = (core_cached[1]) ? core_cached[1].deep_merge(core_non_cached[1]) : core_non_cached[1]
46
+ hash = root ? { root => cached_resource } : cached_resource
47
+
48
+ hash.deep_merge no_root_non_cache.deep_merge no_root_cache
49
+ end
50
+
51
+ def initialize(serializer, options = {})
52
+ super
53
+ @include_directive = JSONAPI::IncludeDirective.new(options[:include], allow_wildcard: true)
54
+ @fieldset = options[:fieldset] || ActiveModel::Serializer::Fieldset.new(options.delete(:fields))
55
+ end
56
+
57
+ # {http://jsonapi.org/format/#crud Requests are transactional, i.e. success or failure}
58
+ # {http://jsonapi.org/format/#document-top-level data and errors MUST NOT coexist in the same document.}
59
+ def serializable_hash(*)
60
+ document = if serializer.success?
61
+ success_document
62
+ else
63
+ failure_document
64
+ end
65
+ self.class.transform_key_casing!(document, instance_options)
66
+ end
67
+
68
+ def fragment_cache(cached_hash, non_cached_hash)
69
+ root = !instance_options.include?(:include)
70
+ self.class.fragment_cache(cached_hash, non_cached_hash, root)
71
+ end
72
+
73
+ # {http://jsonapi.org/format/#document-top-level Primary data}
74
+ # definition:
75
+ # ☐ toplevel_data (required)
76
+ # ☐ toplevel_included
77
+ # ☑ toplevel_meta
78
+ # ☑ toplevel_links
79
+ # ☑ toplevel_jsonapi
80
+ # structure:
81
+ # {
82
+ # data: toplevel_data,
83
+ # included: toplevel_included,
84
+ # meta: toplevel_meta,
85
+ # links: toplevel_links,
86
+ # jsonapi: toplevel_jsonapi
87
+ # }.reject! {|_,v| v.nil? }
88
+ # rubocop:disable Metrics/CyclomaticComplexity
89
+ def success_document
90
+ is_collection = serializer.respond_to?(:each)
91
+ serializers = is_collection ? serializer : [serializer]
92
+ primary_data, included = resource_objects_for(serializers)
93
+
94
+ hash = {}
95
+ # toplevel_data
96
+ # definition:
97
+ # oneOf
98
+ # resource
99
+ # array of unique items of type 'resource'
100
+ # null
101
+ #
102
+ # description:
103
+ # The document's "primary data" is a representation of the resource or collection of resources
104
+ # targeted by a request.
105
+ #
106
+ # Singular: the resource object.
107
+ #
108
+ # Collection: one of an array of resource objects, an array of resource identifier objects, or
109
+ # an empty array ([]), for requests that target resource collections.
110
+ #
111
+ # None: null if the request is one that might correspond to a single resource, but doesn't currently.
112
+ # structure:
113
+ # if serializable_resource.resource?
114
+ # resource
115
+ # elsif serializable_resource.collection?
116
+ # [
117
+ # resource,
118
+ # resource
119
+ # ]
120
+ # else
121
+ # nil
122
+ # end
123
+ hash[:data] = is_collection ? primary_data : primary_data[0]
124
+ # toplevel_included
125
+ # alias included
126
+ # definition:
127
+ # array of unique items of type 'resource'
128
+ #
129
+ # description:
130
+ # To reduce the number of HTTP requests, servers **MAY** allow
131
+ # responses that include related resources along with the requested primary
132
+ # resources. Such responses are called "compound documents".
133
+ # structure:
134
+ # [
135
+ # resource,
136
+ # resource
137
+ # ]
138
+ hash[:included] = included if included.any?
139
+
140
+ Jsonapi.add!(hash)
141
+
142
+ if instance_options[:links]
143
+ hash[:links] ||= {}
144
+ hash[:links].update(instance_options[:links])
145
+ end
146
+
147
+ if is_collection && serializer.paginated?
148
+ hash[:links] ||= {}
149
+ hash[:links].update(pagination_links_for(serializer))
150
+ end
151
+
152
+ hash[:meta] = instance_options[:meta] unless instance_options[:meta].blank?
153
+
154
+ hash
155
+ end
156
+ # rubocop:enable Metrics/CyclomaticComplexity
157
+
158
+ # {http://jsonapi.org/format/#errors JSON API Errors}
159
+ # TODO: look into caching
160
+ # definition:
161
+ # ☑ toplevel_errors array (required)
162
+ # ☐ toplevel_meta
163
+ # ☐ toplevel_jsonapi
164
+ # structure:
165
+ # {
166
+ # errors: toplevel_errors,
167
+ # meta: toplevel_meta,
168
+ # jsonapi: toplevel_jsonapi
169
+ # }.reject! {|_,v| v.nil? }
170
+ # prs:
171
+ # https://github.com/rails-api/active_model_serializers/pull/1004
172
+ def failure_document
173
+ hash = {}
174
+ # PR Please :)
175
+ # Jsonapi.add!(hash)
176
+
177
+ # toplevel_errors
178
+ # definition:
179
+ # array of unique items of type 'error'
180
+ # structure:
181
+ # [
182
+ # error,
183
+ # error
184
+ # ]
185
+ if serializer.respond_to?(:each)
186
+ hash[:errors] = serializer.flat_map do |error_serializer|
187
+ Error.resource_errors(error_serializer, instance_options)
188
+ end
189
+ else
190
+ hash[:errors] = Error.resource_errors(serializer, instance_options)
191
+ end
192
+ hash
193
+ end
194
+
195
+ protected
196
+
197
+ attr_reader :fieldset
198
+
199
+ private
200
+
201
+ # {http://jsonapi.org/format/#document-resource-objects Primary data}
202
+ # resource
203
+ # definition:
204
+ # JSON Object
205
+ #
206
+ # properties:
207
+ # type (required) : String
208
+ # id (required) : String
209
+ # attributes
210
+ # relationships
211
+ # links
212
+ # meta
213
+ #
214
+ # description:
215
+ # "Resource objects" appear in a JSON API document to represent resources
216
+ # structure:
217
+ # {
218
+ # type: 'admin--some-user',
219
+ # id: '1336',
220
+ # attributes: attributes,
221
+ # relationships: relationships,
222
+ # links: links,
223
+ # meta: meta,
224
+ # }.reject! {|_,v| v.nil? }
225
+ # prs:
226
+ # type
227
+ # https://github.com/rails-api/active_model_serializers/pull/1122
228
+ # [x] https://github.com/rails-api/active_model_serializers/pull/1213
229
+ # https://github.com/rails-api/active_model_serializers/pull/1216
230
+ # https://github.com/rails-api/active_model_serializers/pull/1029
231
+ # links
232
+ # [x] https://github.com/rails-api/active_model_serializers/pull/1246
233
+ # [x] url helpers https://github.com/rails-api/active_model_serializers/issues/1269
234
+ # meta
235
+ # [x] https://github.com/rails-api/active_model_serializers/pull/1340
236
+ def resource_objects_for(serializers)
237
+ @primary = []
238
+ @included = []
239
+ @resource_identifiers = Set.new
240
+ serializers.each { |serializer| process_resource(serializer, true, @include_directive) }
241
+ serializers.each { |serializer| process_relationships(serializer, @include_directive) }
242
+
243
+ [@primary, @included]
244
+ end
245
+
246
+ def process_resource(serializer, primary, include_slice = {})
247
+ resource_identifier = ResourceIdentifier.new(serializer, instance_options).as_json
248
+ return false unless @resource_identifiers.add?(resource_identifier)
249
+
250
+ resource_object = resource_object_for(serializer, include_slice)
251
+ if primary
252
+ @primary << resource_object
253
+ else
254
+ @included << resource_object
255
+ end
256
+
257
+ true
258
+ end
259
+
260
+ def process_relationships(serializer, include_slice)
261
+ serializer.associations(include_slice).each do |association|
262
+ # TODO(BF): Process relationship without evaluating lazy_association
263
+ process_relationship(association.lazy_association.serializer, include_slice[association.key])
264
+ end
265
+ end
266
+
267
+ def process_relationship(serializer, include_slice)
268
+ if serializer.respond_to?(:each)
269
+ serializer.each { |s| process_relationship(s, include_slice) }
270
+ return
271
+ end
272
+ return unless serializer && serializer.object
273
+ return unless process_resource(serializer, false, include_slice)
274
+
275
+ process_relationships(serializer, include_slice)
276
+ end
277
+
278
+ # {http://jsonapi.org/format/#document-resource-object-attributes Document Resource Object Attributes}
279
+ # attributes
280
+ # definition:
281
+ # JSON Object
282
+ #
283
+ # patternProperties:
284
+ # ^(?!relationships$|links$)\\w[-\\w_]*$
285
+ #
286
+ # description:
287
+ # Members of the attributes object ("attributes") represent information about the resource
288
+ # object in which it's defined.
289
+ # Attributes may contain any valid JSON value
290
+ # structure:
291
+ # {
292
+ # foo: 'bar'
293
+ # }
294
+ def attributes_for(serializer, fields)
295
+ serializer.attributes(fields).except(:id)
296
+ end
297
+
298
+ # {http://jsonapi.org/format/#document-resource-objects Document Resource Objects}
299
+ def resource_object_for(serializer, include_slice = {})
300
+ resource_object = data_for(serializer, include_slice)
301
+
302
+ # toplevel_links
303
+ # definition:
304
+ # allOf
305
+ # ☐ links
306
+ # ☐ pagination
307
+ #
308
+ # description:
309
+ # Link members related to the primary data.
310
+ # structure:
311
+ # links.merge!(pagination)
312
+ # prs:
313
+ # https://github.com/rails-api/active_model_serializers/pull/1247
314
+ # https://github.com/rails-api/active_model_serializers/pull/1018
315
+ if (links = links_for(serializer)).any?
316
+ resource_object ||= {}
317
+ resource_object[:links] = links
318
+ end
319
+
320
+ # toplevel_meta
321
+ # alias meta
322
+ # definition:
323
+ # meta
324
+ # structure
325
+ # {
326
+ # :'git-ref' => 'abc123'
327
+ # }
328
+ if (meta = meta_for(serializer)).present?
329
+ resource_object ||= {}
330
+ resource_object[:meta] = meta
331
+ end
332
+
333
+ resource_object
334
+ end
335
+
336
+ def data_for(serializer, include_slice)
337
+ data = serializer.fetch(self) do
338
+ resource_object = ResourceIdentifier.new(serializer, instance_options).as_json
339
+ break nil if resource_object.nil?
340
+
341
+ requested_fields = fieldset && fieldset.fields_for(resource_object[:type])
342
+ attributes = attributes_for(serializer, requested_fields)
343
+ resource_object[:attributes] = attributes if attributes.any?
344
+ resource_object
345
+ end
346
+ data.tap do |resource_object|
347
+ next if resource_object.nil?
348
+ # NOTE(BF): the attributes are cached above, separately from the relationships, below.
349
+ requested_associations = fieldset.fields_for(resource_object[:type]) || '*'
350
+ relationships = relationships_for(serializer, requested_associations, include_slice)
351
+ resource_object[:relationships] = relationships if relationships.any?
352
+ end
353
+ end
354
+
355
+ # {http://jsonapi.org/format/#document-resource-object-relationships Document Resource Object Relationship}
356
+ # relationships
357
+ # definition:
358
+ # JSON Object
359
+ #
360
+ # patternProperties:
361
+ # ^\\w[-\\w_]*$"
362
+ #
363
+ # properties:
364
+ # data : relationshipsData
365
+ # links
366
+ # meta
367
+ #
368
+ # description:
369
+ #
370
+ # Members of the relationships object ("relationships") represent references from the
371
+ # resource object in which it's defined to other resource objects."
372
+ # structure:
373
+ # {
374
+ # links: links,
375
+ # meta: meta,
376
+ # data: relationshipsData
377
+ # }.reject! {|_,v| v.nil? }
378
+ #
379
+ # prs:
380
+ # links
381
+ # [x] https://github.com/rails-api/active_model_serializers/pull/1454
382
+ # meta
383
+ # [x] https://github.com/rails-api/active_model_serializers/pull/1454
384
+ # polymorphic
385
+ # [ ] https://github.com/rails-api/active_model_serializers/pull/1420
386
+ #
387
+ # relationshipsData
388
+ # definition:
389
+ # oneOf
390
+ # relationshipToOne
391
+ # relationshipToMany
392
+ #
393
+ # description:
394
+ # Member, whose value represents "resource linkage"
395
+ # structure:
396
+ # if has_one?
397
+ # relationshipToOne
398
+ # else
399
+ # relationshipToMany
400
+ # end
401
+ #
402
+ # definition:
403
+ # anyOf
404
+ # null
405
+ # linkage
406
+ #
407
+ # relationshipToOne
408
+ # description:
409
+ #
410
+ # References to other resource objects in a to-one ("relationship"). Relationships can be
411
+ # specified by including a member in a resource's links object.
412
+ #
413
+ # None: Describes an empty to-one relationship.
414
+ # structure:
415
+ # if has_related?
416
+ # linkage
417
+ # else
418
+ # nil
419
+ # end
420
+ #
421
+ # relationshipToMany
422
+ # definition:
423
+ # array of unique items of type 'linkage'
424
+ #
425
+ # description:
426
+ # An array of objects each containing "type" and "id" members for to-many relationships
427
+ # structure:
428
+ # [
429
+ # linkage,
430
+ # linkage
431
+ # ]
432
+ # prs:
433
+ # polymorphic
434
+ # [ ] https://github.com/rails-api/active_model_serializers/pull/1282
435
+ #
436
+ # linkage
437
+ # definition:
438
+ # type (required) : String
439
+ # id (required) : String
440
+ # meta
441
+ #
442
+ # description:
443
+ # The "type" and "id" to non-empty members.
444
+ # structure:
445
+ # {
446
+ # type: 'required-type',
447
+ # id: 'required-id',
448
+ # meta: meta
449
+ # }.reject! {|_,v| v.nil? }
450
+ def relationships_for(serializer, requested_associations, include_slice)
451
+ include_directive = JSONAPI::IncludeDirective.new(
452
+ requested_associations,
453
+ allow_wildcard: true
454
+ )
455
+ serializer.associations(include_directive, include_slice).each_with_object({}) do |association, hash|
456
+ hash[association.key] = Relationship.new(serializer, instance_options, association).as_json
457
+ end
458
+ end
459
+
460
+ # {http://jsonapi.org/format/#document-links Document Links}
461
+ # links
462
+ # definition:
463
+ # JSON Object
464
+ #
465
+ # properties:
466
+ # self : URI
467
+ # related : link
468
+ #
469
+ # description:
470
+ # A resource object **MAY** contain references to other resource objects ("relationships").
471
+ # Relationships may be to-one or to-many. Relationships can be specified by including a member
472
+ # in a resource's links object.
473
+ #
474
+ # A `self` member’s value is a URL for the relationship itself (a "relationship URL"). This
475
+ # URL allows the client to directly manipulate the relationship. For example, it would allow
476
+ # a client to remove an `author` from an `article` without deleting the people resource
477
+ # itself.
478
+ # structure:
479
+ # {
480
+ # self: 'http://example.com/etc',
481
+ # related: link
482
+ # }.reject! {|_,v| v.nil? }
483
+ def links_for(serializer)
484
+ serializer._links.each_with_object({}) do |(name, value), hash|
485
+ next if value.excluded?(serializer)
486
+ result = Link.new(serializer, value.block).as_json
487
+ hash[name] = result if result
488
+ end
489
+ end
490
+
491
+ # {http://jsonapi.org/format/#fetching-pagination Pagination Links}
492
+ # pagination
493
+ # definition:
494
+ # first : pageObject
495
+ # last : pageObject
496
+ # prev : pageObject
497
+ # next : pageObject
498
+ # structure:
499
+ # {
500
+ # first: pageObject,
501
+ # last: pageObject,
502
+ # prev: pageObject,
503
+ # next: pageObject
504
+ # }
505
+ #
506
+ # pageObject
507
+ # definition:
508
+ # oneOf
509
+ # URI
510
+ # null
511
+ #
512
+ # description:
513
+ # The <x> page of data
514
+ # structure:
515
+ # if has_page?
516
+ # 'http://example.com/some-page?page[number][x]'
517
+ # else
518
+ # nil
519
+ # end
520
+ # prs:
521
+ # https://github.com/rails-api/active_model_serializers/pull/1041
522
+ def pagination_links_for(serializer)
523
+ PaginationLinks.new(serializer.object, instance_options).as_json
524
+ end
525
+
526
+ # {http://jsonapi.org/format/#document-meta Docment Meta}
527
+ def meta_for(serializer)
528
+ Meta.new(serializer).as_json
529
+ end
530
+ end
531
+ end
532
+ end
533
+ # rubocop:enable Style/AsciiComments