active_model_serializers 0.10.0 → 0.10.9

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 (206) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +10 -5
  3. data/.travis.yml +41 -21
  4. data/CHANGELOG.md +200 -2
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +25 -4
  7. data/README.md +166 -28
  8. data/Rakefile +5 -32
  9. data/active_model_serializers.gemspec +23 -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 +137 -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 +15 -3
  34. data/lib/active_model/serializable_resource.rb +2 -0
  35. data/lib/active_model/serializer/adapter/attributes.rb +2 -0
  36. data/lib/active_model/serializer/adapter/base.rb +4 -0
  37. data/lib/active_model/serializer/adapter/json.rb +2 -0
  38. data/lib/active_model/serializer/adapter/json_api.rb +2 -0
  39. data/lib/active_model/serializer/adapter/null.rb +2 -0
  40. data/lib/active_model/serializer/adapter.rb +2 -0
  41. data/lib/active_model/serializer/array_serializer.rb +10 -5
  42. data/lib/active_model/serializer/association.rb +64 -10
  43. data/lib/active_model/serializer/attribute.rb +2 -0
  44. data/lib/active_model/serializer/belongs_to_reflection.rb +6 -3
  45. data/lib/active_model/serializer/collection_serializer.rb +39 -13
  46. data/lib/active_model/serializer/{caching.rb → concerns/caching.rb} +87 -116
  47. data/lib/active_model/serializer/error_serializer.rb +13 -7
  48. data/lib/active_model/serializer/errors_serializer.rb +27 -20
  49. data/lib/active_model/serializer/field.rb +2 -0
  50. data/lib/active_model/serializer/fieldset.rb +2 -0
  51. data/lib/active_model/serializer/has_many_reflection.rb +5 -3
  52. data/lib/active_model/serializer/has_one_reflection.rb +3 -4
  53. data/lib/active_model/serializer/lazy_association.rb +99 -0
  54. data/lib/active_model/serializer/link.rb +23 -0
  55. data/lib/active_model/serializer/lint.rb +136 -130
  56. data/lib/active_model/serializer/null.rb +2 -0
  57. data/lib/active_model/serializer/reflection.rb +132 -67
  58. data/lib/active_model/serializer/version.rb +3 -1
  59. data/lib/active_model/serializer.rb +308 -82
  60. data/lib/active_model_serializers/adapter/attributes.rb +5 -66
  61. data/lib/active_model_serializers/adapter/base.rb +41 -39
  62. data/lib/active_model_serializers/adapter/json.rb +2 -0
  63. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +4 -2
  64. data/lib/active_model_serializers/adapter/json_api/error.rb +2 -0
  65. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +2 -0
  66. data/lib/active_model_serializers/adapter/json_api/link.rb +3 -1
  67. data/lib/active_model_serializers/adapter/json_api/meta.rb +2 -0
  68. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +49 -21
  69. data/lib/active_model_serializers/adapter/json_api/relationship.rb +77 -23
  70. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +41 -10
  71. data/lib/active_model_serializers/adapter/json_api.rb +84 -65
  72. data/lib/active_model_serializers/adapter/null.rb +2 -0
  73. data/lib/active_model_serializers/adapter.rb +9 -1
  74. data/lib/active_model_serializers/callbacks.rb +2 -0
  75. data/lib/active_model_serializers/deprecate.rb +3 -2
  76. data/lib/active_model_serializers/deserialization.rb +4 -0
  77. data/lib/active_model_serializers/json_pointer.rb +2 -0
  78. data/lib/active_model_serializers/logging.rb +2 -0
  79. data/lib/active_model_serializers/lookup_chain.rb +82 -0
  80. data/lib/active_model_serializers/model.rb +111 -28
  81. data/lib/active_model_serializers/railtie.rb +7 -1
  82. data/lib/active_model_serializers/register_jsonapi_renderer.rb +46 -31
  83. data/lib/active_model_serializers/serializable_resource.rb +10 -7
  84. data/lib/active_model_serializers/serialization_context.rb +12 -3
  85. data/lib/active_model_serializers/test/schema.rb +4 -2
  86. data/lib/active_model_serializers/test/serializer.rb +2 -0
  87. data/lib/active_model_serializers/test.rb +2 -0
  88. data/lib/active_model_serializers.rb +35 -10
  89. data/lib/generators/rails/resource_override.rb +3 -1
  90. data/lib/generators/rails/serializer_generator.rb +6 -4
  91. data/lib/grape/active_model_serializers.rb +9 -5
  92. data/lib/grape/formatters/active_model_serializers.rb +21 -2
  93. data/lib/grape/helpers/active_model_serializers.rb +3 -0
  94. data/lib/tasks/rubocop.rake +55 -0
  95. data/test/action_controller/adapter_selector_test.rb +16 -5
  96. data/test/action_controller/explicit_serializer_test.rb +7 -4
  97. data/test/action_controller/json/include_test.rb +108 -27
  98. data/test/action_controller/json_api/deserialization_test.rb +3 -1
  99. data/test/action_controller/json_api/errors_test.rb +10 -9
  100. data/test/action_controller/json_api/fields_test.rb +68 -0
  101. data/test/action_controller/json_api/linked_test.rb +31 -24
  102. data/test/action_controller/json_api/pagination_test.rb +33 -23
  103. data/test/action_controller/json_api/transform_test.rb +13 -3
  104. data/test/action_controller/lookup_proc_test.rb +51 -0
  105. data/test/action_controller/namespace_lookup_test.rb +234 -0
  106. data/test/action_controller/serialization_scope_name_test.rb +14 -6
  107. data/test/action_controller/serialization_test.rb +23 -12
  108. data/test/active_model_serializers/adapter_for_test.rb +2 -0
  109. data/test/active_model_serializers/json_pointer_test.rb +17 -13
  110. data/test/active_model_serializers/logging_test.rb +2 -0
  111. data/test/active_model_serializers/model_test.rb +139 -4
  112. data/test/active_model_serializers/railtie_test_isolated.rb +14 -7
  113. data/test/active_model_serializers/register_jsonapi_renderer_test_isolated.rb +163 -0
  114. data/test/active_model_serializers/serialization_context_test_isolated.rb +25 -10
  115. data/test/active_model_serializers/test/schema_test.rb +5 -2
  116. data/test/active_model_serializers/test/serializer_test.rb +2 -0
  117. data/test/active_record_test.rb +2 -0
  118. data/test/adapter/attributes_test.rb +42 -0
  119. data/test/adapter/deprecation_test.rb +2 -0
  120. data/test/adapter/json/belongs_to_test.rb +2 -0
  121. data/test/adapter/json/collection_test.rb +16 -0
  122. data/test/adapter/json/has_many_test.rb +12 -2
  123. data/test/adapter/json/transform_test.rb +17 -15
  124. data/test/adapter/json_api/belongs_to_test.rb +2 -0
  125. data/test/adapter/json_api/collection_test.rb +6 -3
  126. data/test/adapter/json_api/errors_test.rb +19 -19
  127. data/test/adapter/json_api/fields_test.rb +14 -3
  128. data/test/adapter/json_api/has_many_explicit_serializer_test.rb +2 -0
  129. data/test/adapter/json_api/has_many_test.rb +51 -20
  130. data/test/adapter/json_api/has_one_test.rb +2 -0
  131. data/test/adapter/json_api/include_data_if_sideloaded_test.rb +215 -0
  132. data/test/adapter/json_api/json_api_test.rb +7 -7
  133. data/test/adapter/json_api/linked_test.rb +35 -12
  134. data/test/adapter/json_api/links_test.rb +22 -3
  135. data/test/adapter/json_api/pagination_links_test.rb +55 -13
  136. data/test/adapter/json_api/parse_test.rb +3 -1
  137. data/test/adapter/json_api/relationship_test.rb +311 -73
  138. data/test/adapter/json_api/resource_meta_test.rb +5 -3
  139. data/test/adapter/json_api/toplevel_jsonapi_test.rb +2 -0
  140. data/test/adapter/json_api/transform_test.rb +265 -253
  141. data/test/adapter/json_api/type_test.rb +170 -36
  142. data/test/adapter/json_test.rb +10 -7
  143. data/test/adapter/null_test.rb +3 -2
  144. data/test/adapter/polymorphic_test.rb +54 -5
  145. data/test/adapter_test.rb +3 -1
  146. data/test/array_serializer_test.rb +2 -0
  147. data/test/benchmark/app.rb +3 -1
  148. data/test/benchmark/benchmarking_support.rb +3 -1
  149. data/test/benchmark/bm_active_record.rb +83 -0
  150. data/test/benchmark/bm_adapter.rb +40 -0
  151. data/test/benchmark/bm_caching.rb +18 -16
  152. data/test/benchmark/bm_lookup_chain.rb +85 -0
  153. data/test/benchmark/bm_transform.rb +23 -10
  154. data/test/benchmark/controllers.rb +18 -17
  155. data/test/benchmark/fixtures.rb +74 -72
  156. data/test/cache_test.rb +301 -69
  157. data/test/collection_serializer_test.rb +33 -14
  158. data/test/fixtures/active_record.rb +47 -10
  159. data/test/fixtures/poro.rb +128 -183
  160. data/test/generators/scaffold_controller_generator_test.rb +2 -0
  161. data/test/generators/serializer_generator_test.rb +25 -5
  162. data/test/grape_test.rb +172 -56
  163. data/test/lint_test.rb +3 -1
  164. data/test/logger_test.rb +15 -11
  165. data/test/poro_test.rb +2 -0
  166. data/test/serializable_resource_test.rb +20 -22
  167. data/test/serializers/association_macros_test.rb +5 -2
  168. data/test/serializers/associations_test.rb +274 -49
  169. data/test/serializers/attribute_test.rb +7 -3
  170. data/test/serializers/attributes_test.rb +3 -1
  171. data/test/serializers/caching_configuration_test_isolated.rb +8 -6
  172. data/test/serializers/configuration_test.rb +2 -0
  173. data/test/serializers/fieldset_test.rb +3 -1
  174. data/test/serializers/meta_test.rb +14 -6
  175. data/test/serializers/options_test.rb +19 -6
  176. data/test/serializers/read_attribute_for_serialization_test.rb +5 -3
  177. data/test/serializers/reflection_test.rb +481 -0
  178. data/test/serializers/root_test.rb +3 -1
  179. data/test/serializers/serialization_test.rb +4 -2
  180. data/test/serializers/serializer_for_test.rb +14 -10
  181. data/test/serializers/serializer_for_with_namespace_test.rb +90 -0
  182. data/test/support/isolated_unit.rb +11 -4
  183. data/test/support/rails5_shims.rb +10 -2
  184. data/test/support/rails_app.rb +4 -9
  185. data/test/support/serialization_testing.rb +33 -5
  186. data/test/test_helper.rb +15 -0
  187. metadata +126 -46
  188. data/.rubocop_todo.yml +0 -167
  189. data/docs/ARCHITECTURE.md +0 -126
  190. data/lib/active_model/serializer/associations.rb +0 -100
  191. data/lib/active_model/serializer/attributes.rb +0 -82
  192. data/lib/active_model/serializer/collection_reflection.rb +0 -7
  193. data/lib/active_model/serializer/configuration.rb +0 -35
  194. data/lib/active_model/serializer/include_tree.rb +0 -111
  195. data/lib/active_model/serializer/links.rb +0 -35
  196. data/lib/active_model/serializer/meta.rb +0 -29
  197. data/lib/active_model/serializer/singular_reflection.rb +0 -7
  198. data/lib/active_model/serializer/type.rb +0 -25
  199. data/lib/active_model_serializers/key_transform.rb +0 -70
  200. data/test/active_model_serializers/key_transform_test.rb +0 -263
  201. data/test/adapter/json_api/has_many_embed_ids_test.rb +0 -43
  202. data/test/adapter/json_api/relationships_test.rb +0 -199
  203. data/test/adapter/json_api/resource_identifier_test.rb +0 -85
  204. data/test/include_tree/from_include_args_test.rb +0 -26
  205. data/test/include_tree/from_string_test.rb +0 -94
  206. data/test/include_tree/include_args_to_hash_test.rb +0 -64
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ##
2
4
  # Provides a single method +deprecate+ to be used to declare when
3
5
  # something is going away.
@@ -36,8 +38,7 @@ module ActiveModelSerializers
36
38
  target = is_a?(Module) ? "#{self}." : "#{self.class}#"
37
39
  msg = ["NOTE: #{target}#{name} is deprecated",
38
40
  replacement == :none ? ' with no replacement' : "; use #{replacement} instead",
39
- "\n#{target}#{name} called from #{ActiveModelSerializers.location_of_caller.join(":")}"
40
- ]
41
+ "\n#{target}#{name} called from #{ActiveModelSerializers.location_of_caller.join(':')}"]
41
42
  warn "#{msg.join}."
42
43
  send old, *args, &block
43
44
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Deserialization
3
5
  module_function
@@ -6,8 +8,10 @@ module ActiveModelSerializers
6
8
  Adapter::JsonApi::Deserialization.parse(*args)
7
9
  end
8
10
 
11
+ # :nocov:
9
12
  def jsonapi_parse!(*args)
10
13
  Adapter::JsonApi::Deserialization.parse!(*args)
11
14
  end
15
+ # :nocov:
12
16
  end
13
17
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module JsonPointer
3
5
  module_function
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  ##
2
4
  # ActiveModelSerializers::Logging
3
5
  #
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModelSerializers
4
+ module LookupChain
5
+ # Standard appending of Serializer to the resource name.
6
+ #
7
+ # Example:
8
+ # Author => AuthorSerializer
9
+ BY_RESOURCE = lambda do |resource_class, _serializer_class, _namespace|
10
+ serializer_from(resource_class)
11
+ end
12
+
13
+ # Uses the namespace of the resource to find the serializer
14
+ #
15
+ # Example:
16
+ # British::Author => British::AuthorSerializer
17
+ BY_RESOURCE_NAMESPACE = lambda do |resource_class, _serializer_class, _namespace|
18
+ resource_namespace = namespace_for(resource_class)
19
+ serializer_name = serializer_from(resource_class)
20
+
21
+ "#{resource_namespace}::#{serializer_name}"
22
+ end
23
+
24
+ # Uses the controller namespace of the resource to find the serializer
25
+ #
26
+ # Example:
27
+ # Api::V3::AuthorsController => Api::V3::AuthorSerializer
28
+ BY_NAMESPACE = lambda do |resource_class, _serializer_class, namespace|
29
+ resource_name = resource_class_name(resource_class)
30
+ namespace ? "#{namespace}::#{resource_name}Serializer" : nil
31
+ end
32
+
33
+ # Allows for serializers to be defined in parent serializers
34
+ # - useful if a relationship only needs a different set of attributes
35
+ # than if it were rendered independently.
36
+ #
37
+ # Example:
38
+ # class BlogSerializer < ActiveModel::Serializer
39
+ # class AuthorSerialier < ActiveModel::Serializer
40
+ # ...
41
+ # end
42
+ #
43
+ # belongs_to :author
44
+ # ...
45
+ # end
46
+ #
47
+ # The belongs_to relationship would be rendered with
48
+ # BlogSerializer::AuthorSerialier
49
+ BY_PARENT_SERIALIZER = lambda do |resource_class, serializer_class, _namespace|
50
+ return if serializer_class == ActiveModel::Serializer
51
+
52
+ serializer_name = serializer_from(resource_class)
53
+ "#{serializer_class}::#{serializer_name}"
54
+ end
55
+
56
+ DEFAULT = [
57
+ BY_PARENT_SERIALIZER,
58
+ BY_NAMESPACE,
59
+ BY_RESOURCE_NAMESPACE,
60
+ BY_RESOURCE
61
+ ].freeze
62
+
63
+ module_function
64
+
65
+ def namespace_for(klass)
66
+ klass.name.deconstantize
67
+ end
68
+
69
+ def resource_class_name(klass)
70
+ klass.name.demodulize
71
+ end
72
+
73
+ def serializer_from_resource_name(name)
74
+ "#{name}Serializer"
75
+ end
76
+
77
+ def serializer_from(klass)
78
+ name = resource_class_name(klass)
79
+ serializer_from_resource_name(name)
80
+ end
81
+ end
82
+ end
@@ -1,49 +1,132 @@
1
- # ActiveModelSerializers::Model is a convenient
2
- # serializable class to inherit from when making
3
- # serializable non-activerecord objects.
1
+ # frozen_string_literal: true
2
+
3
+ # ActiveModelSerializers::Model is a convenient superclass for making your models
4
+ # from Plain-Old Ruby Objects (PORO). It also serves as a reference implementation
5
+ # that satisfies ActiveModel::Serializer::Lint::Tests.
6
+ require 'active_support/core_ext/hash'
4
7
  module ActiveModelSerializers
5
8
  class Model
6
- include ActiveModel::Model
7
9
  include ActiveModel::Serializers::JSON
10
+ include ActiveModel::Model
8
11
 
9
- attr_reader :attributes, :errors
12
+ # Declare names of attributes to be included in +attributes+ hash.
13
+ # Is only available as a class-method since the ActiveModel::Serialization mixin in Rails
14
+ # uses an +attribute_names+ local variable, which may conflict if we were to add instance methods here.
15
+ #
16
+ # @overload attribute_names
17
+ # @return [Array<Symbol>]
18
+ class_attribute :attribute_names, instance_writer: false, instance_reader: false
19
+ # Initialize +attribute_names+ for all subclasses. The array is usually
20
+ # mutated in the +attributes+ method, but can be set directly, as well.
21
+ self.attribute_names = []
10
22
 
11
- def initialize(attributes = {})
12
- @attributes = attributes
13
- @errors = ActiveModel::Errors.new(self)
14
- super
23
+ # Easily declare instance attributes with setters and getters for each.
24
+ #
25
+ # To initialize an instance, all attributes must have setters.
26
+ # However, the hash returned by +attributes+ instance method will ALWAYS
27
+ # be the value of the initial attributes, regardless of what accessors are defined.
28
+ # The only way to change the change the attributes after initialization is
29
+ # to mutate the +attributes+ directly.
30
+ # Accessor methods do NOT mutate the attributes. (This is a bug).
31
+ #
32
+ # @note For now, the Model only supports the notion of 'attributes'.
33
+ # In the tests, there is a special Model that also supports 'associations'. This is
34
+ # important so that we can add accessors for values that should not appear in the
35
+ # attributes hash when modeling associations. It is not yet clear if it
36
+ # makes sense for a PORO to have associations outside of the tests.
37
+ #
38
+ # @overload attributes(names)
39
+ # @param names [Array<String, Symbol>]
40
+ # @param name [String, Symbol]
41
+ def self.attributes(*names)
42
+ self.attribute_names |= names.map(&:to_sym)
43
+ # Silence redefinition of methods warnings
44
+ ActiveModelSerializers.silence_warnings do
45
+ attr_accessor(*names)
46
+ end
15
47
  end
16
48
 
17
- # Defaults to the downcased model name.
18
- def id
19
- attributes.fetch(:id) { self.class.name.downcase }
49
+ # Opt-in to breaking change
50
+ def self.derive_attributes_from_names_and_fix_accessors
51
+ unless included_modules.include?(DeriveAttributesFromNamesAndFixAccessors)
52
+ prepend(DeriveAttributesFromNamesAndFixAccessors)
53
+ end
20
54
  end
21
55
 
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")}" }
56
+ module DeriveAttributesFromNamesAndFixAccessors
57
+ def self.included(base)
58
+ # NOTE that +id+ will always be in +attributes+.
59
+ base.attributes :id
60
+ end
61
+
62
+ # Override the +attributes+ method so that the hash is derived from +attribute_names+.
63
+ #
64
+ # The fields in +attribute_names+ determines the returned hash.
65
+ # +attributes+ are returned frozen to prevent any expectations that mutation affects
66
+ # the actual values in the model.
67
+ def attributes
68
+ self.class.attribute_names.each_with_object({}) do |attribute_name, result|
69
+ result[attribute_name] = public_send(attribute_name).freeze
70
+ end.with_indifferent_access.freeze
71
+ end
25
72
  end
26
73
 
27
- # Defaults to the time the serializer file was modified.
28
- def updated_at
29
- attributes.fetch(:updated_at) { File.mtime(__FILE__) }
74
+ # Support for validation and other ActiveModel::Errors
75
+ # @return [ActiveModel::Errors]
76
+ attr_reader :errors
77
+
78
+ # (see #updated_at)
79
+ attr_writer :updated_at
80
+
81
+ # The only way to change the attributes of an instance is to directly mutate the attributes.
82
+ # @example
83
+ #
84
+ # model.attributes[:foo] = :bar
85
+ # @return [Hash]
86
+ attr_reader :attributes
87
+
88
+ # @param attributes [Hash]
89
+ def initialize(attributes = {})
90
+ attributes ||= {} # protect against nil
91
+ @attributes = attributes.symbolize_keys.with_indifferent_access
92
+ @errors = ActiveModel::Errors.new(self)
93
+ super
30
94
  end
31
95
 
32
- def read_attribute_for_serialization(key)
33
- if key == :id || key == 'id'
34
- attributes.fetch(key) { id }
35
- else
36
- attributes[key]
96
+ # Defaults to the downcased model name.
97
+ # This probably isn't a good default, since it's not a unique instance identifier,
98
+ # but that's what is currently implemented \_('-')_/.
99
+ #
100
+ # @note Though +id+ is defined, it will only show up
101
+ # in +attributes+ when it is passed in to the initializer or added to +attributes+,
102
+ # such as <tt>attributes[:id] = 5</tt>.
103
+ # @return [String, Numeric, Symbol]
104
+ def id
105
+ attributes.fetch(:id) do
106
+ defined?(@id) ? @id : self.class.model_name.name && self.class.model_name.name.downcase
37
107
  end
38
108
  end
39
109
 
40
- # The following methods are needed to be minimally implemented for ActiveModel::Errors
41
- def self.human_attribute_name(attr, _options = {})
42
- attr
110
+ # When not set, defaults to the time the file was modified.
111
+ #
112
+ # @note Though +updated_at+ and +updated_at=+ are defined, it will only show up
113
+ # in +attributes+ when it is passed in to the initializer or added to +attributes+,
114
+ # such as <tt>attributes[:updated_at] = Time.current</tt>.
115
+ # @return [String, Numeric, Time]
116
+ def updated_at
117
+ attributes.fetch(:updated_at) do
118
+ defined?(@updated_at) ? @updated_at : File.mtime(__FILE__)
119
+ end
43
120
  end
44
121
 
45
- def self.lookup_ancestors
46
- [self]
122
+ # To customize model behavior, this method must be redefined. However,
123
+ # there are other ways of setting the +cache_key+ a serializer uses.
124
+ # @return [String]
125
+ def cache_key
126
+ ActiveSupport::Cache.expand_cache_key([
127
+ self.class.model_name.name.downcase,
128
+ "#{id}-#{updated_at.strftime('%Y%m%d%H%M%S%9N')}"
129
+ ].compact)
47
130
  end
48
131
  end
49
132
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails/railtie'
2
4
  require 'action_controller'
3
5
  require 'action_controller/railtie'
@@ -5,6 +7,8 @@ require 'action_controller/serialization'
5
7
 
6
8
  module ActiveModelSerializers
7
9
  class Railtie < Rails::Railtie
10
+ config.eager_load_namespaces << ActiveModelSerializers
11
+
8
12
  config.to_prepare do
9
13
  ActiveModel::Serializer.serializers_cache.clear
10
14
  end
@@ -23,7 +27,7 @@ module ActiveModelSerializers
23
27
  # This hook is run after the action_controller railtie has set the configuration
24
28
  # based on the *environment* configuration and before any config/initializers are run
25
29
  # and also before eager_loading (if enabled).
26
- initializer 'active_model_serializers.set_configs', :after => 'action_controller.set_configs' do
30
+ initializer 'active_model_serializers.set_configs', after: 'action_controller.set_configs' do
27
31
  ActiveModelSerializers.logger = Rails.configuration.action_controller.logger
28
32
  ActiveModelSerializers.config.perform_caching = Rails.configuration.action_controller.perform_caching
29
33
  # We want this hook to run after the config has been set, even if ActionController has already loaded.
@@ -32,11 +36,13 @@ module ActiveModelSerializers
32
36
  end
33
37
  end
34
38
 
39
+ # :nocov:
35
40
  generators do |app|
36
41
  Rails::Generators.configure!(app.config.generators)
37
42
  Rails::Generators.hidden_namespaces.uniq!
38
43
  require 'generators/rails/resource_override'
39
44
  end
45
+ # :nocov:
40
46
 
41
47
  if Rails.env.test?
42
48
  ActionController::TestCase.send(:include, ActiveModelSerializers::Test::Schema)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Based on discussion in https://github.com/rails/rails/pull/23712#issuecomment-184977238,
2
4
  # the JSON API media type will have its own format/renderer.
3
5
  #
@@ -22,43 +24,56 @@
22
24
  # render jsonapi: model
23
25
  #
24
26
  # No wrapper format needed as it does not apply (i.e. no `wrap_parameters format: [jsonapi]`)
27
+ module ActiveModelSerializers
28
+ module Jsonapi
29
+ MEDIA_TYPE = 'application/vnd.api+json'.freeze
30
+ HEADERS = {
31
+ response: { 'CONTENT_TYPE'.freeze => MEDIA_TYPE },
32
+ request: { 'ACCEPT'.freeze => MEDIA_TYPE }
33
+ }.freeze
25
34
 
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
35
+ def self.install
36
+ # actionpack/lib/action_dispatch/http/mime_types.rb
37
+ Mime::Type.register MEDIA_TYPE, :jsonapi
38
+
39
+ if Rails::VERSION::MAJOR >= 5
40
+ ActionDispatch::Request.parameter_parsers[:jsonapi] = parser
41
+ else
42
+ ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime[:jsonapi]] = parser
43
+ end
40
44
 
41
- # actionpack/lib/action_dispatch/http/mime_types.rb
42
- Mime::Type.register ActiveModelSerializers::Jsonapi::MEDIA_TYPE, :jsonapi
45
+ # ref https://github.com/rails/rails/pull/21496
46
+ ActionController::Renderers.add :jsonapi do |json, options|
47
+ json = serialize_jsonapi(json, options).to_json(options) unless json.is_a?(String)
48
+ self.content_type ||= Mime[:jsonapi]
49
+ self.response_body = json
50
+ end
51
+ end
43
52
 
44
- parsers = Rails::VERSION::MAJOR >= 5 ? ActionDispatch::Http::Parameters : ActionDispatch::ParamsParser
45
- media_type = Mime::Type.lookup(ActiveModelSerializers::Jsonapi::MEDIA_TYPE)
53
+ # Proposal: should actually deserialize the JSON API params
54
+ # to the hash format expected by `ActiveModel::Serializers::JSON`
55
+ # actionpack/lib/action_dispatch/http/parameters.rb
56
+ def self.parser
57
+ lambda do |body|
58
+ data = JSON.parse(body)
59
+ data = { _json: data } unless data.is_a?(Hash)
60
+ data.with_indifferent_access
61
+ end
62
+ end
46
63
 
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
64
+ module ControllerSupport
65
+ def serialize_jsonapi(json, options)
66
+ options[:adapter] = :json_api
67
+ options.fetch(:serialization_context) do
68
+ options[:serialization_context] = ActiveModelSerializers::SerializationContext.new(request)
69
+ end
70
+ get_serializer(json, options)
71
+ end
72
+ end
73
+ end
54
74
  end
55
75
 
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
61
- end
76
+ ActiveModelSerializers::Jsonapi.install
62
77
 
63
78
  ActiveSupport.on_load(:action_controller) do
64
79
  include ActiveModelSerializers::Jsonapi::ControllerSupport
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  module ActiveModelSerializers
@@ -14,8 +16,8 @@ module ActiveModelSerializers
14
16
  # @return the serializable_resource, ready for #as_json/#to_json/#serializable_hash.
15
17
  def initialize(resource, options = {})
16
18
  @resource = resource
17
- @adapter_opts, @serializer_opts =
18
- options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
19
+ @adapter_opts = options.select { |k, _| ADAPTER_OPTION_KEYS.include? k }
20
+ @serializer_opts = options.reject { |k, _| ADAPTER_OPTION_KEYS.include? k }
19
21
  end
20
22
 
21
23
  def serialization_scope=(scope)
@@ -38,9 +40,10 @@ module ActiveModelSerializers
38
40
 
39
41
  def find_adapter
40
42
  return resource unless serializer?
41
- ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts)
42
- rescue ActiveModel::Serializer::CollectionSerializer::NoSerializerError
43
- resource
43
+ adapter = catch :no_serializer do
44
+ ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts)
45
+ end
46
+ adapter || resource
44
47
  end
45
48
 
46
49
  def serializer_instance
@@ -49,12 +52,12 @@ module ActiveModelSerializers
49
52
 
50
53
  # Get serializer either explicitly :serializer or implicitly from resource
51
54
  # Remove :serializer key from serializer_opts
52
- # Replace :serializer key with :each_serializer if present
55
+ # Remove :each_serializer if present and set as :serializer key
53
56
  def serializer
54
57
  @serializer ||=
55
58
  begin
56
59
  @serializer = serializer_opts.delete(:serializer)
57
- @serializer ||= ActiveModel::Serializer.serializer_for(resource)
60
+ @serializer ||= ActiveModel::Serializer.serializer_for(resource, serializer_opts)
58
61
 
59
62
  if serializer_opts.key?(:each_serializer)
60
63
  serializer_opts[:serializer] = serializer_opts.delete(:each_serializer)
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/array/extract_options'
1
4
  module ActiveModelSerializers
2
5
  class SerializationContext
3
6
  class << self
@@ -22,9 +25,15 @@ module ActiveModelSerializers
22
25
 
23
26
  attr_reader :request_url, :query_parameters, :key_transform
24
27
 
25
- def initialize(request, options = {})
26
- @request_url = request.original_url[/\A[^?]+/]
27
- @query_parameters = request.query_parameters
28
+ def initialize(*args)
29
+ options = args.extract_options!
30
+ if args.size == 1
31
+ request = args.pop
32
+ options[:request_url] = request.original_url[/\A[^?]+/]
33
+ options[:query_parameters] = request.query_parameters
34
+ end
35
+ @request_url = options.delete(:request_url)
36
+ @query_parameters = options.delete(:query_parameters)
28
37
  @url_helpers = options.delete(:url_helpers) || self.class.url_helpers
29
38
  @default_url_options = options.delete(:default_url_options) || self.class.default_url_options
30
39
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Test
3
5
  module Schema
@@ -60,11 +62,11 @@ module ActiveModelSerializers
60
62
  attr_reader :document_store
61
63
 
62
64
  def controller_path
63
- request.filtered_parameters[:controller]
65
+ request.filtered_parameters.with_indifferent_access[:controller]
64
66
  end
65
67
 
66
68
  def action
67
- request.filtered_parameters[:action]
69
+ request.filtered_parameters.with_indifferent_access[:action]
68
70
  end
69
71
 
70
72
  def schema_directory
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
  module ActiveModelSerializers
3
5
  module Test
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModelSerializers
2
4
  module Test
3
5
  extend ActiveSupport::Autoload
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_model'
2
4
  require 'active_support'
3
5
  require 'active_support/core_ext/object/with_options'
@@ -5,15 +7,19 @@ require 'active_support/core_ext/string/inflections'
5
7
  require 'active_support/json'
6
8
  module ActiveModelSerializers
7
9
  extend ActiveSupport::Autoload
8
- autoload :Model
9
- autoload :Callbacks
10
- autoload :Deserialization
11
- autoload :SerializableResource
12
- autoload :Logging
13
- autoload :Test
14
- autoload :Adapter
15
- autoload :JsonPointer
16
- autoload :Deprecate
10
+ eager_autoload do
11
+ autoload :Model
12
+ autoload :Callbacks
13
+ autoload :SerializableResource
14
+ autoload :SerializationContext
15
+ autoload :Logging
16
+ autoload :Test
17
+ autoload :Adapter
18
+ autoload :JsonPointer
19
+ autoload :Deprecate
20
+ autoload :LookupChain
21
+ autoload :Deserialization
22
+ end
17
23
 
18
24
  class << self; attr_accessor :logger; end
19
25
  self.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
@@ -31,8 +37,27 @@ module ActiveModelSerializers
31
37
  [file, lineno]
32
38
  end
33
39
 
40
+ # Memoized default include directive
41
+ # @return [JSONAPI::IncludeDirective]
42
+ def self.default_include_directive
43
+ @default_include_directive ||= JSONAPI::IncludeDirective.new(config.default_includes, allow_wildcard: true)
44
+ end
45
+
46
+ def self.silence_warnings
47
+ original_verbose = $VERBOSE
48
+ $VERBOSE = nil
49
+ yield
50
+ ensure
51
+ $VERBOSE = original_verbose
52
+ end
53
+
54
+ def self.eager_load!
55
+ super
56
+ ActiveModel::Serializer.eager_load!
57
+ end
58
+
34
59
  require 'active_model/serializer/version'
35
60
  require 'active_model/serializer'
36
61
  require 'active_model/serializable_resource'
37
- require 'active_model_serializers/railtie' if defined?(::Rails)
62
+ require 'active_model_serializers/railtie' if defined?(::Rails::Railtie)
38
63
  end
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rails/generators'
2
4
  require 'rails/generators/rails/resource/resource_generator'
3
5
 
4
6
  module Rails
5
7
  module Generators
6
8
  class ResourceGenerator
7
- hook_for :serializer, default: true, boolean: true
9
+ hook_for :serializer, default: true, type: :boolean
8
10
  end
9
11
  end
10
12
  end
@@ -1,12 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rails
2
4
  module Generators
3
5
  class SerializerGenerator < NamedBase
4
6
  source_root File.expand_path('../templates', __FILE__)
5
- check_class_collision :suffix => 'Serializer'
7
+ check_class_collision suffix: 'Serializer'
6
8
 
7
- argument :attributes, :type => :array, :default => [], :banner => 'field:type field:type'
9
+ argument :attributes, type: :array, default: [], banner: 'field:type field:type'
8
10
 
9
- class_option :parent, :type => :string, :desc => 'The parent class for the generated serializer'
11
+ class_option :parent, type: :string, desc: 'The parent class for the generated serializer'
10
12
 
11
13
  def create_serializer_file
12
14
  template 'serializer.rb.erb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb")
@@ -25,7 +27,7 @@ module Rails
25
27
  def parent_class_name
26
28
  if options[:parent]
27
29
  options[:parent]
28
- elsif defined?(::ApplicationSerializer)
30
+ elsif 'ApplicationSerializer'.safe_constantize
29
31
  'ApplicationSerializer'
30
32
  else
31
33
  'ActiveModel::Serializer'
@@ -1,14 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # To add Grape support, require 'grape/active_model_serializers' in the base of your Grape endpoints
2
4
  # Then add 'include Grape::ActiveModelSerializers' to enable the formatter and helpers
3
5
  require 'active_model_serializers'
4
6
  require 'grape/formatters/active_model_serializers'
5
7
  require 'grape/helpers/active_model_serializers'
6
8
 
7
- module Grape::ActiveModelSerializers
8
- extend ActiveSupport::Concern
9
+ module Grape
10
+ module ActiveModelSerializers
11
+ extend ActiveSupport::Concern
9
12
 
10
- included do
11
- formatter :json, Grape::Formatters::ActiveModelSerializers
12
- helpers Grape::Helpers::ActiveModelSerializers
13
+ included do
14
+ formatter :json, Grape::Formatters::ActiveModelSerializers
15
+ helpers Grape::Helpers::ActiveModelSerializers
16
+ end
13
17
  end
14
18
  end