agi_active_model_serializers 0.10.7

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 (220) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE.md +29 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
  4. data/.gitignore +35 -0
  5. data/.rubocop.yml +102 -0
  6. data/.simplecov +110 -0
  7. data/.travis.yml +51 -0
  8. data/CHANGELOG.md +612 -0
  9. data/CODE_OF_CONDUCT.md +74 -0
  10. data/CONTRIBUTING.md +105 -0
  11. data/Gemfile +56 -0
  12. data/MIT-LICENSE +22 -0
  13. data/README.md +307 -0
  14. data/Rakefile +103 -0
  15. data/active_model_serializers.gemspec +63 -0
  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/README.md +41 -0
  21. data/docs/STYLE.md +58 -0
  22. data/docs/general/adapters.md +247 -0
  23. data/docs/general/caching.md +58 -0
  24. data/docs/general/configuration_options.md +169 -0
  25. data/docs/general/deserialization.md +100 -0
  26. data/docs/general/fields.md +31 -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 +279 -0
  32. data/docs/general/serializers.md +461 -0
  33. data/docs/how-open-source-maintained.jpg +0 -0
  34. data/docs/howto/add_pagination_links.md +138 -0
  35. data/docs/howto/add_relationship_links.md +137 -0
  36. data/docs/howto/add_root_key.md +55 -0
  37. data/docs/howto/grape_integration.md +42 -0
  38. data/docs/howto/outside_controller_use.md +65 -0
  39. data/docs/howto/passing_arbitrary_options.md +27 -0
  40. data/docs/howto/serialize_poro.md +32 -0
  41. data/docs/howto/test.md +154 -0
  42. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  43. data/docs/integrations/ember-and-json-api.md +144 -0
  44. data/docs/integrations/grape.md +19 -0
  45. data/docs/jsonapi/errors.md +56 -0
  46. data/docs/jsonapi/schema.md +151 -0
  47. data/docs/jsonapi/schema/schema.json +366 -0
  48. data/docs/rfcs/0000-namespace.md +106 -0
  49. data/docs/rfcs/template.md +15 -0
  50. data/lib/action_controller/serialization.rb +66 -0
  51. data/lib/active_model/serializable_resource.rb +11 -0
  52. data/lib/active_model/serializer.rb +231 -0
  53. data/lib/active_model/serializer/adapter.rb +24 -0
  54. data/lib/active_model/serializer/adapter/attributes.rb +15 -0
  55. data/lib/active_model/serializer/adapter/base.rb +18 -0
  56. data/lib/active_model/serializer/adapter/json.rb +15 -0
  57. data/lib/active_model/serializer/adapter/json_api.rb +15 -0
  58. data/lib/active_model/serializer/adapter/null.rb +15 -0
  59. data/lib/active_model/serializer/array_serializer.rb +12 -0
  60. data/lib/active_model/serializer/association.rb +34 -0
  61. data/lib/active_model/serializer/attribute.rb +25 -0
  62. data/lib/active_model/serializer/belongs_to_reflection.rb +7 -0
  63. data/lib/active_model/serializer/collection_reflection.rb +7 -0
  64. data/lib/active_model/serializer/collection_serializer.rb +87 -0
  65. data/lib/active_model/serializer/concerns/associations.rb +102 -0
  66. data/lib/active_model/serializer/concerns/attributes.rb +82 -0
  67. data/lib/active_model/serializer/concerns/caching.rb +292 -0
  68. data/lib/active_model/serializer/concerns/configuration.rb +59 -0
  69. data/lib/active_model/serializer/concerns/links.rb +35 -0
  70. data/lib/active_model/serializer/concerns/meta.rb +29 -0
  71. data/lib/active_model/serializer/concerns/type.rb +25 -0
  72. data/lib/active_model/serializer/error_serializer.rb +14 -0
  73. data/lib/active_model/serializer/errors_serializer.rb +32 -0
  74. data/lib/active_model/serializer/field.rb +90 -0
  75. data/lib/active_model/serializer/fieldset.rb +31 -0
  76. data/lib/active_model/serializer/has_many_reflection.rb +7 -0
  77. data/lib/active_model/serializer/has_one_reflection.rb +7 -0
  78. data/lib/active_model/serializer/lint.rb +150 -0
  79. data/lib/active_model/serializer/null.rb +17 -0
  80. data/lib/active_model/serializer/reflection.rb +163 -0
  81. data/lib/active_model/serializer/singular_reflection.rb +7 -0
  82. data/lib/active_model/serializer/version.rb +5 -0
  83. data/lib/active_model_serializers.rb +53 -0
  84. data/lib/active_model_serializers/adapter.rb +98 -0
  85. data/lib/active_model_serializers/adapter/attributes.rb +13 -0
  86. data/lib/active_model_serializers/adapter/base.rb +83 -0
  87. data/lib/active_model_serializers/adapter/json.rb +21 -0
  88. data/lib/active_model_serializers/adapter/json_api.rb +517 -0
  89. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +213 -0
  90. data/lib/active_model_serializers/adapter/json_api/error.rb +96 -0
  91. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +49 -0
  92. data/lib/active_model_serializers/adapter/json_api/link.rb +83 -0
  93. data/lib/active_model_serializers/adapter/json_api/meta.rb +37 -0
  94. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +69 -0
  95. data/lib/active_model_serializers/adapter/json_api/relationship.rb +63 -0
  96. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +51 -0
  97. data/lib/active_model_serializers/adapter/null.rb +9 -0
  98. data/lib/active_model_serializers/callbacks.rb +55 -0
  99. data/lib/active_model_serializers/deprecate.rb +54 -0
  100. data/lib/active_model_serializers/deserialization.rb +15 -0
  101. data/lib/active_model_serializers/json_pointer.rb +14 -0
  102. data/lib/active_model_serializers/logging.rb +122 -0
  103. data/lib/active_model_serializers/lookup_chain.rb +80 -0
  104. data/lib/active_model_serializers/model.rb +71 -0
  105. data/lib/active_model_serializers/railtie.rb +48 -0
  106. data/lib/active_model_serializers/register_jsonapi_renderer.rb +78 -0
  107. data/lib/active_model_serializers/serializable_resource.rb +82 -0
  108. data/lib/active_model_serializers/serialization_context.rb +39 -0
  109. data/lib/active_model_serializers/test.rb +7 -0
  110. data/lib/active_model_serializers/test/schema.rb +138 -0
  111. data/lib/active_model_serializers/test/serializer.rb +125 -0
  112. data/lib/generators/rails/USAGE +6 -0
  113. data/lib/generators/rails/resource_override.rb +10 -0
  114. data/lib/generators/rails/serializer_generator.rb +36 -0
  115. data/lib/generators/rails/templates/serializer.rb.erb +15 -0
  116. data/lib/grape/active_model_serializers.rb +16 -0
  117. data/lib/grape/formatters/active_model_serializers.rb +32 -0
  118. data/lib/grape/helpers/active_model_serializers.rb +17 -0
  119. data/test/action_controller/adapter_selector_test.rb +53 -0
  120. data/test/action_controller/explicit_serializer_test.rb +135 -0
  121. data/test/action_controller/json/include_test.rb +246 -0
  122. data/test/action_controller/json_api/deserialization_test.rb +112 -0
  123. data/test/action_controller/json_api/errors_test.rb +40 -0
  124. data/test/action_controller/json_api/fields_test.rb +66 -0
  125. data/test/action_controller/json_api/linked_test.rb +202 -0
  126. data/test/action_controller/json_api/pagination_test.rb +116 -0
  127. data/test/action_controller/json_api/transform_test.rb +189 -0
  128. data/test/action_controller/lookup_proc_test.rb +49 -0
  129. data/test/action_controller/namespace_lookup_test.rb +232 -0
  130. data/test/action_controller/serialization_scope_name_test.rb +229 -0
  131. data/test/action_controller/serialization_test.rb +472 -0
  132. data/test/active_model_serializers/adapter_for_test.rb +208 -0
  133. data/test/active_model_serializers/json_pointer_test.rb +22 -0
  134. data/test/active_model_serializers/logging_test.rb +77 -0
  135. data/test/active_model_serializers/model_test.rb +69 -0
  136. data/test/active_model_serializers/railtie_test_isolated.rb +63 -0
  137. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
  138. data/test/active_model_serializers/serialization_context_test_isolated.rb +71 -0
  139. data/test/active_model_serializers/test/schema_test.rb +131 -0
  140. data/test/active_model_serializers/test/serializer_test.rb +62 -0
  141. data/test/active_record_test.rb +9 -0
  142. data/test/adapter/attributes_test.rb +43 -0
  143. data/test/adapter/deprecation_test.rb +100 -0
  144. data/test/adapter/json/belongs_to_test.rb +45 -0
  145. data/test/adapter/json/collection_test.rb +104 -0
  146. data/test/adapter/json/has_many_test.rb +45 -0
  147. data/test/adapter/json/transform_test.rb +93 -0
  148. data/test/adapter/json_api/belongs_to_test.rb +155 -0
  149. data/test/adapter/json_api/collection_test.rb +96 -0
  150. data/test/adapter/json_api/errors_test.rb +76 -0
  151. data/test/adapter/json_api/fields_test.rb +96 -0
  152. data/test/adapter/json_api/has_many_embed_ids_test.rb +43 -0
  153. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +96 -0
  154. data/test/adapter/json_api/has_many_test.rb +165 -0
  155. data/test/adapter/json_api/has_one_test.rb +80 -0
  156. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +168 -0
  157. data/test/adapter/json_api/json_api_test.rb +33 -0
  158. data/test/adapter/json_api/linked_test.rb +413 -0
  159. data/test/adapter/json_api/links_test.rb +95 -0
  160. data/test/adapter/json_api/pagination_links_test.rb +193 -0
  161. data/test/adapter/json_api/parse_test.rb +137 -0
  162. data/test/adapter/json_api/relationship_test.rb +397 -0
  163. data/test/adapter/json_api/resource_identifier_test.rb +110 -0
  164. data/test/adapter/json_api/resource_meta_test.rb +100 -0
  165. data/test/adapter/json_api/toplevel_jsonapi_test.rb +82 -0
  166. data/test/adapter/json_api/transform_test.rb +512 -0
  167. data/test/adapter/json_api/type_test.rb +61 -0
  168. data/test/adapter/json_test.rb +46 -0
  169. data/test/adapter/null_test.rb +22 -0
  170. data/test/adapter/polymorphic_test.rb +171 -0
  171. data/test/adapter_test.rb +67 -0
  172. data/test/array_serializer_test.rb +22 -0
  173. data/test/benchmark/app.rb +65 -0
  174. data/test/benchmark/benchmarking_support.rb +67 -0
  175. data/test/benchmark/bm_active_record.rb +81 -0
  176. data/test/benchmark/bm_adapter.rb +38 -0
  177. data/test/benchmark/bm_caching.rb +119 -0
  178. data/test/benchmark/bm_lookup_chain.rb +83 -0
  179. data/test/benchmark/bm_transform.rb +45 -0
  180. data/test/benchmark/config.ru +3 -0
  181. data/test/benchmark/controllers.rb +83 -0
  182. data/test/benchmark/fixtures.rb +219 -0
  183. data/test/cache_test.rb +595 -0
  184. data/test/collection_serializer_test.rb +123 -0
  185. data/test/fixtures/active_record.rb +113 -0
  186. data/test/fixtures/poro.rb +232 -0
  187. data/test/generators/scaffold_controller_generator_test.rb +24 -0
  188. data/test/generators/serializer_generator_test.rb +74 -0
  189. data/test/grape_test.rb +178 -0
  190. data/test/lint_test.rb +49 -0
  191. data/test/logger_test.rb +20 -0
  192. data/test/poro_test.rb +9 -0
  193. data/test/serializable_resource_test.rb +79 -0
  194. data/test/serializers/association_macros_test.rb +37 -0
  195. data/test/serializers/associations_test.rb +383 -0
  196. data/test/serializers/attribute_test.rb +153 -0
  197. data/test/serializers/attributes_test.rb +52 -0
  198. data/test/serializers/caching_configuration_test_isolated.rb +170 -0
  199. data/test/serializers/configuration_test.rb +32 -0
  200. data/test/serializers/fieldset_test.rb +14 -0
  201. data/test/serializers/meta_test.rb +202 -0
  202. data/test/serializers/options_test.rb +32 -0
  203. data/test/serializers/read_attribute_for_serialization_test.rb +79 -0
  204. data/test/serializers/root_test.rb +21 -0
  205. data/test/serializers/serialization_test.rb +55 -0
  206. data/test/serializers/serializer_for_test.rb +136 -0
  207. data/test/serializers/serializer_for_with_namespace_test.rb +88 -0
  208. data/test/support/custom_schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  209. data/test/support/isolated_unit.rb +82 -0
  210. data/test/support/rails5_shims.rb +53 -0
  211. data/test/support/rails_app.rb +36 -0
  212. data/test/support/schemas/active_model_serializers/test/schema_test/my/index.json +6 -0
  213. data/test/support/schemas/active_model_serializers/test/schema_test/my/show.json +6 -0
  214. data/test/support/schemas/custom/show.json +7 -0
  215. data/test/support/schemas/hyper_schema.json +93 -0
  216. data/test/support/schemas/render_using_json_api.json +43 -0
  217. data/test/support/schemas/simple_json_pointers.json +10 -0
  218. data/test/support/serialization_testing.rb +71 -0
  219. data/test/test_helper.rb +58 -0
  220. metadata +602 -0
@@ -0,0 +1,80 @@
1
+ module ActiveModelSerializers
2
+ module LookupChain
3
+ # Standard appending of Serializer to the resource name.
4
+ #
5
+ # Example:
6
+ # Author => AuthorSerializer
7
+ BY_RESOURCE = lambda do |resource_class, _serializer_class, _namespace|
8
+ serializer_from(resource_class)
9
+ end
10
+
11
+ # Uses the namespace of the resource to find the serializer
12
+ #
13
+ # Example:
14
+ # British::Author => British::AuthorSerializer
15
+ BY_RESOURCE_NAMESPACE = lambda do |resource_class, _serializer_class, _namespace|
16
+ resource_namespace = namespace_for(resource_class)
17
+ serializer_name = serializer_from(resource_class)
18
+
19
+ "#{resource_namespace}::#{serializer_name}"
20
+ end
21
+
22
+ # Uses the controller namespace of the resource to find the serializer
23
+ #
24
+ # Example:
25
+ # Api::V3::AuthorsController => Api::V3::AuthorSerializer
26
+ BY_NAMESPACE = lambda do |resource_class, _serializer_class, namespace|
27
+ resource_name = resource_class_name(resource_class)
28
+ namespace ? "#{namespace}::#{resource_name}Serializer" : nil
29
+ end
30
+
31
+ # Allows for serializers to be defined in parent serializers
32
+ # - useful if a relationship only needs a different set of attributes
33
+ # than if it were rendered independently.
34
+ #
35
+ # Example:
36
+ # class BlogSerializer < ActiveModel::Serializer
37
+ # class AuthorSerialier < ActiveModel::Serializer
38
+ # ...
39
+ # end
40
+ #
41
+ # belongs_to :author
42
+ # ...
43
+ # end
44
+ #
45
+ # The belongs_to relationship would be rendered with
46
+ # BlogSerializer::AuthorSerialier
47
+ BY_PARENT_SERIALIZER = lambda do |resource_class, serializer_class, _namespace|
48
+ return if serializer_class == ActiveModel::Serializer
49
+
50
+ serializer_name = serializer_from(resource_class)
51
+ "#{serializer_class}::#{serializer_name}"
52
+ end
53
+
54
+ DEFAULT = [
55
+ BY_PARENT_SERIALIZER,
56
+ BY_NAMESPACE,
57
+ BY_RESOURCE_NAMESPACE,
58
+ BY_RESOURCE
59
+ ].freeze
60
+
61
+ module_function
62
+
63
+ def namespace_for(klass)
64
+ klass.name.deconstantize
65
+ end
66
+
67
+ def resource_class_name(klass)
68
+ klass.name.demodulize
69
+ end
70
+
71
+ def serializer_from_resource_name(name)
72
+ "#{name}Serializer"
73
+ end
74
+
75
+ def serializer_from(klass)
76
+ name = resource_class_name(klass)
77
+ serializer_from_resource_name(name)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,71 @@
1
+ # ActiveModelSerializers::Model is a convenient
2
+ # serializable class to inherit from when making
3
+ # serializable non-activerecord objects.
4
+ module ActiveModelSerializers
5
+ class Model
6
+ include ActiveModel::Serializers::JSON
7
+ include ActiveModel::Model
8
+
9
+ class_attribute :attribute_names
10
+ # Initialize +attribute_names+ for all subclasses. The array is usually
11
+ # mutated in the +attributes+ method, but can be set directly, as well.
12
+ self.attribute_names = []
13
+
14
+ def self.attributes(*names)
15
+ self.attribute_names |= names.map(&:to_sym)
16
+ # Silence redefinition of methods warnings
17
+ ActiveModelSerializers.silence_warnings do
18
+ attr_accessor(*names)
19
+ end
20
+ end
21
+
22
+ attr_reader :errors
23
+ # NOTE that +updated_at+ isn't included in +attribute_names+,
24
+ # which means it won't show up in +attributes+ unless a subclass has
25
+ # either <tt>attributes :updated_at</tt> which will redefine the methods
26
+ # or <tt>attribute_names << :updated_at</tt>.
27
+ attr_writer :updated_at
28
+ # NOTE that +id+ will always be in +attributes+.
29
+ attributes :id
30
+
31
+ def initialize(attributes = {})
32
+ @errors = ActiveModel::Errors.new(self)
33
+ super
34
+ end
35
+
36
+ # The the fields in +attribute_names+ determines the returned hash.
37
+ # +attributes+ are returned frozen to prevent any expectations that mutation affects
38
+ # the actual values in the model.
39
+ def attributes
40
+ attribute_names.each_with_object({}) do |attribute_name, result|
41
+ result[attribute_name] = public_send(attribute_name).freeze
42
+ end.with_indifferent_access.freeze
43
+ end
44
+
45
+ # To customize model behavior, this method must be redefined. However,
46
+ # there are other ways of setting the +cache_key+ a serializer uses.
47
+ def cache_key
48
+ ActiveSupport::Cache.expand_cache_key([
49
+ self.class.model_name.name.downcase,
50
+ "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}"
51
+ ].compact)
52
+ end
53
+
54
+ # When no set, defaults to the time the file was modified.
55
+ # See NOTE by attr_writer :updated_at
56
+ def updated_at
57
+ defined?(@updated_at) ? @updated_at : File.mtime(__FILE__)
58
+ end
59
+
60
+ # The following methods are needed to be minimally implemented for ActiveModel::Errors
61
+ # :nocov:
62
+ def self.human_attribute_name(attr, _options = {})
63
+ attr
64
+ end
65
+
66
+ def self.lookup_ancestors
67
+ [self]
68
+ end
69
+ # :nocov:
70
+ end
71
+ end
@@ -0,0 +1,48 @@
1
+ require 'rails/railtie'
2
+ require 'action_controller'
3
+ require 'action_controller/railtie'
4
+ require 'action_controller/serialization'
5
+
6
+ module ActiveModelSerializers
7
+ class Railtie < Rails::Railtie
8
+ config.to_prepare do
9
+ ActiveModel::Serializer.serializers_cache.clear
10
+ end
11
+
12
+ initializer 'active_model_serializers.action_controller' do
13
+ ActiveSupport.on_load(:action_controller) do
14
+ include(::ActionController::Serialization)
15
+ end
16
+ end
17
+
18
+ initializer 'active_model_serializers.prepare_serialization_context' do
19
+ SerializationContext.url_helpers = Rails.application.routes.url_helpers
20
+ SerializationContext.default_url_options = Rails.application.routes.default_url_options
21
+ end
22
+
23
+ # This hook is run after the action_controller railtie has set the configuration
24
+ # based on the *environment* configuration and before any config/initializers are run
25
+ # and also before eager_loading (if enabled).
26
+ initializer 'active_model_serializers.set_configs', after: 'action_controller.set_configs' do
27
+ ActiveModelSerializers.logger = Rails.configuration.action_controller.logger
28
+ ActiveModelSerializers.config.perform_caching = Rails.configuration.action_controller.perform_caching
29
+ # We want this hook to run after the config has been set, even if ActionController has already loaded.
30
+ ActiveSupport.on_load(:action_controller) do
31
+ ActiveModelSerializers.config.cache_store = ActionController::Base.cache_store
32
+ end
33
+ end
34
+
35
+ # :nocov:
36
+ generators do |app|
37
+ Rails::Generators.configure!(app.config.generators)
38
+ Rails::Generators.hidden_namespaces.uniq!
39
+ require 'generators/rails/resource_override'
40
+ end
41
+ # :nocov:
42
+
43
+ if Rails.env.test?
44
+ ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Schema)
45
+ ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Serializer)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,78 @@
1
+ # Based on discussion in https://github.com/rails/rails/pull/23712#issuecomment-184977238,
2
+ # the JSON API media type will have its own format/renderer.
3
+ #
4
+ # > We recommend the media type be registered on its own as jsonapi
5
+ # when a jsonapi Renderer and deserializer (Http::Parameters::DEFAULT_PARSERS) are added.
6
+ #
7
+ # Usage:
8
+ #
9
+ # ActiveSupport.on_load(:action_controller) do
10
+ # require 'active_model_serializers/register_jsonapi_renderer'
11
+ # end
12
+ #
13
+ # And then in controllers, use `render jsonapi: model` rather than `render json: model, adapter: :json_api`.
14
+ #
15
+ # For example, in a controller action, we can:
16
+ # respond_to do |format|
17
+ # format.jsonapi { render jsonapi: model }
18
+ # end
19
+ #
20
+ # or
21
+ #
22
+ # render jsonapi: model
23
+ #
24
+ # No wrapper format needed as it does not apply (i.e. no `wrap_parameters format: [jsonapi]`)
25
+ module ActiveModelSerializers
26
+ module Jsonapi
27
+ MEDIA_TYPE = 'application/vnd.api+json'.freeze
28
+ HEADERS = {
29
+ response: { 'CONTENT_TYPE'.freeze => MEDIA_TYPE },
30
+ request: { 'ACCEPT'.freeze => MEDIA_TYPE }
31
+ }.freeze
32
+
33
+ def self.install
34
+ # actionpack/lib/action_dispatch/http/mime_types.rb
35
+ Mime::Type.register MEDIA_TYPE, :jsonapi
36
+
37
+ if Rails::VERSION::MAJOR >= 5
38
+ ActionDispatch::Request.parameter_parsers[:jsonapi] = parser
39
+ else
40
+ ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime[:jsonapi]] = parser
41
+ end
42
+
43
+ # ref https://github.com/rails/rails/pull/21496
44
+ ActionController::Renderers.add :jsonapi do |json, options|
45
+ json = serialize_jsonapi(json, options).to_json(options) unless json.is_a?(String)
46
+ self.content_type ||= Mime[:jsonapi]
47
+ self.response_body = json
48
+ end
49
+ end
50
+
51
+ # Proposal: should actually deserialize the JSON API params
52
+ # to the hash format expected by `ActiveModel::Serializers::JSON`
53
+ # actionpack/lib/action_dispatch/http/parameters.rb
54
+ def self.parser
55
+ lambda do |body|
56
+ data = JSON.parse(body)
57
+ data = { _json: data } unless data.is_a?(Hash)
58
+ data.with_indifferent_access
59
+ end
60
+ end
61
+
62
+ module ControllerSupport
63
+ def serialize_jsonapi(json, options)
64
+ options[:adapter] = :json_api
65
+ options.fetch(:serialization_context) do
66
+ options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request)
67
+ end
68
+ get_serializer(json, options)
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ ActiveModelSerializers::Jsonapi.install
75
+
76
+ ActiveSupport.on_load(:action_controller) do
77
+ include ActiveModelSerializers::Jsonapi::ControllerSupport
78
+ end
@@ -0,0 +1,82 @@
1
+ require 'set'
2
+
3
+ module ActiveModelSerializers
4
+ class SerializableResource
5
+ ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links, :serialization_context, :key_transform])
6
+ include ActiveModelSerializers::Logging
7
+
8
+ delegate :serializable_hash, :as_json, :to_json, to: :adapter
9
+ notify :serializable_hash, :render
10
+ notify :as_json, :render
11
+ notify :to_json, :render
12
+
13
+ # Primary interface to composing a resource with a serializer and adapter.
14
+ # @return the serializable_resource, ready for #as_json/#to_json/#serializable_hash.
15
+ def initialize(resource, options = {})
16
+ @resource = resource
17
+ @adapter_opts, @serializer_opts =
18
+ options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
19
+ end
20
+
21
+ def serialization_scope=(scope)
22
+ serializer_opts[:scope] = scope
23
+ end
24
+
25
+ def serialization_scope
26
+ serializer_opts[:scope]
27
+ end
28
+
29
+ def serialization_scope_name=(scope_name)
30
+ serializer_opts[:scope_name] = scope_name
31
+ end
32
+
33
+ # NOTE: if no adapter is available, returns the resource itself. (i.e. adapter is a no-op)
34
+ def adapter
35
+ @adapter ||= find_adapter
36
+ end
37
+ alias adapter_instance adapter
38
+
39
+ def find_adapter
40
+ return resource unless serializer?
41
+ adapter = catch :no_serializer do
42
+ ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts)
43
+ end
44
+ adapter || resource
45
+ end
46
+
47
+ def serializer_instance
48
+ @serializer_instance ||= serializer.new(resource, serializer_opts)
49
+ end
50
+
51
+ # Get serializer either explicitly :serializer or implicitly from resource
52
+ # Remove :serializer key from serializer_opts
53
+ # Remove :each_serializer if present and set as :serializer key
54
+ def serializer
55
+ @serializer ||=
56
+ begin
57
+ @serializer = serializer_opts.delete(:serializer)
58
+ @serializer ||= ActiveModel::Serializer.serializer_for(resource, serializer_opts)
59
+
60
+ if serializer_opts.key?(:each_serializer)
61
+ serializer_opts[:serializer] = serializer_opts.delete(:each_serializer)
62
+ end
63
+ @serializer
64
+ end
65
+ end
66
+ alias serializer_class serializer
67
+
68
+ # True when no explicit adapter given, or explicit appear is truthy (non-nil)
69
+ # False when explicit adapter is falsy (nil or false)
70
+ def use_adapter?
71
+ !(adapter_opts.key?(:adapter) && !adapter_opts[:adapter])
72
+ end
73
+
74
+ def serializer?
75
+ use_adapter? && !serializer.nil?
76
+ end
77
+
78
+ protected
79
+
80
+ attr_reader :resource, :adapter_opts, :serializer_opts
81
+ end
82
+ end
@@ -0,0 +1,39 @@
1
+ require 'active_support/core_ext/array/extract_options'
2
+ module ActiveModelSerializers
3
+ class SerializationContext
4
+ class << self
5
+ attr_writer :url_helpers, :default_url_options
6
+ def url_helpers
7
+ @url_helpers ||= Module.new
8
+ end
9
+
10
+ def default_url_options
11
+ @default_url_options ||= {}
12
+ end
13
+ end
14
+ module UrlHelpers
15
+ def self.included(base)
16
+ base.send(:include, SerializationContext.url_helpers)
17
+ end
18
+
19
+ def default_url_options
20
+ SerializationContext.default_url_options
21
+ end
22
+ end
23
+
24
+ attr_reader :request_url, :query_parameters, :key_transform
25
+
26
+ def initialize(*args)
27
+ options = args.extract_options!
28
+ if args.size == 1
29
+ request = args.pop
30
+ options[:request_url] = request.original_url[/\A[^?]+/]
31
+ options[:query_parameters] = request.query_parameters
32
+ end
33
+ @request_url = options.delete(:request_url)
34
+ @query_parameters = options.delete(:query_parameters)
35
+ @url_helpers = options.delete(:url_helpers) || self.class.url_helpers
36
+ @default_url_options = options.delete(:default_url_options) || self.class.default_url_options
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveModelSerializers
2
+ module Test
3
+ extend ActiveSupport::Autoload
4
+ autoload :Serializer
5
+ autoload :Schema
6
+ end
7
+ end
@@ -0,0 +1,138 @@
1
+ module ActiveModelSerializers
2
+ module Test
3
+ module Schema
4
+ # A Minitest Assertion that test the response is valid against a schema.
5
+ # @param schema_path [String] a custom schema path
6
+ # @param message [String] a custom error message
7
+ # @return [Boolean] true when the response is valid
8
+ # @return [Minitest::Assertion] when the response is invalid
9
+ # @example
10
+ # get :index
11
+ # assert_response_schema
12
+ def assert_response_schema(schema_path = nil, message = nil)
13
+ matcher = AssertResponseSchema.new(schema_path, request, response, message)
14
+ assert(matcher.call, matcher.message)
15
+ end
16
+
17
+ def assert_request_schema(schema_path = nil, message = nil)
18
+ matcher = AssertRequestSchema.new(schema_path, request, response, message)
19
+ assert(matcher.call, matcher.message)
20
+ end
21
+
22
+ # May be renamed
23
+ def assert_request_response_schema(schema_path = nil, message = nil)
24
+ assert_request_schema(schema_path, message)
25
+ assert_response_schema(schema_path, message)
26
+ end
27
+
28
+ def assert_schema(payload, schema_path = nil, message = nil)
29
+ matcher = AssertSchema.new(schema_path, request, response, message, payload)
30
+ assert(matcher.call, matcher.message)
31
+ end
32
+
33
+ MissingSchema = Class.new(Minitest::Assertion)
34
+ InvalidSchemaError = Class.new(Minitest::Assertion)
35
+
36
+ class AssertSchema
37
+ attr_reader :schema_path, :request, :response, :message, :payload
38
+
39
+ # Interface may change.
40
+ def initialize(schema_path, request, response, message, payload = nil)
41
+ require_json_schema!
42
+ @request = request
43
+ @response = response
44
+ @payload = payload
45
+ @schema_path = schema_path || schema_path_default
46
+ @message = message
47
+ @document_store = JsonSchema::DocumentStore.new
48
+ add_schema_to_document_store
49
+ end
50
+
51
+ def call
52
+ json_schema.expand_references!(store: document_store)
53
+ status, errors = json_schema.validate(response_body)
54
+ @message = [message, errors.map(&:to_s).to_sentence].compact.join(': ')
55
+ status
56
+ end
57
+
58
+ protected
59
+
60
+ attr_reader :document_store
61
+
62
+ def controller_path
63
+ request.filtered_parameters[:controller]
64
+ end
65
+
66
+ def action
67
+ request.filtered_parameters[:action]
68
+ end
69
+
70
+ def schema_directory
71
+ ActiveModelSerializers.config.schema_path
72
+ end
73
+
74
+ def schema_full_path
75
+ "#{schema_directory}/#{schema_path}"
76
+ end
77
+
78
+ def schema_path_default
79
+ "#{controller_path}/#{action}.json"
80
+ end
81
+
82
+ def schema_data
83
+ load_json_file(schema_full_path)
84
+ end
85
+
86
+ def response_body
87
+ load_json(response.body)
88
+ end
89
+
90
+ def request_params
91
+ request.env['action_dispatch.request.request_parameters']
92
+ end
93
+
94
+ def json_schema
95
+ @json_schema ||= JsonSchema.parse!(schema_data)
96
+ end
97
+
98
+ def add_schema_to_document_store
99
+ Dir.glob("#{schema_directory}/**/*.json").each do |path|
100
+ schema_data = load_json_file(path)
101
+ extra_schema = JsonSchema.parse!(schema_data)
102
+ document_store.add_schema(extra_schema)
103
+ end
104
+ end
105
+
106
+ def load_json(json)
107
+ JSON.parse(json)
108
+ rescue JSON::ParserError => ex
109
+ raise InvalidSchemaError, ex.message
110
+ end
111
+
112
+ def load_json_file(path)
113
+ load_json(File.read(path))
114
+ rescue Errno::ENOENT
115
+ raise MissingSchema, "No Schema file at #{schema_full_path}"
116
+ end
117
+
118
+ def require_json_schema!
119
+ require 'json_schema'
120
+ rescue LoadError
121
+ raise LoadError, "You don't have json_schema installed in your application. Please add it to your Gemfile and run bundle install"
122
+ end
123
+ end
124
+ class AssertResponseSchema < AssertSchema
125
+ def initialize(*)
126
+ super
127
+ @payload = response_body
128
+ end
129
+ end
130
+ class AssertRequestSchema < AssertSchema
131
+ def initialize(*)
132
+ super
133
+ @payload = request_params
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end