active_model_serializers 0.10.0 → 0.10.3

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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -4
  3. data/.travis.yml +9 -1
  4. data/CHANGELOG.md +81 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +5 -2
  7. data/README.md +24 -24
  8. data/Rakefile +3 -3
  9. data/active_model_serializers.gemspec +20 -24
  10. data/docs/ARCHITECTURE.md +6 -7
  11. data/docs/README.md +2 -0
  12. data/docs/general/adapters.md +4 -2
  13. data/docs/general/caching.md +7 -1
  14. data/docs/general/configuration_options.md +70 -1
  15. data/docs/general/deserialization.md +1 -1
  16. data/docs/general/fields.md +31 -0
  17. data/docs/general/rendering.md +42 -3
  18. data/docs/general/serializers.md +97 -8
  19. data/docs/howto/add_pagination_links.md +4 -5
  20. data/docs/howto/add_relationship_links.md +137 -0
  21. data/docs/howto/add_root_key.md +4 -0
  22. data/docs/howto/grape_integration.md +42 -0
  23. data/docs/howto/outside_controller_use.md +9 -2
  24. data/docs/howto/passing_arbitrary_options.md +2 -2
  25. data/docs/howto/test.md +2 -0
  26. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  27. data/docs/integrations/ember-and-json-api.md +64 -32
  28. data/docs/jsonapi/schema.md +1 -1
  29. data/lib/action_controller/serialization.rb +13 -3
  30. data/lib/active_model/serializer/adapter/base.rb +2 -0
  31. data/lib/active_model/serializer/array_serializer.rb +8 -5
  32. data/lib/active_model/serializer/association.rb +19 -4
  33. data/lib/active_model/serializer/belongs_to_reflection.rb +0 -3
  34. data/lib/active_model/serializer/collection_serializer.rb +35 -12
  35. data/lib/active_model/serializer/{associations.rb → concerns/associations.rb} +13 -11
  36. data/lib/active_model/serializer/{attributes.rb → concerns/attributes.rb} +0 -0
  37. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +72 -113
  38. data/lib/active_model/serializer/{configuration.rb → concerns/configuration.rb} +25 -1
  39. data/lib/active_model/serializer/{links.rb → concerns/links.rb} +0 -0
  40. data/lib/active_model/serializer/{meta.rb → concerns/meta.rb} +0 -0
  41. data/lib/active_model/serializer/{type.rb → concerns/type.rb} +0 -0
  42. data/lib/active_model/serializer/error_serializer.rb +11 -7
  43. data/lib/active_model/serializer/errors_serializer.rb +25 -20
  44. data/lib/active_model/serializer/has_many_reflection.rb +0 -3
  45. data/lib/active_model/serializer/has_one_reflection.rb +0 -3
  46. data/lib/active_model/serializer/lint.rb +134 -130
  47. data/lib/active_model/serializer/reflection.rb +37 -21
  48. data/lib/active_model/serializer/version.rb +1 -1
  49. data/lib/active_model/serializer.rb +76 -37
  50. data/lib/active_model_serializers/adapter/attributes.rb +3 -66
  51. data/lib/active_model_serializers/adapter/base.rb +38 -38
  52. data/lib/active_model_serializers/adapter/json_api/link.rb +1 -1
  53. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +8 -1
  54. data/lib/active_model_serializers/adapter/json_api/relationship.rb +30 -19
  55. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +23 -9
  56. data/lib/active_model_serializers/adapter/json_api.rb +44 -43
  57. data/lib/active_model_serializers/adapter.rb +6 -0
  58. data/lib/active_model_serializers/deprecate.rb +1 -2
  59. data/lib/active_model_serializers/deserialization.rb +2 -0
  60. data/lib/active_model_serializers/key_transform.rb +4 -0
  61. data/lib/active_model_serializers/lookup_chain.rb +80 -0
  62. data/lib/active_model_serializers/model.rb +4 -2
  63. data/lib/active_model_serializers/railtie.rb +3 -1
  64. data/lib/active_model_serializers/register_jsonapi_renderer.rb +44 -31
  65. data/lib/active_model_serializers/serializable_resource.rb +6 -5
  66. data/lib/active_model_serializers/serialization_context.rb +10 -3
  67. data/lib/active_model_serializers.rb +7 -0
  68. data/lib/generators/rails/serializer_generator.rb +4 -4
  69. data/lib/grape/active_model_serializers.rb +7 -5
  70. data/lib/grape/formatters/active_model_serializers.rb +19 -2
  71. data/lib/grape/helpers/active_model_serializers.rb +1 -0
  72. data/test/action_controller/adapter_selector_test.rb +4 -4
  73. data/test/action_controller/explicit_serializer_test.rb +5 -4
  74. data/test/action_controller/json/include_test.rb +106 -27
  75. data/test/action_controller/json_api/errors_test.rb +6 -7
  76. data/test/action_controller/json_api/fields_test.rb +57 -0
  77. data/test/action_controller/json_api/linked_test.rb +29 -24
  78. data/test/action_controller/json_api/pagination_test.rb +19 -19
  79. data/test/action_controller/json_api/transform_test.rb +3 -3
  80. data/test/action_controller/lookup_proc_test.rb +49 -0
  81. data/test/action_controller/namespace_lookup_test.rb +226 -0
  82. data/test/action_controller/serialization_test.rb +10 -7
  83. data/test/active_model_serializers/json_pointer_test.rb +15 -13
  84. data/test/active_model_serializers/key_transform_test.rb +286 -252
  85. data/test/active_model_serializers/model_test.rb +17 -4
  86. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +143 -0
  87. data/test/active_model_serializers/serialization_context_test_isolated.rb +23 -10
  88. data/test/adapter/attributes_test.rb +43 -0
  89. data/test/adapter/json/collection_test.rb +14 -0
  90. data/test/adapter/json/transform_test.rb +15 -15
  91. data/test/adapter/json_api/collection_test.rb +4 -3
  92. data/test/adapter/json_api/errors_test.rb +17 -19
  93. data/test/adapter/json_api/fields_test.rb +4 -3
  94. data/test/adapter/json_api/has_many_test.rb +39 -18
  95. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +166 -0
  96. data/test/adapter/json_api/json_api_test.rb +5 -7
  97. data/test/adapter/json_api/linked_test.rb +33 -12
  98. data/test/adapter/json_api/links_test.rb +4 -2
  99. data/test/adapter/json_api/pagination_links_test.rb +35 -8
  100. data/test/adapter/json_api/relationship_test.rb +309 -73
  101. data/test/adapter/json_api/resource_identifier_test.rb +27 -2
  102. data/test/adapter/json_api/resource_meta_test.rb +3 -3
  103. data/test/adapter/json_api/transform_test.rb +255 -253
  104. data/test/adapter/json_api/type_test.rb +1 -1
  105. data/test/adapter/json_test.rb +8 -7
  106. data/test/adapter/null_test.rb +1 -2
  107. data/test/adapter/polymorphic_test.rb +5 -5
  108. data/test/adapter_test.rb +1 -1
  109. data/test/benchmark/app.rb +1 -1
  110. data/test/benchmark/benchmarking_support.rb +1 -1
  111. data/test/benchmark/bm_active_record.rb +81 -0
  112. data/test/benchmark/bm_adapter.rb +38 -0
  113. data/test/benchmark/bm_caching.rb +16 -16
  114. data/test/benchmark/bm_lookup_chain.rb +83 -0
  115. data/test/benchmark/bm_transform.rb +16 -5
  116. data/test/benchmark/controllers.rb +16 -17
  117. data/test/benchmark/fixtures.rb +72 -72
  118. data/test/cache_test.rb +143 -49
  119. data/test/collection_serializer_test.rb +3 -3
  120. data/test/fixtures/poro.rb +52 -48
  121. data/test/generators/serializer_generator_test.rb +22 -5
  122. data/test/grape_test.rb +152 -56
  123. data/test/lint_test.rb +1 -1
  124. data/test/logger_test.rb +13 -11
  125. data/test/serializable_resource_test.rb +18 -22
  126. data/test/serializers/association_macros_test.rb +3 -2
  127. data/test/serializers/associations_test.rb +107 -32
  128. data/test/serializers/attribute_test.rb +2 -2
  129. data/test/serializers/attributes_test.rb +1 -1
  130. data/test/serializers/fieldset_test.rb +1 -1
  131. data/test/serializers/meta_test.rb +12 -6
  132. data/test/serializers/root_test.rb +1 -1
  133. data/test/serializers/serializer_for_test.rb +6 -4
  134. data/test/serializers/serializer_for_with_namespace_test.rb +87 -0
  135. data/test/support/isolated_unit.rb +5 -2
  136. data/test/support/rails5_shims.rb +8 -2
  137. data/test/support/rails_app.rb +0 -9
  138. data/test/support/serialization_testing.rb +23 -5
  139. data/test/test_helper.rb +1 -0
  140. metadata +85 -34
  141. data/.rubocop_todo.yml +0 -167
  142. data/lib/active_model/serializer/include_tree.rb +0 -111
  143. data/test/adapter/json_api/relationships_test.rb +0 -199
  144. data/test/include_tree/from_include_args_test.rb +0 -26
  145. data/test/include_tree/from_string_test.rb +0 -94
  146. data/test/include_tree/include_args_to_hash_test.rb +0 -64
@@ -9,7 +9,7 @@ module ActiveModelSerializers
9
9
  attr_reader :attributes, :errors
10
10
 
11
11
  def initialize(attributes = {})
12
- @attributes = attributes
12
+ @attributes = attributes && attributes.symbolize_keys
13
13
  @errors = ActiveModel::Errors.new(self)
14
14
  super
15
15
  end
@@ -21,7 +21,7 @@ module ActiveModelSerializers
21
21
 
22
22
  # Defaults to the downcased model name and updated_at
23
23
  def cache_key
24
- attributes.fetch(:cache_key) { "#{self.class.name.downcase}/#{id}-#{updated_at.strftime("%Y%m%d%H%M%S%9N")}" }
24
+ attributes.fetch(:cache_key) { "#{self.class.name.downcase}/#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}" }
25
25
  end
26
26
 
27
27
  # Defaults to the time the serializer file was modified.
@@ -38,6 +38,7 @@ module ActiveModelSerializers
38
38
  end
39
39
 
40
40
  # The following methods are needed to be minimally implemented for ActiveModel::Errors
41
+ # :nocov:
41
42
  def self.human_attribute_name(attr, _options = {})
42
43
  attr
43
44
  end
@@ -45,5 +46,6 @@ module ActiveModelSerializers
45
46
  def self.lookup_ancestors
46
47
  [self]
47
48
  end
49
+ # :nocov:
48
50
  end
49
51
  end
@@ -23,7 +23,7 @@ module ActiveModelSerializers
23
23
  # This hook is run after the action_controller railtie has set the configuration
24
24
  # based on the *environment* configuration and before any config/initializers are run
25
25
  # and also before eager_loading (if enabled).
26
- initializer 'active_model_serializers.set_configs', :after => 'action_controller.set_configs' do
26
+ initializer 'active_model_serializers.set_configs', after: 'action_controller.set_configs' do
27
27
  ActiveModelSerializers.logger = Rails.configuration.action_controller.logger
28
28
  ActiveModelSerializers.config.perform_caching = Rails.configuration.action_controller.perform_caching
29
29
  # We want this hook to run after the config has been set, even if ActionController has already loaded.
@@ -32,11 +32,13 @@ module ActiveModelSerializers
32
32
  end
33
33
  end
34
34
 
35
+ # :nocov:
35
36
  generators do |app|
36
37
  Rails::Generators.configure!(app.config.generators)
37
38
  Rails::Generators.hidden_namespaces.uniq!
38
39
  require 'generators/rails/resource_override'
39
40
  end
41
+ # :nocov:
40
42
 
41
43
  if Rails.env.test?
42
44
  ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Schema)
@@ -22,44 +22,57 @@
22
22
  # render jsonapi: model
23
23
  #
24
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
25
32
 
26
- module ActiveModelSerializers::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
- module ControllerSupport
33
- def serialize_jsonapi(json, options)
34
- options[:adapter] = :json_api
35
- options.fetch(:serialization_context) { options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request) }
36
- get_serializer(json, options)
37
- end
38
- end
39
- end
33
+ def self.install
34
+ # actionpack/lib/action_dispatch/http/mime_types.rb
35
+ Mime::Type.register MEDIA_TYPE, :jsonapi
40
36
 
41
- # actionpack/lib/action_dispatch/http/mime_types.rb
42
- Mime::Type.register ActiveModelSerializers::Jsonapi::MEDIA_TYPE, :jsonapi
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
43
42
 
44
- parsers = Rails::VERSION::MAJOR >= 5 ? ActionDispatch::Http::Parameters : ActionDispatch::ParamsParser
45
- media_type = Mime::Type.lookup(ActiveModelSerializers::Jsonapi::MEDIA_TYPE)
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
46
50
 
47
- # Proposal: should actually deserialize the JSON API params
48
- # to the hash format expected by `ActiveModel::Serializers::JSON`
49
- # actionpack/lib/action_dispatch/http/parameters.rb
50
- parsers::DEFAULT_PARSERS[media_type] = lambda do |body|
51
- data = JSON.parse(body)
52
- data = { :_json => data } unless data.is_a?(Hash)
53
- data.with_indifferent_access
54
- end
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
55
61
 
56
- # ref https://github.com/rails/rails/pull/21496
57
- ActionController::Renderers.add :jsonapi do |json, options|
58
- json = serialize_jsonapi(json, options).to_json(options) unless json.is_a?(String)
59
- self.content_type ||= media_type
60
- self.response_body = json
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
61
72
  end
62
73
 
74
+ ActiveModelSerializers::Jsonapi.install
75
+
63
76
  ActiveSupport.on_load(:action_controller) do
64
77
  include ActiveModelSerializers::Jsonapi::ControllerSupport
65
78
  end
@@ -38,9 +38,10 @@ module ActiveModelSerializers
38
38
 
39
39
  def find_adapter
40
40
  return resource unless serializer?
41
- ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts)
42
- rescue ActiveModel::Serializer::CollectionSerializer::NoSerializerError
43
- resource
41
+ adapter = catch :no_serializer do
42
+ ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts)
43
+ end
44
+ adapter || resource
44
45
  end
45
46
 
46
47
  def serializer_instance
@@ -49,12 +50,12 @@ module ActiveModelSerializers
49
50
 
50
51
  # Get serializer either explicitly :serializer or implicitly from resource
51
52
  # Remove :serializer key from serializer_opts
52
- # Replace :serializer key with :each_serializer if present
53
+ # Remove :each_serializer if present and set as :serializer key
53
54
  def serializer
54
55
  @serializer ||=
55
56
  begin
56
57
  @serializer = serializer_opts.delete(:serializer)
57
- @serializer ||= ActiveModel::Serializer.serializer_for(resource)
58
+ @serializer ||= ActiveModel::Serializer.serializer_for(resource, serializer_opts)
58
59
 
59
60
  if serializer_opts.key?(:each_serializer)
60
61
  serializer_opts[:serializer] = serializer_opts.delete(:each_serializer)
@@ -1,3 +1,4 @@
1
+ require 'active_support/core_ext/array/extract_options'
1
2
  module ActiveModelSerializers
2
3
  class SerializationContext
3
4
  class << self
@@ -22,9 +23,15 @@ module ActiveModelSerializers
22
23
 
23
24
  attr_reader :request_url, :query_parameters, :key_transform
24
25
 
25
- def initialize(request, options = {})
26
- @request_url = request.original_url[/\A[^?]+/]
27
- @query_parameters = request.query_parameters
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)
28
35
  @url_helpers = options.delete(:url_helpers) || self.class.url_helpers
29
36
  @default_url_options = options.delete(:default_url_options) || self.class.default_url_options
30
37
  end
@@ -14,6 +14,7 @@ module ActiveModelSerializers
14
14
  autoload :Adapter
15
15
  autoload :JsonPointer
16
16
  autoload :Deprecate
17
+ autoload :LookupChain
17
18
 
18
19
  class << self; attr_accessor :logger; end
19
20
  self.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
@@ -31,6 +32,12 @@ module ActiveModelSerializers
31
32
  [file, lineno]
32
33
  end
33
34
 
35
+ # Memoized default include directive
36
+ # @return [JSONAPI::IncludeDirective]
37
+ def self.default_include_directive
38
+ @default_include_directive ||= JSONAPI::IncludeDirective.new(config.default_includes, allow_wildcard: true)
39
+ end
40
+
34
41
  require 'active_model/serializer/version'
35
42
  require 'active_model/serializer'
36
43
  require 'active_model/serializable_resource'
@@ -2,11 +2,11 @@ module Rails
2
2
  module Generators
3
3
  class SerializerGenerator < NamedBase
4
4
  source_root File.expand_path('../templates', __FILE__)
5
- check_class_collision :suffix => 'Serializer'
5
+ check_class_collision suffix: 'Serializer'
6
6
 
7
- argument :attributes, :type => :array, :default => [], :banner => 'field:type field:type'
7
+ argument :attributes, type: :array, default: [], banner: 'field:type field:type'
8
8
 
9
- class_option :parent, :type => :string, :desc => 'The parent class for the generated serializer'
9
+ class_option :parent, type: :string, desc: 'The parent class for the generated serializer'
10
10
 
11
11
  def create_serializer_file
12
12
  template 'serializer.rb.erb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb")
@@ -25,7 +25,7 @@ module Rails
25
25
  def parent_class_name
26
26
  if options[:parent]
27
27
  options[:parent]
28
- elsif defined?(::ApplicationSerializer)
28
+ elsif 'ApplicationSerializer'.safe_constantize
29
29
  'ApplicationSerializer'
30
30
  else
31
31
  'ActiveModel::Serializer'
@@ -4,11 +4,13 @@ require 'active_model_serializers'
4
4
  require 'grape/formatters/active_model_serializers'
5
5
  require 'grape/helpers/active_model_serializers'
6
6
 
7
- module Grape::ActiveModelSerializers
8
- extend ActiveSupport::Concern
7
+ module Grape
8
+ module ActiveModelSerializers
9
+ extend ActiveSupport::Concern
9
10
 
10
- included do
11
- formatter :json, Grape::Formatters::ActiveModelSerializers
12
- helpers Grape::Helpers::ActiveModelSerializers
11
+ included do
12
+ formatter :json, Grape::Formatters::ActiveModelSerializers
13
+ helpers Grape::Helpers::ActiveModelSerializers
14
+ end
13
15
  end
14
16
  end
@@ -2,14 +2,31 @@
2
2
  #
3
3
  # Serializer options can be passed as a hash from your Grape endpoint using env[:active_model_serializer_options],
4
4
  # or better yet user the render helper in Grape::Helpers::ActiveModelSerializers
5
+
6
+ require 'active_model_serializers/serialization_context'
7
+
5
8
  module Grape
6
9
  module Formatters
7
10
  module ActiveModelSerializers
8
11
  def self.call(resource, env)
9
- serializer_options = {}
10
- serializer_options.merge!(env[:active_model_serializer_options]) if env[:active_model_serializer_options]
12
+ serializer_options = build_serializer_options(env)
11
13
  ::ActiveModelSerializers::SerializableResource.new(resource, serializer_options).to_json
12
14
  end
15
+
16
+ def self.build_serializer_options(env)
17
+ ams_options = env[:active_model_serializer_options] || {}
18
+
19
+ # Add serialization context
20
+ ams_options.fetch(:serialization_context) do
21
+ request = env['grape.request']
22
+ ams_options[:serialization_context] = ::ActiveModelSerializers::SerializationContext.new(
23
+ request_url: request.url[/\A[^?]+/],
24
+ query_parameters: request.params
25
+ )
26
+ end
27
+
28
+ ams_options
29
+ end
13
30
  end
14
31
  end
15
32
  end
@@ -1,4 +1,5 @@
1
1
  # Helpers can be included in your Grape endpoint as: helpers Grape::Helpers::ActiveModelSerializers
2
+
2
3
  module Grape
3
4
  module Helpers
4
5
  module ActiveModelSerializers
@@ -5,17 +5,17 @@ module ActionController
5
5
  class AdapterSelectorTest < ActionController::TestCase
6
6
  class AdapterSelectorTestController < ActionController::Base
7
7
  def render_using_default_adapter
8
- @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
8
+ @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
9
9
  render json: @profile
10
10
  end
11
11
 
12
12
  def render_using_adapter_override
13
- @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
13
+ @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
14
14
  render json: @profile, adapter: :json_api
15
15
  end
16
16
 
17
17
  def render_skipping_adapter
18
- @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
18
+ @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
19
19
  render json: @profile, adapter: false
20
20
  end
21
21
  end
@@ -32,7 +32,7 @@ module ActionController
32
32
 
33
33
  expected = {
34
34
  data: {
35
- id: assigns(:profile).id.to_s,
35
+ id: @controller.instance_variable_get(:@profile).id.to_s,
36
36
  type: 'profiles',
37
37
  attributes: {
38
38
  name: 'Name 1',
@@ -100,11 +100,12 @@ module ActionController
100
100
  get :render_array_using_explicit_serializer_and_custom_serializers
101
101
 
102
102
  expected = [
103
- { 'title' => 'New Post',
103
+ {
104
+ 'title' => 'New Post',
104
105
  'body' => 'Body',
105
- 'id' => assigns(:post).id,
106
+ 'id' => @controller.instance_variable_get(:@post).id,
106
107
  'comments' => [{ 'id' => 1 }, { 'id' => 2 }],
107
- 'author' => { 'id' => assigns(:author).id }
108
+ 'author' => { 'id' => @controller.instance_variable_get(:@author).id }
108
109
  }
109
110
  ]
110
111
 
@@ -122,7 +123,7 @@ module ActionController
122
123
  id: 42,
123
124
  lat: '-23.550520',
124
125
  lng: '-46.633309',
125
- place: 'Nowhere' # is a virtual attribute on LocationSerializer
126
+ address: 'Nowhere' # is a virtual attribute on LocationSerializer
126
127
  }
127
128
  ]
128
129
  }
@@ -4,6 +4,10 @@ module ActionController
4
4
  module Serialization
5
5
  class Json
6
6
  class IncludeTest < ActionController::TestCase
7
+ INCLUDE_STRING = 'posts.comments'.freeze
8
+ INCLUDE_HASH = { posts: :comments }.freeze
9
+ DEEP_INCLUDE = 'posts.comments.author'.freeze
10
+
7
11
  class IncludeTestController < ActionController::Base
8
12
  def setup_data
9
13
  ActionController::Base.cache_store.clear
@@ -38,17 +42,28 @@ module ActionController
38
42
 
39
43
  def render_resource_with_include_hash
40
44
  setup_data
41
- render json: @author, include: { posts: :comments }, adapter: :json
45
+ render json: @author, include: INCLUDE_HASH, adapter: :json
42
46
  end
43
47
 
44
48
  def render_resource_with_include_string
45
49
  setup_data
46
- render json: @author, include: 'posts.comments', adapter: :json
50
+ render json: @author, include: INCLUDE_STRING, adapter: :json
47
51
  end
48
52
 
49
53
  def render_resource_with_deep_include
50
54
  setup_data
51
- render json: @author, include: 'posts.comments.author', adapter: :json
55
+ render json: @author, include: DEEP_INCLUDE, adapter: :json
56
+ end
57
+
58
+ def render_without_recursive_relationships
59
+ # testing recursive includes ('**') can't have any cycles in the
60
+ # relationships, or we enter an infinite loop.
61
+ author = Author.new(id: 11, name: 'Jane Doe')
62
+ post = Post.new(id: 12, title: 'Hello World', body: 'My first post')
63
+ comment = Comment.new(id: 13, body: 'Commentary')
64
+ author.posts = [post]
65
+ post.comments = [comment]
66
+ render json: author
52
67
  end
53
68
  end
54
69
 
@@ -77,34 +92,90 @@ module ActionController
77
92
  def test_render_resource_with_include_hash
78
93
  get :render_resource_with_include_hash
79
94
  response = JSON.parse(@response.body)
80
- expected = {
81
- 'author' => {
82
- 'id' => 1,
83
- 'name' => 'Steve K.',
95
+
96
+ assert_equal(expected_include_response, response)
97
+ end
98
+
99
+ def test_render_resource_with_include_string
100
+ get :render_resource_with_include_string
101
+
102
+ response = JSON.parse(@response.body)
103
+
104
+ assert_equal(expected_include_response, response)
105
+ end
106
+
107
+ def test_render_resource_with_deep_include
108
+ get :render_resource_with_deep_include
109
+
110
+ response = JSON.parse(@response.body)
111
+
112
+ assert_equal(expected_deep_include_response, response)
113
+ end
114
+
115
+ def test_render_with_empty_default_includes
116
+ with_default_includes '' do
117
+ get :render_without_include
118
+ response = JSON.parse(@response.body)
119
+ expected = {
120
+ 'author' => {
121
+ 'id' => 1,
122
+ 'name' => 'Steve K.'
123
+ }
124
+ }
125
+ assert_equal(expected, response)
126
+ end
127
+ end
128
+
129
+ def test_render_with_recursive_default_includes
130
+ with_default_includes '**' do
131
+ get :render_without_recursive_relationships
132
+ response = JSON.parse(@response.body)
133
+
134
+ expected = {
135
+ 'id' => 11,
136
+ 'name' => 'Jane Doe',
137
+ 'roles' => nil,
138
+ 'bio' => nil,
84
139
  'posts' => [
85
140
  {
86
- 'id' => 42, 'title' => 'New Post', 'body' => 'Body',
141
+ 'id' => 12,
142
+ 'title' => 'Hello World',
143
+ 'body' => 'My first post',
87
144
  'comments' => [
88
145
  {
89
- 'id' => 1, 'body' => 'ZOMG A COMMENT'
90
- },
91
- {
92
- 'id' => 2, 'body' => 'ZOMG ANOTHER COMMENT'
146
+ 'id' => 13,
147
+ 'body' => 'Commentary',
148
+ 'post' => nil, # not set to avoid infinite recursion
149
+ 'author' => nil, # not set to avoid infinite recursion
93
150
  }
94
- ]
151
+ ],
152
+ 'blog' => {
153
+ 'id' => 999,
154
+ 'name' => 'Custom blog',
155
+ 'writer' => nil,
156
+ 'articles' => nil
157
+ },
158
+ 'author' => nil # not set to avoid infinite recursion
95
159
  }
96
160
  ]
97
161
  }
98
- }
162
+ assert_equal(expected, response)
163
+ end
164
+ end
99
165
 
100
- assert_equal(expected, response)
166
+ def test_render_with_includes_overrides_default_includes
167
+ with_default_includes '' do
168
+ get :render_resource_with_include_hash
169
+ response = JSON.parse(@response.body)
170
+
171
+ assert_equal(expected_include_response, response)
172
+ end
101
173
  end
102
174
 
103
- def test_render_resource_with_include_string
104
- get :render_resource_with_include_string
175
+ private
105
176
 
106
- response = JSON.parse(@response.body)
107
- expected = {
177
+ def expected_include_response
178
+ {
108
179
  'author' => {
109
180
  'id' => 1,
110
181
  'name' => 'Steve K.',
@@ -123,15 +194,10 @@ module ActionController
123
194
  ]
124
195
  }
125
196
  }
126
-
127
- assert_equal(expected, response)
128
197
  end
129
198
 
130
- def test_render_resource_with_deep_include
131
- get :render_resource_with_deep_include
132
-
133
- response = JSON.parse(@response.body)
134
- expected = {
199
+ def expected_deep_include_response
200
+ {
135
201
  'author' => {
136
202
  'id' => 1,
137
203
  'name' => 'Steve K.',
@@ -158,8 +224,21 @@ module ActionController
158
224
  ]
159
225
  }
160
226
  }
227
+ end
161
228
 
162
- assert_equal(expected, response)
229
+ def with_default_includes(include_directive)
230
+ original = ActiveModelSerializers.config.default_includes
231
+ ActiveModelSerializers.config.default_includes = include_directive
232
+ clear_include_directive_cache
233
+ yield
234
+ ensure
235
+ ActiveModelSerializers.config.default_includes = original
236
+ clear_include_directive_cache
237
+ end
238
+
239
+ def clear_include_directive_cache
240
+ ActiveModelSerializers
241
+ .instance_variable_set(:@default_include_directive, nil)
163
242
  end
164
243
  end
165
244
  end
@@ -7,13 +7,12 @@ module ActionController
7
7
  def test_active_model_with_multiple_errors
8
8
  get :render_resource_with_errors
9
9
 
10
- expected_errors_object =
11
- { :errors =>
12
- [
13
- { :source => { :pointer => '/data/attributes/name' }, :detail => 'cannot be nil' },
14
- { :source => { :pointer => '/data/attributes/name' }, :detail => 'must be longer' },
15
- { :source => { :pointer => '/data/attributes/id' }, :detail => 'must be a uuid' }
16
- ]
10
+ expected_errors_object = {
11
+ errors: [
12
+ { source: { pointer: '/data/attributes/name' }, detail: 'cannot be nil' },
13
+ { source: { pointer: '/data/attributes/name' }, detail: 'must be longer' },
14
+ { source: { pointer: '/data/attributes/id' }, detail: 'must be a uuid' }
15
+ ]
17
16
  }.to_json
18
17
  assert_equal json_reponse_body.to_json, expected_errors_object
19
18
  end
@@ -0,0 +1,57 @@
1
+ require 'test_helper'
2
+
3
+ module ActionController
4
+ module Serialization
5
+ class JsonApi
6
+ class FieldsTest < ActionController::TestCase
7
+ class FieldsTestController < ActionController::Base
8
+ class PostSerializer < ActiveModel::Serializer
9
+ type 'posts'
10
+ attributes :title, :body, :publish_at
11
+ belongs_to :author
12
+ has_many :comments
13
+ end
14
+
15
+ def setup_post
16
+ ActionController::Base.cache_store.clear
17
+ @author = Author.new(id: 1, first_name: 'Bob', last_name: 'Jones')
18
+ @comment1 = Comment.new(id: 7, body: 'cool', author: @author)
19
+ @comment2 = Comment.new(id: 12, body: 'awesome', author: @author)
20
+ @post = Post.new(id: 1337, title: 'Title 1', body: 'Body 1',
21
+ author: @author, comments: [@comment1, @comment2],
22
+ publish_at: '2020-03-16T03:55:25.291Z')
23
+ @comment1.post = @post
24
+ @comment2.post = @post
25
+ end
26
+
27
+ def render_fields_works_on_relationships
28
+ setup_post
29
+ render json: @post, serializer: PostSerializer, adapter: :json_api, fields: { posts: [:author] }
30
+ end
31
+ end
32
+
33
+ tests FieldsTestController
34
+
35
+ test 'fields works on relationships' do
36
+ get :render_fields_works_on_relationships
37
+ response = JSON.parse(@response.body)
38
+ expected = {
39
+ 'data' => {
40
+ 'id' => '1337',
41
+ 'type' => 'posts',
42
+ 'relationships' => {
43
+ 'author' => {
44
+ 'data' => {
45
+ 'id' => '1',
46
+ 'type' => 'authors'
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ assert_equal expected, response
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end