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,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,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.with_indifferent_access[:controller]
64
+ end
65
+
66
+ def action
67
+ request.filtered_parameters.with_indifferent_access[: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
@@ -0,0 +1,125 @@
1
+ require 'set'
2
+ module ActiveModelSerializers
3
+ module Test
4
+ module Serializer
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ setup :setup_serialization_subscriptions
9
+ teardown :teardown_serialization_subscriptions
10
+ end
11
+
12
+ # Asserts that the request was rendered with the appropriate serializers.
13
+ #
14
+ # # assert that the "PostSerializer" serializer was rendered
15
+ # assert_serializer "PostSerializer"
16
+ #
17
+ # # return a custom error message
18
+ # assert_serializer "PostSerializer", "PostSerializer not rendered"
19
+ #
20
+ # # assert that the instance of PostSerializer was rendered
21
+ # assert_serializer PostSerializer
22
+ #
23
+ # # assert that the "PostSerializer" serializer was rendered
24
+ # assert_serializer :post_serializer
25
+ #
26
+ # # assert that the rendered serializer starts with "Post"
27
+ # assert_serializer %r{\APost.+\Z}
28
+ #
29
+ # # assert that no serializer was rendered
30
+ # assert_serializer nil
31
+ #
32
+ def assert_serializer(expectation, message = nil)
33
+ @assert_serializer.expectation = expectation
34
+ @assert_serializer.message = message
35
+ @assert_serializer.response = response
36
+ assert(@assert_serializer.matches?, @assert_serializer.message)
37
+ end
38
+
39
+ class AssertSerializer
40
+ attr_reader :serializers, :message
41
+ attr_accessor :response, :expectation
42
+
43
+ def initialize
44
+ @serializers = Set.new
45
+ @_subscribers = []
46
+ end
47
+
48
+ def message=(message)
49
+ @message = message || "expecting <#{expectation.inspect}> but rendering with <#{serializers.to_a}>"
50
+ end
51
+
52
+ def matches?
53
+ # Force body to be read in case the template is being streamed.
54
+ response.body
55
+
56
+ case expectation
57
+ when a_serializer? then matches_class?
58
+ when Symbol then matches_symbol?
59
+ when String then matches_string?
60
+ when Regexp then matches_regexp?
61
+ when NilClass then matches_nil?
62
+ else fail ArgumentError, 'assert_serializer only accepts a String, Symbol, Regexp, ActiveModel::Serializer, or nil'
63
+ end
64
+ end
65
+
66
+ def subscribe
67
+ @_subscribers << ActiveSupport::Notifications.subscribe(event_name) do |_name, _start, _finish, _id, payload|
68
+ serializer = payload[:serializer].name
69
+ serializers << serializer
70
+ end
71
+ end
72
+
73
+ def unsubscribe
74
+ @_subscribers.each do |subscriber|
75
+ ActiveSupport::Notifications.unsubscribe(subscriber)
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def matches_class?
82
+ serializers.include?(expectation.name)
83
+ end
84
+
85
+ def matches_symbol?
86
+ camelize_expectation = expectation.to_s.camelize
87
+ serializers.include?(camelize_expectation)
88
+ end
89
+
90
+ def matches_string?
91
+ !expectation.empty? && serializers.include?(expectation)
92
+ end
93
+
94
+ def matches_regexp?
95
+ serializers.any? do |serializer|
96
+ serializer.match(expectation)
97
+ end
98
+ end
99
+
100
+ def matches_nil?
101
+ serializers.empty?
102
+ end
103
+
104
+ def a_serializer?
105
+ ->(exp) { exp.is_a?(Class) && exp < ActiveModel::Serializer }
106
+ end
107
+
108
+ def event_name
109
+ ::ActiveModelSerializers::Logging::RENDER_EVENT
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ def setup_serialization_subscriptions
116
+ @assert_serializer = AssertSerializer.new
117
+ @assert_serializer.subscribe
118
+ end
119
+
120
+ def teardown_serialization_subscriptions
121
+ @assert_serializer.unsubscribe
122
+ end
123
+ end
124
+ end
125
+ 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
@@ -1,95 +1,61 @@
1
- require "active_support"
2
- require "active_support/core_ext/string/inflections"
3
- require "active_support/notifications"
4
- require "active_model"
5
- require "active_model/array_serializer"
6
- require "active_model/serializer"
7
- require "active_model/serializer/associations"
8
- require "set"
9
-
10
- if defined?(Rails)
11
- module ActiveModel
12
- class Railtie < Rails::Railtie
13
- generators do |app|
14
- app ||= Rails.application # Rails 3.0.x does not yield `app`
15
-
16
- Rails::Generators.configure!(app.config.generators)
17
- Rails::Generators.hidden_namespaces.uniq!
18
- require_relative "generators/resource_override"
19
- end
20
-
21
- initializer "include_routes.active_model_serializer" do |app|
22
- ActiveSupport.on_load(:active_model_serializers) do
23
- include app.routes.url_helpers
24
- end
25
- end
1
+ require 'active_model'
2
+ require 'active_support'
3
+ require 'active_support/core_ext/object/with_options'
4
+ require 'active_support/core_ext/string/inflections'
5
+ require 'active_support/json'
6
+ module ActiveModelSerializers
7
+ extend ActiveSupport::Autoload
8
+ eager_autoload do
9
+ autoload :Model
10
+ autoload :Callbacks
11
+ autoload :SerializableResource
12
+ autoload :SerializationContext
13
+ autoload :Logging
14
+ autoload :Test
15
+ autoload :Adapter
16
+ autoload :JsonPointer
17
+ autoload :Deprecate
18
+ autoload :LookupChain
19
+ autoload :Deserialization
20
+ end
26
21
 
27
- initializer "caching.active_model_serializer" do |app|
28
- ActiveModel::Serializer.perform_caching = app.config.action_controller.perform_caching
29
- ActiveModel::ArraySerializer.perform_caching = app.config.action_controller.perform_caching
22
+ class << self; attr_accessor :logger; end
23
+ self.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
30
24
 
31
- ActiveModel::Serializer.cache = Rails.cache
32
- ActiveModel::ArraySerializer.cache = Rails.cache
33
- end
34
- end
25
+ def self.config
26
+ ActiveModel::Serializer.config
35
27
  end
36
- end
37
28
 
38
- module ActiveModel::SerializerSupport
39
- extend ActiveSupport::Concern
29
+ # The file name and line number of the caller of the caller of this method.
30
+ def self.location_of_caller
31
+ caller[1] =~ /(.*?):(\d+).*?$/i
32
+ file = Regexp.last_match(1)
33
+ lineno = Regexp.last_match(2).to_i
40
34
 
41
- module ClassMethods #:nodoc:
42
- if "".respond_to?(:safe_constantize)
43
- def active_model_serializer
44
- "#{self.name}Serializer".safe_constantize
45
- end
46
- else
47
- def active_model_serializer
48
- begin
49
- "#{self.name}Serializer".constantize
50
- rescue NameError => e
51
- raise unless e.message =~ /uninitialized constant/
52
- end
53
- end
54
- end
35
+ [file, lineno]
55
36
  end
56
37
 
57
- # Returns a model serializer for this object considering its namespace.
58
- def active_model_serializer
59
- self.class.active_model_serializer
38
+ # Memoized default include directive
39
+ # @return [JSONAPI::IncludeDirective]
40
+ def self.default_include_directive
41
+ @default_include_directive ||= JSONAPI::IncludeDirective.new(config.default_includes, allow_wildcard: true)
60
42
  end
61
43
 
62
- alias :read_attribute_for_serialization :send
63
- end
64
-
65
- module ActiveModel::ArraySerializerSupport
66
- def active_model_serializer
67
- ActiveModel::ArraySerializer
44
+ def self.silence_warnings
45
+ original_verbose = $VERBOSE
46
+ $VERBOSE = nil
47
+ yield
48
+ ensure
49
+ $VERBOSE = original_verbose
68
50
  end
69
- end
70
-
71
- Array.send(:include, ActiveModel::ArraySerializerSupport)
72
- Set.send(:include, ActiveModel::ArraySerializerSupport)
73
51
 
74
- {
75
- :active_record => 'ActiveRecord::Relation',
76
- :mongoid => 'Mongoid::Criteria'
77
- }.each do |orm, rel_class|
78
- ActiveSupport.on_load(orm) do
79
- include ActiveModel::SerializerSupport
80
- rel_class.constantize.send(:include, ActiveModel::ArraySerializerSupport)
52
+ def self.eager_load!
53
+ super
54
+ ActiveModel::Serializer.eager_load!
81
55
  end
82
- end
83
56
 
84
- begin
85
- require 'action_controller'
86
- require 'action_controller/serialization'
87
-
88
- ActiveSupport.on_load(:action_controller) do
89
- include ::ActionController::Serialization
90
- end
91
- rescue LoadError => ex
92
- # rails on installed, continuing
57
+ require 'active_model/serializer/version'
58
+ require 'active_model/serializer'
59
+ require 'active_model/serializable_resource'
60
+ require 'active_model_serializers/railtie' if defined?(::Rails::Railtie)
93
61
  end
94
-
95
- ActiveSupport.run_load_hooks(:active_model_serializers, ActiveModel::Serializer)
@@ -0,0 +1,6 @@
1
+ Description:
2
+ Generates a serializer for the given resource.
3
+
4
+ Example:
5
+ `rails generate serializer Account name created_at`
6
+
@@ -0,0 +1,10 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/rails/resource/resource_generator'
3
+
4
+ module Rails
5
+ module Generators
6
+ class ResourceGenerator
7
+ hook_for :serializer, default: true, type: :boolean
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,36 @@
1
+ module Rails
2
+ module Generators
3
+ class SerializerGenerator < NamedBase
4
+ source_root File.expand_path('../templates', __FILE__)
5
+ check_class_collision suffix: 'Serializer'
6
+
7
+ argument :attributes, type: :array, default: [], banner: 'field:type field:type'
8
+
9
+ class_option :parent, type: :string, desc: 'The parent class for the generated serializer'
10
+
11
+ def create_serializer_file
12
+ template 'serializer.rb.erb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb")
13
+ end
14
+
15
+ private
16
+
17
+ def attributes_names
18
+ [:id] + attributes.reject(&:reference?).map! { |a| a.name.to_sym }
19
+ end
20
+
21
+ def association_names
22
+ attributes.select(&:reference?).map! { |a| a.name.to_sym }
23
+ end
24
+
25
+ def parent_class_name
26
+ if options[:parent]
27
+ options[:parent]
28
+ elsif 'ApplicationSerializer'.safe_constantize
29
+ 'ApplicationSerializer'
30
+ else
31
+ 'ActiveModel::Serializer'
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,8 @@
1
+ <% module_namespacing do -%>
2
+ class <%= class_name %>Serializer < <%= parent_class_name %>
3
+ attributes <%= attributes_names.map(&:inspect).join(", ") %>
4
+ <% association_names.each do |attribute| -%>
5
+ has_one :<%= attribute %>
6
+ <% end -%>
7
+ end
8
+ <% end -%>