active_model_serializers 0.9.12 → 0.10.15

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 (142) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +638 -82
  3. data/MIT-LICENSE +3 -2
  4. data/README.md +194 -846
  5. data/lib/action_controller/serialization.rb +34 -74
  6. data/lib/active_model/serializable_resource.rb +13 -0
  7. data/lib/active_model/serializer/adapter/attributes.rb +17 -0
  8. data/lib/active_model/serializer/adapter/base.rb +20 -0
  9. data/lib/active_model/serializer/adapter/json.rb +17 -0
  10. data/lib/active_model/serializer/adapter/json_api.rb +17 -0
  11. data/lib/active_model/serializer/adapter/null.rb +17 -0
  12. data/lib/active_model/serializer/adapter.rb +26 -0
  13. data/lib/active_model/serializer/array_serializer.rb +14 -0
  14. data/lib/active_model/serializer/association.rb +53 -38
  15. data/lib/active_model/serializer/attribute.rb +27 -0
  16. data/lib/active_model/serializer/belongs_to_reflection.rb +13 -0
  17. data/lib/active_model/serializer/collection_serializer.rb +99 -0
  18. data/lib/active_model/serializer/concerns/caching.rb +305 -0
  19. data/lib/active_model/serializer/error_serializer.rb +16 -0
  20. data/lib/active_model/serializer/errors_serializer.rb +34 -0
  21. data/lib/active_model/serializer/field.rb +92 -0
  22. data/lib/active_model/serializer/fieldset.rb +33 -0
  23. data/lib/active_model/serializer/has_many_reflection.rb +12 -0
  24. data/lib/active_model/serializer/has_one_reflection.rb +9 -0
  25. data/lib/active_model/serializer/lazy_association.rb +99 -0
  26. data/lib/active_model/serializer/link.rb +23 -0
  27. data/lib/active_model/serializer/lint.rb +152 -0
  28. data/lib/active_model/serializer/null.rb +19 -0
  29. data/lib/active_model/serializer/reflection.rb +212 -0
  30. data/lib/active_model/serializer/version.rb +1 -1
  31. data/lib/active_model/serializer.rb +361 -263
  32. data/lib/active_model_serializers/adapter/attributes.rb +36 -0
  33. data/lib/active_model_serializers/adapter/base.rb +85 -0
  34. data/lib/active_model_serializers/adapter/json.rb +23 -0
  35. data/lib/active_model_serializers/adapter/json_api/deserialization.rb +215 -0
  36. data/lib/active_model_serializers/adapter/json_api/error.rb +98 -0
  37. data/lib/active_model_serializers/adapter/json_api/jsonapi.rb +51 -0
  38. data/lib/active_model_serializers/adapter/json_api/link.rb +85 -0
  39. data/lib/active_model_serializers/adapter/json_api/meta.rb +39 -0
  40. data/lib/active_model_serializers/adapter/json_api/pagination_links.rb +94 -0
  41. data/lib/active_model_serializers/adapter/json_api/relationship.rb +106 -0
  42. data/lib/active_model_serializers/adapter/json_api/resource_identifier.rb +68 -0
  43. data/lib/active_model_serializers/adapter/json_api.rb +535 -0
  44. data/lib/active_model_serializers/adapter/null.rb +11 -0
  45. data/lib/active_model_serializers/adapter.rb +100 -0
  46. data/lib/active_model_serializers/callbacks.rb +57 -0
  47. data/lib/active_model_serializers/deprecate.rb +56 -0
  48. data/lib/active_model_serializers/deserialization.rb +17 -0
  49. data/lib/active_model_serializers/json_pointer.rb +16 -0
  50. data/lib/active_model_serializers/logging.rb +124 -0
  51. data/lib/active_model_serializers/lookup_chain.rb +82 -0
  52. data/lib/active_model_serializers/model.rb +132 -0
  53. data/lib/active_model_serializers/railtie.rb +62 -0
  54. data/lib/active_model_serializers/register_jsonapi_renderer.rb +80 -0
  55. data/lib/active_model_serializers/serializable_resource.rb +84 -0
  56. data/lib/active_model_serializers/serialization_context.rb +41 -0
  57. data/lib/active_model_serializers/test/schema.rb +140 -0
  58. data/lib/active_model_serializers/test/serializer.rb +127 -0
  59. data/lib/active_model_serializers/test.rb +9 -0
  60. data/lib/active_model_serializers.rb +58 -27
  61. data/lib/generators/rails/USAGE +6 -0
  62. data/lib/{active_model/serializer/generators → generators/rails}/resource_override.rb +1 -4
  63. data/lib/{active_model/serializer/generators/serializer → generators/rails}/serializer_generator.rb +4 -5
  64. data/lib/grape/active_model_serializers.rb +18 -0
  65. data/lib/grape/formatters/active_model_serializers.rb +34 -0
  66. data/lib/grape/helpers/active_model_serializers.rb +19 -0
  67. data/lib/tasks/rubocop.rake +60 -0
  68. metadata +248 -155
  69. data/CONTRIBUTING.md +0 -20
  70. data/DESIGN.textile +0 -586
  71. data/lib/action_controller/serialization_test_case.rb +0 -82
  72. data/lib/active_model/array_serializer.rb +0 -70
  73. data/lib/active_model/default_serializer.rb +0 -30
  74. data/lib/active_model/serializable/utils.rb +0 -18
  75. data/lib/active_model/serializable.rb +0 -61
  76. data/lib/active_model/serializer/association/has_many.rb +0 -41
  77. data/lib/active_model/serializer/association/has_one.rb +0 -27
  78. data/lib/active_model/serializer/config.rb +0 -33
  79. data/lib/active_model/serializer/generators/serializer/USAGE +0 -9
  80. data/lib/active_model/serializer/generators/serializer/scaffold_controller_generator.rb +0 -16
  81. data/lib/active_model/serializer/generators/serializer/templates/controller.rb +0 -93
  82. data/lib/active_model/serializer/railtie.rb +0 -24
  83. data/lib/active_model/serializer_support.rb +0 -7
  84. data/test/benchmark/app.rb +0 -60
  85. data/test/benchmark/benchmarking_support.rb +0 -67
  86. data/test/benchmark/bm_active_record.rb +0 -41
  87. data/test/benchmark/setup.rb +0 -75
  88. data/test/benchmark/tmp/miniprofiler/mp_timers_6eqewtfgrhitvq5gqm25 +0 -0
  89. data/test/benchmark/tmp/miniprofiler/mp_timers_8083sx03hu72pxz1a4d0 +0 -0
  90. data/test/benchmark/tmp/miniprofiler/mp_timers_fyz2gsml4z0ph9kpoy1c +0 -0
  91. data/test/benchmark/tmp/miniprofiler/mp_timers_hjry5rc32imd42oxoi48 +0 -0
  92. data/test/benchmark/tmp/miniprofiler/mp_timers_m8fpoz2cvt3g9agz0bs3 +0 -0
  93. data/test/benchmark/tmp/miniprofiler/mp_timers_p92m2drnj1i568u3sta0 +0 -0
  94. data/test/benchmark/tmp/miniprofiler/mp_timers_qg52tpca3uesdfguee9i +0 -0
  95. data/test/benchmark/tmp/miniprofiler/mp_timers_s15t1a6mvxe0z7vjv790 +0 -0
  96. data/test/benchmark/tmp/miniprofiler/mp_timers_x8kal3d17nfds6vp4kcj +0 -0
  97. data/test/benchmark/tmp/miniprofiler/mp_views_127.0.0.1 +0 -0
  98. data/test/fixtures/active_record.rb +0 -96
  99. data/test/fixtures/poro.rb +0 -255
  100. data/test/fixtures/template.html.erb +0 -1
  101. data/test/integration/action_controller/namespaced_serialization_test.rb +0 -105
  102. data/test/integration/action_controller/serialization_test.rb +0 -287
  103. data/test/integration/action_controller/serialization_test_case_test.rb +0 -71
  104. data/test/integration/active_record/active_record_test.rb +0 -94
  105. data/test/integration/generators/resource_generator_test.rb +0 -26
  106. data/test/integration/generators/scaffold_controller_generator_test.rb +0 -64
  107. data/test/integration/generators/serializer_generator_test.rb +0 -41
  108. data/test/test_app.rb +0 -18
  109. data/test/test_helper.rb +0 -31
  110. data/test/tmp/app/assets/javascripts/accounts.js +0 -2
  111. data/test/tmp/app/assets/stylesheets/accounts.css +0 -4
  112. data/test/tmp/app/controllers/accounts_controller.rb +0 -3
  113. data/test/tmp/app/helpers/accounts_helper.rb +0 -3
  114. data/test/tmp/app/serializers/account_serializer.rb +0 -4
  115. data/test/tmp/config/routes.rb +0 -2
  116. data/test/unit/active_model/array_serializer/except_test.rb +0 -18
  117. data/test/unit/active_model/array_serializer/key_format_test.rb +0 -18
  118. data/test/unit/active_model/array_serializer/meta_test.rb +0 -53
  119. data/test/unit/active_model/array_serializer/only_test.rb +0 -18
  120. data/test/unit/active_model/array_serializer/options_test.rb +0 -16
  121. data/test/unit/active_model/array_serializer/root_test.rb +0 -102
  122. data/test/unit/active_model/array_serializer/scope_test.rb +0 -24
  123. data/test/unit/active_model/array_serializer/serialization_test.rb +0 -239
  124. data/test/unit/active_model/default_serializer_test.rb +0 -13
  125. data/test/unit/active_model/serializer/associations/build_serializer_test.rb +0 -36
  126. data/test/unit/active_model/serializer/associations_test.rb +0 -49
  127. data/test/unit/active_model/serializer/attributes_test.rb +0 -57
  128. data/test/unit/active_model/serializer/config_test.rb +0 -91
  129. data/test/unit/active_model/serializer/filter_test.rb +0 -69
  130. data/test/unit/active_model/serializer/has_many_polymorphic_test.rb +0 -189
  131. data/test/unit/active_model/serializer/has_many_test.rb +0 -265
  132. data/test/unit/active_model/serializer/has_one_and_has_many_test.rb +0 -27
  133. data/test/unit/active_model/serializer/has_one_polymorphic_test.rb +0 -196
  134. data/test/unit/active_model/serializer/has_one_test.rb +0 -253
  135. data/test/unit/active_model/serializer/key_format_test.rb +0 -25
  136. data/test/unit/active_model/serializer/meta_test.rb +0 -39
  137. data/test/unit/active_model/serializer/options_test.rb +0 -42
  138. data/test/unit/active_model/serializer/root_test.rb +0 -117
  139. data/test/unit/active_model/serializer/scope_test.rb +0 -49
  140. data/test/unit/active_model/serializer/url_helpers_test.rb +0 -36
  141. data/test/unit/active_model/serilizable_test.rb +0 -50
  142. /data/lib/{active_model/serializer/generators/serializer/templates/serializer.rb → generators/rails/templates/serializer.rb.erb} +0 -0
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Provides a single method +deprecate+ to be used to declare when
5
+ # something is going away.
6
+ #
7
+ # class Legacy
8
+ # def self.klass_method
9
+ # # ...
10
+ # end
11
+ #
12
+ # def instance_method
13
+ # # ...
14
+ # end
15
+ #
16
+ # extend ActiveModelSerializers::Deprecate
17
+ # deprecate :instance_method, "ActiveModelSerializers::NewPlace#new_method"
18
+ #
19
+ # class << self
20
+ # extend ActiveModelSerializers::Deprecate
21
+ # deprecate :klass_method, :none
22
+ # end
23
+ # end
24
+ #
25
+ # Adapted from https://github.com/rubygems/rubygems/blob/1591331/lib/rubygems/deprecate.rb
26
+ module ActiveModelSerializers
27
+ module Deprecate
28
+ ##
29
+ # Simple deprecation method that deprecates +name+ by wrapping it up
30
+ # in a dummy method. It warns on each call to the dummy method
31
+ # telling the user of +replacement+ (unless +replacement+ is :none) that it is planned to go away.
32
+
33
+ def deprecate(name, replacement)
34
+ old = "_deprecated_#{name}"
35
+ alias_method old, name
36
+ class_eval do
37
+ define_method(name) do |*args, &block|
38
+ target = is_a?(Module) ? "#{self}." : "#{self.class}#"
39
+ msg = ["NOTE: #{target}#{name} is deprecated",
40
+ replacement == :none ? ' with no replacement' : "; use #{replacement} instead",
41
+ "\n#{target}#{name} called from #{ActiveModelSerializers.location_of_caller.join(':')}"]
42
+ warn "#{msg.join}."
43
+ send old, *args, &block
44
+ end
45
+ end
46
+ end
47
+
48
+ def delegate_and_deprecate(method, delegee)
49
+ delegate method, to: delegee
50
+ deprecate method, "#{delegee.name}."
51
+ end
52
+
53
+ module_function :deprecate
54
+ module_function :delegate_and_deprecate
55
+ end
56
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModelSerializers
4
+ module Deserialization
5
+ module_function
6
+
7
+ def jsonapi_parse(*args)
8
+ Adapter::JsonApi::Deserialization.parse(*args)
9
+ end
10
+
11
+ # :nocov:
12
+ def jsonapi_parse!(*args)
13
+ Adapter::JsonApi::Deserialization.parse!(*args)
14
+ end
15
+ # :nocov:
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModelSerializers
4
+ module JsonPointer
5
+ module_function
6
+
7
+ POINTERS = {
8
+ attribute: '/data/attributes/%s'.freeze,
9
+ primary_data: '/data%s'.freeze
10
+ }.freeze
11
+
12
+ def new(pointer_type, value = nil)
13
+ format(POINTERS[pointer_type], value)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # ActiveModelSerializers::Logging
5
+ #
6
+ # https://github.com/rails/rails/blob/280654ef88/activejob/lib/active_job/logging.rb
7
+ #
8
+ module ActiveModelSerializers
9
+ module Logging
10
+ RENDER_EVENT = 'render.active_model_serializers'.freeze
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ include ActiveModelSerializers::Callbacks
15
+ extend Macros
16
+ instrument_rendering
17
+ end
18
+
19
+ module ClassMethods
20
+ def instrument_rendering
21
+ around_render do |args, block|
22
+ tag_logger do
23
+ notify_render do
24
+ block.call(args)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ # Macros that can be used to customize the logging of class or instance methods,
32
+ # by extending the class or its singleton.
33
+ #
34
+ # Adapted from:
35
+ # https://github.com/rubygems/rubygems/blob/cb28f5e991/lib/rubygems/deprecate.rb
36
+ #
37
+ # Provides a single method +notify+ to be used to declare when
38
+ # something a method notifies, with the argument +callback_name+ of the notification callback.
39
+ #
40
+ # class Adapter
41
+ # def self.klass_method
42
+ # # ...
43
+ # end
44
+ #
45
+ # def instance_method
46
+ # # ...
47
+ # end
48
+ #
49
+ # include ActiveModelSerializers::Logging::Macros
50
+ # notify :instance_method, :render
51
+ #
52
+ # class << self
53
+ # extend ActiveModelSerializers::Logging::Macros
54
+ # notify :klass_method, :render
55
+ # end
56
+ # end
57
+ module Macros
58
+ ##
59
+ # Simple notify method that wraps up +name+
60
+ # in a dummy method. It notifies on with the +callback_name+ notifier on
61
+ # each call to the dummy method, telling what the current serializer and adapter
62
+ # are being rendered.
63
+ # Adapted from:
64
+ # https://github.com/rubygems/rubygems/blob/cb28f5e991/lib/rubygems/deprecate.rb
65
+ def notify(name, callback_name)
66
+ class_eval do
67
+ old = "_notifying_#{callback_name}_#{name}"
68
+ alias_method old, name
69
+ define_method name do |*args, &block|
70
+ run_callbacks callback_name do
71
+ send old, *args, &block
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ def notify_render(*)
79
+ event_name = RENDER_EVENT
80
+ ActiveSupport::Notifications.instrument(event_name, notify_render_payload) do
81
+ yield
82
+ end
83
+ end
84
+
85
+ def notify_render_payload
86
+ {
87
+ serializer: serializer || ActiveModel::Serializer::Null,
88
+ adapter: adapter || ActiveModelSerializers::Adapter::Null
89
+ }
90
+ end
91
+
92
+ private
93
+
94
+ def tag_logger(*tags)
95
+ if ActiveModelSerializers.logger.respond_to?(:tagged)
96
+ tags.unshift 'active_model_serializers'.freeze unless logger_tagged_by_active_model_serializers?
97
+ ActiveModelSerializers.logger.tagged(*tags) { yield }
98
+ else
99
+ yield
100
+ end
101
+ end
102
+
103
+ def logger_tagged_by_active_model_serializers?
104
+ ActiveModelSerializers.logger.formatter.current_tags.include?('active_model_serializers'.freeze)
105
+ end
106
+
107
+ class LogSubscriber < ActiveSupport::LogSubscriber
108
+ def render(event)
109
+ info do
110
+ serializer = event.payload[:serializer]
111
+ adapter = event.payload[:adapter]
112
+ duration = event.duration.round(2)
113
+ "Rendered #{serializer.name} with #{adapter.class} (#{duration}ms)"
114
+ end
115
+ end
116
+
117
+ def logger
118
+ ActiveModelSerializers.logger
119
+ end
120
+ end
121
+ end
122
+ end
123
+
124
+ ActiveModelSerializers::Logging::LogSubscriber.attach_to :active_model_serializers
@@ -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
@@ -0,0 +1,132 @@
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'
7
+ module ActiveModelSerializers
8
+ class Model
9
+ include ActiveModel::Serializers::JSON
10
+ include ActiveModel::Model
11
+
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 = []
22
+
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
47
+ end
48
+
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
54
+ end
55
+
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
72
+ end
73
+
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
94
+ end
95
+
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
107
+ end
108
+ end
109
+
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
120
+ end
121
+
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)
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/railtie'
4
+ require 'action_controller'
5
+ require 'action_controller/railtie'
6
+ require 'action_controller/serialization'
7
+
8
+ module ActiveModelSerializers
9
+ class Railtie < Rails::Railtie
10
+ config.eager_load_namespaces << ActiveModelSerializers
11
+
12
+ config.to_prepare do
13
+ ActiveModel::Serializer.serializers_cache.clear
14
+ end
15
+
16
+ initializer 'active_model_serializers.action_controller' do
17
+ ActiveSupport.on_load(:action_controller) do
18
+ include(::ActionController::Serialization)
19
+ end
20
+ end
21
+
22
+ initializer 'active_model_serializers.prepare_serialization_context' do
23
+ SerializationContext.url_helpers = Rails.application.routes.url_helpers
24
+ SerializationContext.default_url_options = Rails.application.routes.default_url_options
25
+ end
26
+
27
+ # This hook is run after the action_controller railtie has set the configuration
28
+ # based on the *environment* configuration and before any config/initializers are run
29
+ # and also before eager_loading (if enabled).
30
+ initializer 'active_model_serializers.set_configs', after: 'action_controller.set_configs' do
31
+ ActiveModelSerializers.logger = Rails.configuration.action_controller.logger
32
+ ActiveModelSerializers.config.perform_caching = Rails.configuration.action_controller.perform_caching
33
+ # We want this hook to run after the config has been set, even if ActionController has already loaded.
34
+ ActiveSupport.on_load(:action_controller) do
35
+ ActiveModelSerializers.config.cache_store = ActionController::Base.cache_store
36
+ end
37
+ end
38
+
39
+ # :nocov:
40
+ generators do |app|
41
+ Rails::Generators.configure!(app.config.generators)
42
+ Rails::Generators.hidden_namespaces.uniq!
43
+ require 'generators/rails/resource_override'
44
+ end
45
+ # :nocov:
46
+
47
+ def extend_action_controller_test_case(&block)
48
+ if Rails::VERSION::MAJOR >= 6 || Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR >= 2
49
+ ActiveSupport.on_load(:action_controller_test_case, run_once: true, &block)
50
+ else
51
+ ActionController::TestCase.instance_eval(&block)
52
+ end
53
+ end
54
+
55
+ if Rails.env.test?
56
+ extend_action_controller_test_case do
57
+ include ActiveModelSerializers::Test::Schema
58
+ include ActiveModelSerializers::Test::Serializer
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Based on discussion in https://github.com/rails/rails/pull/23712#issuecomment-184977238,
4
+ # the JSON API media type will have its own format/renderer.
5
+ #
6
+ # > We recommend the media type be registered on its own as jsonapi
7
+ # when a jsonapi Renderer and deserializer (Http::Parameters::DEFAULT_PARSERS) are added.
8
+ #
9
+ # Usage:
10
+ #
11
+ # ActiveSupport.on_load(:action_controller) do
12
+ # require 'active_model_serializers/register_jsonapi_renderer'
13
+ # end
14
+ #
15
+ # And then in controllers, use `render jsonapi: model` rather than `render json: model, adapter: :json_api`.
16
+ #
17
+ # For example, in a controller action, we can:
18
+ # respond_to do |format|
19
+ # format.jsonapi { render jsonapi: model }
20
+ # end
21
+ #
22
+ # or
23
+ #
24
+ # render jsonapi: model
25
+ #
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
34
+
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
44
+
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
52
+
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
63
+
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
74
+ end
75
+
76
+ ActiveModelSerializers::Jsonapi.install
77
+
78
+ ActiveSupport.on_load(:action_controller) do
79
+ include ActiveModelSerializers::Jsonapi::ControllerSupport
80
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module ActiveModelSerializers
6
+ class SerializableResource
7
+ ADAPTER_OPTION_KEYS = Set.new([:include, :fields, :adapter, :meta, :meta_key, :links, :serialization_context, :key_transform])
8
+ include ActiveModelSerializers::Logging
9
+
10
+ delegate :serializable_hash, :as_json, :to_json, to: :adapter
11
+ notify :serializable_hash, :render
12
+ notify :as_json, :render
13
+ notify :to_json, :render
14
+
15
+ # Primary interface to composing a resource with a serializer and adapter.
16
+ # @return the serializable_resource, ready for #as_json/#to_json/#serializable_hash.
17
+ def initialize(resource, options = {})
18
+ @resource = resource
19
+ @adapter_opts = options.select { |k, _| ADAPTER_OPTION_KEYS.include? k }
20
+ @serializer_opts = options.reject { |k, _| ADAPTER_OPTION_KEYS.include? k }
21
+ end
22
+
23
+ def serialization_scope=(scope)
24
+ serializer_opts[:scope] = scope
25
+ end
26
+
27
+ def serialization_scope
28
+ serializer_opts[:scope]
29
+ end
30
+
31
+ def serialization_scope_name=(scope_name)
32
+ serializer_opts[:scope_name] = scope_name
33
+ end
34
+
35
+ # NOTE: if no adapter is available, returns the resource itself. (i.e. adapter is a no-op)
36
+ def adapter
37
+ @adapter ||= find_adapter
38
+ end
39
+ alias adapter_instance adapter
40
+
41
+ def find_adapter
42
+ return resource unless serializer?
43
+ adapter = catch :no_serializer do
44
+ ActiveModelSerializers::Adapter.create(serializer_instance, adapter_opts)
45
+ end
46
+ adapter || resource
47
+ end
48
+
49
+ def serializer_instance
50
+ @serializer_instance ||= serializer.new(resource, serializer_opts)
51
+ end
52
+
53
+ # Get serializer either explicitly :serializer or implicitly from resource
54
+ # Remove :serializer key from serializer_opts
55
+ # Remove :each_serializer if present and set as :serializer key
56
+ def serializer
57
+ @serializer ||=
58
+ begin
59
+ @serializer = serializer_opts.delete(:serializer)
60
+ @serializer ||= ActiveModel::Serializer.serializer_for(resource, serializer_opts)
61
+
62
+ if serializer_opts.key?(:each_serializer)
63
+ serializer_opts[:serializer] = serializer_opts.delete(:each_serializer)
64
+ end
65
+ @serializer
66
+ end
67
+ end
68
+ alias serializer_class serializer
69
+
70
+ # True when no explicit adapter given, or explicit appear is truthy (non-nil)
71
+ # False when explicit adapter is falsy (nil or false)
72
+ def use_adapter?
73
+ !(adapter_opts.key?(:adapter) && !adapter_opts[:adapter])
74
+ end
75
+
76
+ def serializer?
77
+ use_adapter? && !serializer.nil?
78
+ end
79
+
80
+ protected
81
+
82
+ attr_reader :resource, :adapter_opts, :serializer_opts
83
+ end
84
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/array/extract_options'
4
+ module ActiveModelSerializers
5
+ class SerializationContext
6
+ class << self
7
+ attr_writer :url_helpers, :default_url_options
8
+ def url_helpers
9
+ @url_helpers ||= Module.new
10
+ end
11
+
12
+ def default_url_options
13
+ @default_url_options ||= {}
14
+ end
15
+ end
16
+ module UrlHelpers
17
+ def self.included(base)
18
+ base.send(:include, SerializationContext.url_helpers)
19
+ end
20
+
21
+ def default_url_options
22
+ SerializationContext.default_url_options
23
+ end
24
+ end
25
+
26
+ attr_reader :request_url, :query_parameters, :key_transform
27
+
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)
37
+ @url_helpers = options.delete(:url_helpers) || self.class.url_helpers
38
+ @default_url_options = options.delete(:default_url_options) || self.class.default_url_options
39
+ end
40
+ end
41
+ end