active_model_serializers 0.10.0 → 0.10.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (161) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -4
  3. data/.travis.yml +17 -5
  4. data/CHANGELOG.md +112 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +5 -2
  7. data/README.md +166 -26
  8. data/Rakefile +3 -3
  9. data/active_model_serializers.gemspec +21 -24
  10. data/appveyor.yml +9 -3
  11. data/docs/README.md +2 -1
  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/logging.md +7 -0
  18. data/docs/general/rendering.md +44 -20
  19. data/docs/general/serializers.md +100 -11
  20. data/docs/howto/add_pagination_links.md +15 -16
  21. data/docs/howto/add_relationship_links.md +140 -0
  22. data/docs/howto/add_root_key.md +4 -0
  23. data/docs/howto/grape_integration.md +42 -0
  24. data/docs/howto/outside_controller_use.md +12 -4
  25. data/docs/howto/passing_arbitrary_options.md +2 -2
  26. data/docs/howto/serialize_poro.md +46 -5
  27. data/docs/howto/test.md +2 -0
  28. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  29. data/docs/integrations/ember-and-json-api.md +64 -32
  30. data/docs/jsonapi/schema.md +1 -1
  31. data/lib/action_controller/serialization.rb +13 -3
  32. data/lib/active_model/serializer/adapter/base.rb +2 -0
  33. data/lib/active_model/serializer/array_serializer.rb +8 -5
  34. data/lib/active_model/serializer/association.rb +19 -4
  35. data/lib/active_model/serializer/belongs_to_reflection.rb +0 -3
  36. data/lib/active_model/serializer/collection_serializer.rb +35 -12
  37. data/lib/active_model/serializer/{associations.rb → concerns/associations.rb} +13 -11
  38. data/lib/active_model/serializer/{attributes.rb → concerns/attributes.rb} +1 -1
  39. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +72 -113
  40. data/lib/active_model/serializer/{configuration.rb → concerns/configuration.rb} +25 -1
  41. data/lib/active_model/serializer/{links.rb → concerns/links.rb} +0 -0
  42. data/lib/active_model/serializer/{meta.rb → concerns/meta.rb} +0 -0
  43. data/lib/active_model/serializer/{type.rb → concerns/type.rb} +0 -0
  44. data/lib/active_model/serializer/error_serializer.rb +11 -7
  45. data/lib/active_model/serializer/errors_serializer.rb +25 -20
  46. data/lib/active_model/serializer/has_many_reflection.rb +0 -3
  47. data/lib/active_model/serializer/has_one_reflection.rb +0 -3
  48. data/lib/active_model/serializer/lint.rb +134 -130
  49. data/lib/active_model/serializer/reflection.rb +37 -21
  50. data/lib/active_model/serializer/version.rb +1 -1
  51. data/lib/active_model/serializer.rb +76 -37
  52. data/lib/active_model_serializers/adapter/attributes.rb +3 -66
  53. data/lib/active_model_serializers/adapter/base.rb +39 -39
  54. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +1 -1
  55. data/lib/active_model_serializers/adapter/json_api/link.rb +1 -1
  56. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +8 -1
  57. data/lib/active_model_serializers/adapter/json_api/relationship.rb +30 -19
  58. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +23 -9
  59. data/lib/active_model_serializers/adapter/json_api.rb +44 -43
  60. data/lib/active_model_serializers/adapter.rb +6 -0
  61. data/lib/active_model_serializers/deprecate.rb +1 -2
  62. data/lib/active_model_serializers/deserialization.rb +2 -0
  63. data/lib/active_model_serializers/lookup_chain.rb +80 -0
  64. data/lib/active_model_serializers/model.rb +108 -28
  65. data/lib/active_model_serializers/railtie.rb +3 -1
  66. data/lib/active_model_serializers/register_jsonapi_renderer.rb +44 -31
  67. data/lib/active_model_serializers/serializable_resource.rb +6 -5
  68. data/lib/active_model_serializers/serialization_context.rb +10 -3
  69. data/lib/active_model_serializers/test/schema.rb +2 -2
  70. data/lib/active_model_serializers.rb +15 -0
  71. data/lib/generators/rails/resource_override.rb +1 -1
  72. data/lib/generators/rails/serializer_generator.rb +4 -4
  73. data/lib/grape/active_model_serializers.rb +7 -5
  74. data/lib/grape/formatters/active_model_serializers.rb +19 -2
  75. data/lib/grape/helpers/active_model_serializers.rb +1 -0
  76. data/test/action_controller/adapter_selector_test.rb +14 -5
  77. data/test/action_controller/explicit_serializer_test.rb +5 -4
  78. data/test/action_controller/json/include_test.rb +106 -27
  79. data/test/action_controller/json_api/errors_test.rb +8 -9
  80. data/test/action_controller/json_api/fields_test.rb +66 -0
  81. data/test/action_controller/json_api/linked_test.rb +29 -24
  82. data/test/action_controller/json_api/pagination_test.rb +19 -19
  83. data/test/action_controller/json_api/transform_test.rb +11 -3
  84. data/test/action_controller/lookup_proc_test.rb +49 -0
  85. data/test/action_controller/namespace_lookup_test.rb +232 -0
  86. data/test/action_controller/serialization_scope_name_test.rb +12 -6
  87. data/test/action_controller/serialization_test.rb +12 -9
  88. data/test/active_model_serializers/json_pointer_test.rb +15 -13
  89. data/test/active_model_serializers/model_test.rb +137 -4
  90. data/test/active_model_serializers/railtie_test_isolated.rb +12 -7
  91. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
  92. data/test/active_model_serializers/serialization_context_test_isolated.rb +23 -10
  93. data/test/active_model_serializers/test/schema_test.rb +3 -2
  94. data/test/adapter/attributes_test.rb +40 -0
  95. data/test/adapter/json/collection_test.rb +14 -0
  96. data/test/adapter/json/has_many_test.rb +10 -2
  97. data/test/adapter/json/transform_test.rb +15 -15
  98. data/test/adapter/json_api/collection_test.rb +4 -3
  99. data/test/adapter/json_api/errors_test.rb +17 -19
  100. data/test/adapter/json_api/fields_test.rb +12 -3
  101. data/test/adapter/json_api/has_many_test.rb +49 -20
  102. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +183 -0
  103. data/test/adapter/json_api/json_api_test.rb +5 -7
  104. data/test/adapter/json_api/linked_test.rb +33 -12
  105. data/test/adapter/json_api/links_test.rb +4 -2
  106. data/test/adapter/json_api/pagination_links_test.rb +35 -8
  107. data/test/adapter/json_api/relationship_test.rb +309 -73
  108. data/test/adapter/json_api/resource_identifier_test.rb +27 -2
  109. data/test/adapter/json_api/resource_meta_test.rb +3 -3
  110. data/test/adapter/json_api/transform_test.rb +263 -253
  111. data/test/adapter/json_api/type_test.rb +1 -1
  112. data/test/adapter/json_test.rb +8 -7
  113. data/test/adapter/null_test.rb +1 -2
  114. data/test/adapter/polymorphic_test.rb +5 -5
  115. data/test/adapter_test.rb +1 -1
  116. data/test/benchmark/app.rb +1 -1
  117. data/test/benchmark/benchmarking_support.rb +1 -1
  118. data/test/benchmark/bm_active_record.rb +81 -0
  119. data/test/benchmark/bm_adapter.rb +38 -0
  120. data/test/benchmark/bm_caching.rb +16 -16
  121. data/test/benchmark/bm_lookup_chain.rb +83 -0
  122. data/test/benchmark/bm_transform.rb +21 -10
  123. data/test/benchmark/controllers.rb +16 -17
  124. data/test/benchmark/fixtures.rb +72 -72
  125. data/test/cache_test.rb +235 -69
  126. data/test/collection_serializer_test.rb +25 -12
  127. data/test/fixtures/active_record.rb +45 -10
  128. data/test/fixtures/poro.rb +124 -181
  129. data/test/generators/serializer_generator_test.rb +23 -5
  130. data/test/grape_test.rb +170 -56
  131. data/test/lint_test.rb +1 -1
  132. data/test/logger_test.rb +13 -11
  133. data/test/serializable_resource_test.rb +18 -22
  134. data/test/serializers/association_macros_test.rb +3 -2
  135. data/test/serializers/associations_test.rb +132 -36
  136. data/test/serializers/attribute_test.rb +5 -3
  137. data/test/serializers/attributes_test.rb +1 -1
  138. data/test/serializers/caching_configuration_test_isolated.rb +6 -6
  139. data/test/serializers/fieldset_test.rb +1 -1
  140. data/test/serializers/meta_test.rb +12 -6
  141. data/test/serializers/options_test.rb +17 -6
  142. data/test/serializers/read_attribute_for_serialization_test.rb +3 -3
  143. data/test/serializers/root_test.rb +1 -1
  144. data/test/serializers/serialization_test.rb +2 -2
  145. data/test/serializers/serializer_for_test.rb +12 -10
  146. data/test/serializers/serializer_for_with_namespace_test.rb +88 -0
  147. data/test/support/isolated_unit.rb +5 -2
  148. data/test/support/rails5_shims.rb +8 -2
  149. data/test/support/rails_app.rb +2 -9
  150. data/test/support/serialization_testing.rb +23 -5
  151. data/test/test_helper.rb +13 -0
  152. metadata +104 -38
  153. data/.rubocop_todo.yml +0 -167
  154. data/docs/ARCHITECTURE.md +0 -126
  155. data/lib/active_model/serializer/include_tree.rb +0 -111
  156. data/lib/active_model_serializers/key_transform.rb +0 -70
  157. data/test/active_model_serializers/key_transform_test.rb +0 -263
  158. data/test/adapter/json_api/relationships_test.rb +0 -199
  159. data/test/include_tree/from_include_args_test.rb +0 -26
  160. data/test/include_tree/from_string_test.rb +0 -94
  161. data/test/include_tree/include_args_to_hash_test.rb +0 -64
@@ -1,49 +1,129 @@
1
- # ActiveModelSerializers::Model is a convenient
2
- # serializable class to inherit from when making
3
- # serializable non-activerecord objects.
1
+ # ActiveModelSerializers::Model is a convenient superclass for making your models
2
+ # from Plain-Old Ruby Objects (PORO). It also serves as a reference implementation
3
+ # that satisfies ActiveModel::Serializer::Lint::Tests.
4
4
  module ActiveModelSerializers
5
5
  class Model
6
- include ActiveModel::Model
7
6
  include ActiveModel::Serializers::JSON
7
+ include ActiveModel::Model
8
8
 
9
- attr_reader :attributes, :errors
9
+ # Declare names of attributes to be included in +sttributes+ hash.
10
+ # Is only available as a class-method since the ActiveModel::Serialization mixin in Rails
11
+ # uses an +attribute_names+ local variable, which may conflict if we were to add instance methods here.
12
+ #
13
+ # @overload attribute_names
14
+ # @return [Array<Symbol>]
15
+ class_attribute :attribute_names, instance_writer: false, instance_reader: false
16
+ # Initialize +attribute_names+ for all subclasses. The array is usually
17
+ # mutated in the +attributes+ method, but can be set directly, as well.
18
+ self.attribute_names = []
10
19
 
11
- def initialize(attributes = {})
12
- @attributes = attributes
13
- @errors = ActiveModel::Errors.new(self)
14
- super
20
+ # Easily declare instance attributes with setters and getters for each.
21
+ #
22
+ # All attributes to initialize an instance must have setters.
23
+ # However, the hash turned by +attributes+ instance method will ALWAYS
24
+ # be the value of the initial attributes, regardless of what accessors are defined.
25
+ # The only way to change the change the attributes after initialization is
26
+ # to mutate the +attributes+ directly.
27
+ # Accessor methods do NOT mutate the attributes. (This is a bug).
28
+ #
29
+ # @note For now, the Model only supports the notion of 'attributes'.
30
+ # In the tests, there is a special Model that also supports 'associations'. This is
31
+ # important so that we can add accessors for values that should not appear in the
32
+ # attributes hash when modeling associations. It is not yet clear if it
33
+ # makes sense for a PORO to have associations outside of the tests.
34
+ #
35
+ # @overload attributes(names)
36
+ # @param names [Array<String, Symbol>]
37
+ # @param name [String, Symbol]
38
+ def self.attributes(*names)
39
+ self.attribute_names |= names.map(&:to_sym)
40
+ # Silence redefinition of methods warnings
41
+ ActiveModelSerializers.silence_warnings do
42
+ attr_accessor(*names)
43
+ end
15
44
  end
16
45
 
17
- # Defaults to the downcased model name.
18
- def id
19
- attributes.fetch(:id) { self.class.name.downcase }
46
+ # Opt-in to breaking change
47
+ def self.derive_attributes_from_names_and_fix_accessors
48
+ unless included_modules.include?(DeriveAttributesFromNamesAndFixAccessors)
49
+ prepend(DeriveAttributesFromNamesAndFixAccessors)
50
+ end
20
51
  end
21
52
 
22
- # Defaults to the downcased model name and updated_at
23
- def cache_key
24
- attributes.fetch(:cache_key) { "#{self.class.name.downcase}/#{id}-#{updated_at.strftime("%Y%m%d%H%M%S%9N")}" }
53
+ module DeriveAttributesFromNamesAndFixAccessors
54
+ def self.included(base)
55
+ # NOTE that +id+ will always be in +attributes+.
56
+ base.attributes :id
57
+ end
58
+
59
+ # Override the +attributes+ method so that the hash is derived from +attribute_names+.
60
+ #
61
+ # The the fields in +attribute_names+ determines the returned hash.
62
+ # +attributes+ are returned frozen to prevent any expectations that mutation affects
63
+ # the actual values in the model.
64
+ def attributes
65
+ self.class.attribute_names.each_with_object({}) do |attribute_name, result|
66
+ result[attribute_name] = public_send(attribute_name).freeze
67
+ end.with_indifferent_access.freeze
68
+ end
25
69
  end
26
70
 
27
- # Defaults to the time the serializer file was modified.
28
- def updated_at
29
- attributes.fetch(:updated_at) { File.mtime(__FILE__) }
71
+ # Support for validation and other ActiveModel::Errors
72
+ # @return [ActiveModel::Errors]
73
+ attr_reader :errors
74
+
75
+ # (see #updated_at)
76
+ attr_writer :updated_at
77
+
78
+ # The only way to change the attributes of an instance is to directly mutate the attributes.
79
+ # @example
80
+ #
81
+ # model.attributes[:foo] = :bar
82
+ # @return [Hash]
83
+ attr_reader :attributes
84
+
85
+ # @param attributes [Hash]
86
+ def initialize(attributes = {})
87
+ attributes ||= {} # protect against nil
88
+ @attributes = attributes.symbolize_keys.with_indifferent_access
89
+ @errors = ActiveModel::Errors.new(self)
90
+ super
30
91
  end
31
92
 
32
- def read_attribute_for_serialization(key)
33
- if key == :id || key == 'id'
34
- attributes.fetch(key) { id }
35
- else
36
- attributes[key]
93
+ # Defaults to the downcased model name.
94
+ # This probably isn't a good default, since it's not a unique instance identifier,
95
+ # but that's what is currently implemented \_('-')_/.
96
+ #
97
+ # @note Though +id+ is defined, it will only show up
98
+ # in +attributes+ when it is passed in to the initializer or added to +attributes+,
99
+ # such as <tt>attributes[:id] = 5</tt>.
100
+ # @return [String, Numeric, Symbol]
101
+ def id
102
+ attributes.fetch(:id) do
103
+ defined?(@id) ? @id : self.class.model_name.name && self.class.model_name.name.downcase
37
104
  end
38
105
  end
39
106
 
40
- # The following methods are needed to be minimally implemented for ActiveModel::Errors
41
- def self.human_attribute_name(attr, _options = {})
42
- attr
107
+ # When not set, defaults to the time the file was modified.
108
+ #
109
+ # @note Though +updated_at+ and +updated_at=+ are defined, it will only show up
110
+ # in +attributes+ when it is passed in to the initializer or added to +attributes+,
111
+ # such as <tt>attributes[:updated_at] = Time.current</tt>.
112
+ # @return [String, Numeric, Time]
113
+ def updated_at
114
+ attributes.fetch(:updated_at) do
115
+ defined?(@updated_at) ? @updated_at : File.mtime(__FILE__)
116
+ end
43
117
  end
44
118
 
45
- def self.lookup_ancestors
46
- [self]
119
+ # To customize model behavior, this method must be redefined. However,
120
+ # there are other ways of setting the +cache_key+ a serializer uses.
121
+ # @return [String]
122
+ def cache_key
123
+ ActiveSupport::Cache.expand_cache_key([
124
+ self.class.model_name.name.downcase,
125
+ "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}"
126
+ ].compact)
47
127
  end
48
128
  end
49
129
  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
@@ -60,11 +60,11 @@ module ActiveModelSerializers
60
60
  attr_reader :document_store
61
61
 
62
62
  def controller_path
63
- request.filtered_parameters[:controller]
63
+ request.filtered_parameters.with_indifferent_access[:controller]
64
64
  end
65
65
 
66
66
  def action
67
- request.filtered_parameters[:action]
67
+ request.filtered_parameters.with_indifferent_access[:action]
68
68
  end
69
69
 
70
70
  def schema_directory
@@ -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,20 @@ 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
+
41
+ def self.silence_warnings
42
+ original_verbose = $VERBOSE
43
+ $VERBOSE = nil
44
+ yield
45
+ ensure
46
+ $VERBOSE = original_verbose
47
+ end
48
+
34
49
  require 'active_model/serializer/version'
35
50
  require 'active_model/serializer'
36
51
  require 'active_model/serializable_resource'
@@ -4,7 +4,7 @@ require 'rails/generators/rails/resource/resource_generator'
4
4
  module Rails
5
5
  module Generators
6
6
  class ResourceGenerator
7
- hook_for :serializer, default: true, boolean: true
7
+ hook_for :serializer, default: true, type: :boolean
8
8
  end
9
9
  end
10
10
  end
@@ -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
@@ -3,19 +3,28 @@ require 'test_helper'
3
3
  module ActionController
4
4
  module Serialization
5
5
  class AdapterSelectorTest < ActionController::TestCase
6
+ class Profile < Model
7
+ attributes :id, :name, :description
8
+ associations :comments
9
+ end
10
+ class ProfileSerializer < ActiveModel::Serializer
11
+ type 'profiles'
12
+ attributes :name, :description
13
+ end
14
+
6
15
  class AdapterSelectorTestController < ActionController::Base
7
16
  def render_using_default_adapter
8
- @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
17
+ @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
9
18
  render json: @profile
10
19
  end
11
20
 
12
21
  def render_using_adapter_override
13
- @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
22
+ @profile = Profile.new(name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
14
23
  render json: @profile, adapter: :json_api
15
24
  end
16
25
 
17
26
  def render_skipping_adapter
18
- @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })
27
+ @profile = Profile.new(id: 'render_skipping_adapter_id', name: 'Name 1', description: 'Description 1', comments: 'Comments 1')
19
28
  render json: @profile, adapter: false
20
29
  end
21
30
  end
@@ -32,7 +41,7 @@ module ActionController
32
41
 
33
42
  expected = {
34
43
  data: {
35
- id: assigns(:profile).id.to_s,
44
+ id: @controller.instance_variable_get(:@profile).id.to_s,
36
45
  type: 'profiles',
37
46
  attributes: {
38
47
  name: 'Name 1',
@@ -46,7 +55,7 @@ module ActionController
46
55
 
47
56
  def test_render_skipping_adapter
48
57
  get :render_skipping_adapter
49
- assert_equal '{"name":"Name 1","description":"Description 1","comments":"Comments 1"}', response.body
58
+ assert_equal '{"id":"render_skipping_adapter_id","name":"Name 1","description":"Description 1"}', response.body
50
59
  end
51
60
  end
52
61
  end
@@ -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
  }