active_model_serializers 0.10.0 → 0.10.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +6 -5
  3. data/.travis.yml +30 -21
  4. data/CHANGELOG.md +172 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +23 -4
  7. data/README.md +166 -28
  8. data/Rakefile +3 -32
  9. data/active_model_serializers.gemspec +22 -25
  10. data/appveyor.yml +10 -6
  11. data/bin/rubocop +38 -0
  12. data/docs/README.md +2 -1
  13. data/docs/general/adapters.md +35 -11
  14. data/docs/general/caching.md +7 -1
  15. data/docs/general/configuration_options.md +86 -1
  16. data/docs/general/deserialization.md +1 -1
  17. data/docs/general/fields.md +31 -0
  18. data/docs/general/getting_started.md +1 -1
  19. data/docs/general/logging.md +7 -0
  20. data/docs/general/rendering.md +63 -25
  21. data/docs/general/serializers.md +125 -14
  22. data/docs/howto/add_pagination_links.md +16 -17
  23. data/docs/howto/add_relationship_links.md +140 -0
  24. data/docs/howto/add_root_key.md +11 -0
  25. data/docs/howto/grape_integration.md +42 -0
  26. data/docs/howto/outside_controller_use.md +12 -4
  27. data/docs/howto/passing_arbitrary_options.md +2 -2
  28. data/docs/howto/serialize_poro.md +46 -5
  29. data/docs/howto/test.md +2 -0
  30. data/docs/howto/upgrade_from_0_8_to_0_10.md +265 -0
  31. data/docs/integrations/ember-and-json-api.md +67 -32
  32. data/docs/jsonapi/schema.md +1 -1
  33. data/lib/action_controller/serialization.rb +13 -3
  34. data/lib/active_model/serializer/adapter/base.rb +2 -0
  35. data/lib/active_model/serializer/array_serializer.rb +8 -5
  36. data/lib/active_model/serializer/association.rb +62 -10
  37. data/lib/active_model/serializer/belongs_to_reflection.rb +4 -3
  38. data/lib/active_model/serializer/collection_serializer.rb +39 -13
  39. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +82 -115
  40. data/lib/active_model/serializer/error_serializer.rb +11 -7
  41. data/lib/active_model/serializer/errors_serializer.rb +25 -20
  42. data/lib/active_model/serializer/has_many_reflection.rb +3 -3
  43. data/lib/active_model/serializer/has_one_reflection.rb +1 -4
  44. data/lib/active_model/serializer/lazy_association.rb +95 -0
  45. data/lib/active_model/serializer/lint.rb +134 -130
  46. data/lib/active_model/serializer/reflection.rb +127 -67
  47. data/lib/active_model/serializer/version.rb +1 -1
  48. data/lib/active_model/serializer.rb +297 -79
  49. data/lib/active_model_serializers/adapter/attributes.rb +3 -66
  50. data/lib/active_model_serializers/adapter/base.rb +39 -39
  51. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +2 -2
  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 +47 -21
  54. data/lib/active_model_serializers/adapter/json_api/relationship.rb +75 -23
  55. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +39 -10
  56. data/lib/active_model_serializers/adapter/json_api.rb +71 -57
  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/lookup_chain.rb +80 -0
  61. data/lib/active_model_serializers/model.rb +109 -28
  62. data/lib/active_model_serializers/railtie.rb +3 -1
  63. data/lib/active_model_serializers/register_jsonapi_renderer.rb +44 -31
  64. data/lib/active_model_serializers/serializable_resource.rb +6 -5
  65. data/lib/active_model_serializers/serialization_context.rb +10 -3
  66. data/lib/active_model_serializers/test/schema.rb +2 -2
  67. data/lib/active_model_serializers.rb +16 -1
  68. data/lib/generators/rails/resource_override.rb +1 -1
  69. data/lib/generators/rails/serializer_generator.rb +4 -4
  70. data/lib/grape/active_model_serializers.rb +7 -5
  71. data/lib/grape/formatters/active_model_serializers.rb +19 -2
  72. data/lib/grape/helpers/active_model_serializers.rb +1 -0
  73. data/lib/tasks/rubocop.rake +53 -0
  74. data/test/action_controller/adapter_selector_test.rb +14 -5
  75. data/test/action_controller/explicit_serializer_test.rb +5 -4
  76. data/test/action_controller/json/include_test.rb +106 -27
  77. data/test/action_controller/json_api/deserialization_test.rb +1 -1
  78. data/test/action_controller/json_api/errors_test.rb +8 -9
  79. data/test/action_controller/json_api/fields_test.rb +66 -0
  80. data/test/action_controller/json_api/linked_test.rb +29 -24
  81. data/test/action_controller/json_api/pagination_test.rb +31 -23
  82. data/test/action_controller/json_api/transform_test.rb +11 -3
  83. data/test/action_controller/lookup_proc_test.rb +49 -0
  84. data/test/action_controller/namespace_lookup_test.rb +232 -0
  85. data/test/action_controller/serialization_scope_name_test.rb +12 -6
  86. data/test/action_controller/serialization_test.rb +12 -9
  87. data/test/active_model_serializers/json_pointer_test.rb +15 -13
  88. data/test/active_model_serializers/model_test.rb +137 -4
  89. data/test/active_model_serializers/railtie_test_isolated.rb +12 -7
  90. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
  91. data/test/active_model_serializers/serialization_context_test_isolated.rb +23 -10
  92. data/test/active_model_serializers/test/schema_test.rb +3 -2
  93. data/test/adapter/attributes_test.rb +40 -0
  94. data/test/adapter/json/collection_test.rb +14 -0
  95. data/test/adapter/json/has_many_test.rb +10 -2
  96. data/test/adapter/json/transform_test.rb +15 -15
  97. data/test/adapter/json_api/collection_test.rb +4 -3
  98. data/test/adapter/json_api/errors_test.rb +17 -19
  99. data/test/adapter/json_api/fields_test.rb +12 -3
  100. data/test/adapter/json_api/has_many_test.rb +49 -20
  101. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +213 -0
  102. data/test/adapter/json_api/json_api_test.rb +5 -7
  103. data/test/adapter/json_api/linked_test.rb +33 -12
  104. data/test/adapter/json_api/links_test.rb +4 -2
  105. data/test/adapter/json_api/pagination_links_test.rb +53 -13
  106. data/test/adapter/json_api/parse_test.rb +1 -1
  107. data/test/adapter/json_api/relationship_test.rb +309 -73
  108. data/test/adapter/json_api/resource_meta_test.rb +3 -3
  109. data/test/adapter/json_api/transform_test.rb +263 -253
  110. data/test/adapter/json_api/type_test.rb +168 -36
  111. data/test/adapter/json_test.rb +8 -7
  112. data/test/adapter/null_test.rb +1 -2
  113. data/test/adapter/polymorphic_test.rb +52 -5
  114. data/test/adapter_test.rb +1 -1
  115. data/test/benchmark/app.rb +1 -1
  116. data/test/benchmark/benchmarking_support.rb +1 -1
  117. data/test/benchmark/bm_active_record.rb +81 -0
  118. data/test/benchmark/bm_adapter.rb +38 -0
  119. data/test/benchmark/bm_caching.rb +16 -16
  120. data/test/benchmark/bm_lookup_chain.rb +83 -0
  121. data/test/benchmark/bm_transform.rb +21 -10
  122. data/test/benchmark/controllers.rb +16 -17
  123. data/test/benchmark/fixtures.rb +72 -72
  124. data/test/cache_test.rb +235 -69
  125. data/test/collection_serializer_test.rb +31 -14
  126. data/test/fixtures/active_record.rb +45 -10
  127. data/test/fixtures/poro.rb +124 -181
  128. data/test/generators/serializer_generator_test.rb +23 -5
  129. data/test/grape_test.rb +170 -56
  130. data/test/lint_test.rb +1 -1
  131. data/test/logger_test.rb +13 -11
  132. data/test/serializable_resource_test.rb +18 -22
  133. data/test/serializers/association_macros_test.rb +3 -2
  134. data/test/serializers/associations_test.rb +222 -49
  135. data/test/serializers/attribute_test.rb +5 -3
  136. data/test/serializers/attributes_test.rb +1 -1
  137. data/test/serializers/caching_configuration_test_isolated.rb +6 -6
  138. data/test/serializers/fieldset_test.rb +1 -1
  139. data/test/serializers/meta_test.rb +12 -6
  140. data/test/serializers/options_test.rb +17 -6
  141. data/test/serializers/read_attribute_for_serialization_test.rb +3 -3
  142. data/test/serializers/reflection_test.rb +427 -0
  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 +9 -4
  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 +31 -5
  151. data/test/test_helper.rb +13 -0
  152. metadata +130 -71
  153. data/.rubocop_todo.yml +0 -167
  154. data/docs/ARCHITECTURE.md +0 -126
  155. data/lib/active_model/serializer/associations.rb +0 -100
  156. data/lib/active_model/serializer/attributes.rb +0 -82
  157. data/lib/active_model/serializer/collection_reflection.rb +0 -7
  158. data/lib/active_model/serializer/configuration.rb +0 -35
  159. data/lib/active_model/serializer/include_tree.rb +0 -111
  160. data/lib/active_model/serializer/links.rb +0 -35
  161. data/lib/active_model/serializer/meta.rb +0 -29
  162. data/lib/active_model/serializer/singular_reflection.rb +0 -7
  163. data/lib/active_model/serializer/type.rb +0 -25
  164. data/lib/active_model_serializers/key_transform.rb +0 -70
  165. data/test/active_model_serializers/key_transform_test.rb +0 -263
  166. data/test/adapter/json_api/has_many_embed_ids_test.rb +0 -43
  167. data/test/adapter/json_api/relationships_test.rb +0 -199
  168. data/test/adapter/json_api/resource_identifier_test.rb +0 -85
  169. data/test/include_tree/from_include_args_test.rb +0 -26
  170. data/test/include_tree/from_string_test.rb +0 -94
  171. data/test/include_tree/include_args_to_hash_test.rb +0 -64
@@ -1,49 +1,130 @@
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
+ require 'active_support/core_ext/hash'
4
5
  module ActiveModelSerializers
5
6
  class Model
6
- include ActiveModel::Model
7
7
  include ActiveModel::Serializers::JSON
8
+ include ActiveModel::Model
8
9
 
9
- attr_reader :attributes, :errors
10
+ # Declare names of attributes to be included in +attributes+ hash.
11
+ # Is only available as a class-method since the ActiveModel::Serialization mixin in Rails
12
+ # uses an +attribute_names+ local variable, which may conflict if we were to add instance methods here.
13
+ #
14
+ # @overload attribute_names
15
+ # @return [Array<Symbol>]
16
+ class_attribute :attribute_names, instance_writer: false, instance_reader: false
17
+ # Initialize +attribute_names+ for all subclasses. The array is usually
18
+ # mutated in the +attributes+ method, but can be set directly, as well.
19
+ self.attribute_names = []
10
20
 
11
- def initialize(attributes = {})
12
- @attributes = attributes
13
- @errors = ActiveModel::Errors.new(self)
14
- super
21
+ # Easily declare instance attributes with setters and getters for each.
22
+ #
23
+ # To initialize an instance, all attributes must have setters.
24
+ # However, the hash returned by +attributes+ instance method will ALWAYS
25
+ # be the value of the initial attributes, regardless of what accessors are defined.
26
+ # The only way to change the change the attributes after initialization is
27
+ # to mutate the +attributes+ directly.
28
+ # Accessor methods do NOT mutate the attributes. (This is a bug).
29
+ #
30
+ # @note For now, the Model only supports the notion of 'attributes'.
31
+ # In the tests, there is a special Model that also supports 'associations'. This is
32
+ # important so that we can add accessors for values that should not appear in the
33
+ # attributes hash when modeling associations. It is not yet clear if it
34
+ # makes sense for a PORO to have associations outside of the tests.
35
+ #
36
+ # @overload attributes(names)
37
+ # @param names [Array<String, Symbol>]
38
+ # @param name [String, Symbol]
39
+ def self.attributes(*names)
40
+ self.attribute_names |= names.map(&:to_sym)
41
+ # Silence redefinition of methods warnings
42
+ ActiveModelSerializers.silence_warnings do
43
+ attr_accessor(*names)
44
+ end
15
45
  end
16
46
 
17
- # Defaults to the downcased model name.
18
- def id
19
- attributes.fetch(:id) { self.class.name.downcase }
47
+ # Opt-in to breaking change
48
+ def self.derive_attributes_from_names_and_fix_accessors
49
+ unless included_modules.include?(DeriveAttributesFromNamesAndFixAccessors)
50
+ prepend(DeriveAttributesFromNamesAndFixAccessors)
51
+ end
20
52
  end
21
53
 
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")}" }
54
+ module DeriveAttributesFromNamesAndFixAccessors
55
+ def self.included(base)
56
+ # NOTE that +id+ will always be in +attributes+.
57
+ base.attributes :id
58
+ end
59
+
60
+ # Override the +attributes+ method so that the hash is derived from +attribute_names+.
61
+ #
62
+ # The fields in +attribute_names+ determines the returned hash.
63
+ # +attributes+ are returned frozen to prevent any expectations that mutation affects
64
+ # the actual values in the model.
65
+ def attributes
66
+ self.class.attribute_names.each_with_object({}) do |attribute_name, result|
67
+ result[attribute_name] = public_send(attribute_name).freeze
68
+ end.with_indifferent_access.freeze
69
+ end
25
70
  end
26
71
 
27
- # Defaults to the time the serializer file was modified.
28
- def updated_at
29
- attributes.fetch(:updated_at) { File.mtime(__FILE__) }
72
+ # Support for validation and other ActiveModel::Errors
73
+ # @return [ActiveModel::Errors]
74
+ attr_reader :errors
75
+
76
+ # (see #updated_at)
77
+ attr_writer :updated_at
78
+
79
+ # The only way to change the attributes of an instance is to directly mutate the attributes.
80
+ # @example
81
+ #
82
+ # model.attributes[:foo] = :bar
83
+ # @return [Hash]
84
+ attr_reader :attributes
85
+
86
+ # @param attributes [Hash]
87
+ def initialize(attributes = {})
88
+ attributes ||= {} # protect against nil
89
+ @attributes = attributes.symbolize_keys.with_indifferent_access
90
+ @errors = ActiveModel::Errors.new(self)
91
+ super
30
92
  end
31
93
 
32
- def read_attribute_for_serialization(key)
33
- if key == :id || key == 'id'
34
- attributes.fetch(key) { id }
35
- else
36
- attributes[key]
94
+ # Defaults to the downcased model name.
95
+ # This probably isn't a good default, since it's not a unique instance identifier,
96
+ # but that's what is currently implemented \_('-')_/.
97
+ #
98
+ # @note Though +id+ is defined, it will only show up
99
+ # in +attributes+ when it is passed in to the initializer or added to +attributes+,
100
+ # such as <tt>attributes[:id] = 5</tt>.
101
+ # @return [String, Numeric, Symbol]
102
+ def id
103
+ attributes.fetch(:id) do
104
+ defined?(@id) ? @id : self.class.model_name.name && self.class.model_name.name.downcase
37
105
  end
38
106
  end
39
107
 
40
- # The following methods are needed to be minimally implemented for ActiveModel::Errors
41
- def self.human_attribute_name(attr, _options = {})
42
- attr
108
+ # When not set, defaults to the time the file was modified.
109
+ #
110
+ # @note Though +updated_at+ and +updated_at=+ are defined, it will only show up
111
+ # in +attributes+ when it is passed in to the initializer or added to +attributes+,
112
+ # such as <tt>attributes[:updated_at] = Time.current</tt>.
113
+ # @return [String, Numeric, Time]
114
+ def updated_at
115
+ attributes.fetch(:updated_at) do
116
+ defined?(@updated_at) ? @updated_at : File.mtime(__FILE__)
117
+ end
43
118
  end
44
119
 
45
- def self.lookup_ancestors
46
- [self]
120
+ # To customize model behavior, this method must be redefined. However,
121
+ # there are other ways of setting the +cache_key+ a serializer uses.
122
+ # @return [String]
123
+ def cache_key
124
+ ActiveSupport::Cache.expand_cache_key([
125
+ self.class.model_name.name.downcase,
126
+ "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}"
127
+ ].compact)
47
128
  end
48
129
  end
49
130
  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,8 +32,22 @@ 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'
37
- require 'active_model_serializers/railtie' if defined?(::Rails)
52
+ require 'active_model_serializers/railtie' if defined?(::Rails::Railtie)
38
53
  end
@@ -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
@@ -0,0 +1,53 @@
1
+ begin
2
+ require 'rubocop'
3
+ require 'rubocop/rake_task'
4
+ rescue LoadError # rubocop:disable Lint/HandleExceptions
5
+ else
6
+ require 'rbconfig'
7
+ # https://github.com/bundler/bundler/blob/1b3eb2465a/lib/bundler/constants.rb#L2
8
+ windows_platforms = /(msdos|mswin|djgpp|mingw)/
9
+ if RbConfig::CONFIG['host_os'] =~ windows_platforms
10
+ desc 'No-op rubocop on Windows-- unsupported platform'
11
+ task :rubocop do
12
+ puts 'Skipping rubocop on Windows'
13
+ end
14
+ elsif defined?(::Rubinius)
15
+ desc 'No-op rubocop to avoid rbx segfault'
16
+ task :rubocop do
17
+ puts 'Skipping rubocop on rbx due to segfault'
18
+ puts 'https://github.com/rubinius/rubinius/issues/3499'
19
+ end
20
+ else
21
+ Rake::Task[:rubocop].clear if Rake::Task.task_defined?(:rubocop)
22
+ patterns = [
23
+ 'Gemfile',
24
+ 'Rakefile',
25
+ 'lib/**/*.{rb,rake}',
26
+ 'config/**/*.rb',
27
+ 'app/**/*.rb',
28
+ 'test/**/*.rb'
29
+ ]
30
+ desc 'Execute rubocop'
31
+ RuboCop::RakeTask.new(:rubocop) do |task|
32
+ task.options = ['--rails', '--display-cop-names', '--display-style-guide']
33
+ task.formatters = ['progress']
34
+ task.patterns = patterns
35
+ task.fail_on_error = true
36
+ end
37
+
38
+ namespace :rubocop do
39
+ desc 'Auto-gen rubocop config'
40
+ task :auto_gen_config do
41
+ options = ['--auto-gen-config'].concat patterns
42
+ require 'benchmark'
43
+ result = 0
44
+ cli = RuboCop::CLI.new
45
+ time = Benchmark.realtime do
46
+ result = cli.run(options)
47
+ end
48
+ puts "Finished in #{time} seconds" if cli.options[:debug]
49
+ abort('RuboCop failed!') if result.nonzero?
50
+ end
51
+ end
52
+ end
53
+ end
@@ -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(id: 'render_using_adapter_override', 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: 'render_using_adapter_override',
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
  }