active_model_serializers 0.10.0 → 0.10.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -5
  3. data/.travis.yml +17 -5
  4. data/CHANGELOG.md +126 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +5 -2
  7. data/README.md +166 -26
  8. data/Rakefile +3 -32
  9. data/active_model_serializers.gemspec +22 -25
  10. data/appveyor.yml +9 -3
  11. data/bin/rubocop +38 -0
  12. data/docs/README.md +2 -1
  13. data/docs/general/adapters.md +29 -11
  14. data/docs/general/caching.md +7 -1
  15. data/docs/general/configuration_options.md +70 -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 +62 -24
  21. data/docs/general/serializers.md +121 -13
  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 +4 -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 +35 -12
  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 +296 -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 +1 -1
  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 +63 -23
  55. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +32 -9
  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 +15 -0
  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/errors_test.rb +8 -9
  78. data/test/action_controller/json_api/fields_test.rb +66 -0
  79. data/test/action_controller/json_api/linked_test.rb +29 -24
  80. data/test/action_controller/json_api/pagination_test.rb +19 -19
  81. data/test/action_controller/json_api/transform_test.rb +11 -3
  82. data/test/action_controller/lookup_proc_test.rb +49 -0
  83. data/test/action_controller/namespace_lookup_test.rb +232 -0
  84. data/test/action_controller/serialization_scope_name_test.rb +12 -6
  85. data/test/action_controller/serialization_test.rb +12 -9
  86. data/test/active_model_serializers/json_pointer_test.rb +15 -13
  87. data/test/active_model_serializers/model_test.rb +137 -4
  88. data/test/active_model_serializers/railtie_test_isolated.rb +12 -7
  89. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +161 -0
  90. data/test/active_model_serializers/serialization_context_test_isolated.rb +23 -10
  91. data/test/active_model_serializers/test/schema_test.rb +3 -2
  92. data/test/adapter/attributes_test.rb +40 -0
  93. data/test/adapter/json/collection_test.rb +14 -0
  94. data/test/adapter/json/has_many_test.rb +10 -2
  95. data/test/adapter/json/transform_test.rb +15 -15
  96. data/test/adapter/json_api/collection_test.rb +4 -3
  97. data/test/adapter/json_api/errors_test.rb +17 -19
  98. data/test/adapter/json_api/fields_test.rb +12 -3
  99. data/test/adapter/json_api/has_many_test.rb +49 -20
  100. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +183 -0
  101. data/test/adapter/json_api/json_api_test.rb +5 -7
  102. data/test/adapter/json_api/linked_test.rb +33 -12
  103. data/test/adapter/json_api/links_test.rb +4 -2
  104. data/test/adapter/json_api/pagination_links_test.rb +35 -8
  105. data/test/adapter/json_api/relationship_test.rb +309 -73
  106. data/test/adapter/json_api/resource_identifier_test.rb +27 -2
  107. data/test/adapter/json_api/resource_meta_test.rb +3 -3
  108. data/test/adapter/json_api/transform_test.rb +263 -253
  109. data/test/adapter/json_api/type_test.rb +1 -1
  110. data/test/adapter/json_test.rb +8 -7
  111. data/test/adapter/null_test.rb +1 -2
  112. data/test/adapter/polymorphic_test.rb +5 -5
  113. data/test/adapter_test.rb +1 -1
  114. data/test/benchmark/app.rb +1 -1
  115. data/test/benchmark/benchmarking_support.rb +1 -1
  116. data/test/benchmark/bm_active_record.rb +81 -0
  117. data/test/benchmark/bm_adapter.rb +38 -0
  118. data/test/benchmark/bm_caching.rb +16 -16
  119. data/test/benchmark/bm_lookup_chain.rb +83 -0
  120. data/test/benchmark/bm_transform.rb +21 -10
  121. data/test/benchmark/controllers.rb +16 -17
  122. data/test/benchmark/fixtures.rb +72 -72
  123. data/test/cache_test.rb +235 -69
  124. data/test/collection_serializer_test.rb +25 -12
  125. data/test/fixtures/active_record.rb +45 -10
  126. data/test/fixtures/poro.rb +124 -181
  127. data/test/generators/serializer_generator_test.rb +23 -5
  128. data/test/grape_test.rb +170 -56
  129. data/test/lint_test.rb +1 -1
  130. data/test/logger_test.rb +13 -11
  131. data/test/serializable_resource_test.rb +18 -22
  132. data/test/serializers/association_macros_test.rb +3 -2
  133. data/test/serializers/associations_test.rb +178 -49
  134. data/test/serializers/attribute_test.rb +5 -3
  135. data/test/serializers/attributes_test.rb +1 -1
  136. data/test/serializers/caching_configuration_test_isolated.rb +6 -6
  137. data/test/serializers/fieldset_test.rb +1 -1
  138. data/test/serializers/meta_test.rb +12 -6
  139. data/test/serializers/options_test.rb +17 -6
  140. data/test/serializers/read_attribute_for_serialization_test.rb +3 -3
  141. data/test/serializers/reflection_test.rb +427 -0
  142. data/test/serializers/root_test.rb +1 -1
  143. data/test/serializers/serialization_test.rb +2 -2
  144. data/test/serializers/serializer_for_test.rb +12 -10
  145. data/test/serializers/serializer_for_with_namespace_test.rb +88 -0
  146. data/test/support/isolated_unit.rb +5 -2
  147. data/test/support/rails5_shims.rb +8 -2
  148. data/test/support/rails_app.rb +2 -9
  149. data/test/support/serialization_testing.rb +23 -5
  150. data/test/test_helper.rb +13 -0
  151. metadata +105 -42
  152. data/.rubocop_todo.yml +0 -167
  153. data/docs/ARCHITECTURE.md +0 -126
  154. data/lib/active_model/serializer/associations.rb +0 -100
  155. data/lib/active_model/serializer/attributes.rb +0 -82
  156. data/lib/active_model/serializer/collection_reflection.rb +0 -7
  157. data/lib/active_model/serializer/configuration.rb +0 -35
  158. data/lib/active_model/serializer/include_tree.rb +0 -111
  159. data/lib/active_model/serializer/links.rb +0 -35
  160. data/lib/active_model/serializer/meta.rb +0 -29
  161. data/lib/active_model/serializer/singular_reflection.rb +0 -7
  162. data/lib/active_model/serializer/type.rb +0 -25
  163. data/lib/active_model_serializers/key_transform.rb +0 -70
  164. data/test/active_model_serializers/key_transform_test.rb +0 -263
  165. data/test/adapter/json_api/relationships_test.rb +0 -199
  166. data/test/include_tree/from_include_args_test.rb +0 -26
  167. data/test/include_tree/from_string_test.rb +0 -94
  168. 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,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
@@ -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
  }