Skip to content

Commit 80e470d

Browse files
authored
Merge pull request #2074 from bf4/make_serializer_interface_more_obvious
Make serializer interface more obvious
2 parents a461389 + 2e71bc4 commit 80e470d

File tree

8 files changed

+239
-345
lines changed

8 files changed

+239
-345
lines changed

lib/active_model/serializer.rb

Lines changed: 226 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@
44
require 'active_model/serializer/array_serializer'
55
require 'active_model/serializer/error_serializer'
66
require 'active_model/serializer/errors_serializer'
7-
require 'active_model/serializer/concerns/associations'
8-
require 'active_model/serializer/concerns/attributes'
97
require 'active_model/serializer/concerns/caching'
10-
require 'active_model/serializer/concerns/configuration'
11-
require 'active_model/serializer/concerns/links'
12-
require 'active_model/serializer/concerns/meta'
13-
require 'active_model/serializer/concerns/type'
148
require 'active_model/serializer/fieldset'
159
require 'active_model/serializer/lint'
1610

@@ -23,13 +17,16 @@ class Serializer
2317
extend ActiveSupport::Autoload
2418
autoload :Adapter
2519
autoload :Null
26-
include Configuration
27-
include Associations
28-
include Attributes
20+
autoload :Attribute
21+
autoload :Association
22+
autoload :Reflection
23+
autoload :SingularReflection
24+
autoload :CollectionReflection
25+
autoload :BelongsToReflection
26+
autoload :HasOneReflection
27+
autoload :HasManyReflection
28+
include ActiveSupport::Configurable
2929
include Caching
30-
include Links
31-
include Meta
32-
include Type
3330

3431
# @param resource [ActiveRecord::Base, ActiveModelSerializers::Model]
3532
# @return [ActiveModel::Serializer]
@@ -114,6 +111,193 @@ def self.serialization_adapter_instance
114111
@serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
115112
end
116113

114+
# Preferred interface is ActiveModelSerializers.config
115+
# BEGIN DEFAULT CONFIGURATION
116+
config.collection_serializer = ActiveModel::Serializer::CollectionSerializer
117+
config.serializer_lookup_enabled = true
118+
119+
# @deprecated Use {#config.collection_serializer=} instead of this. Is
120+
# compatibilty layer for ArraySerializer.
121+
def config.array_serializer=(collection_serializer)
122+
self.collection_serializer = collection_serializer
123+
end
124+
125+
# @deprecated Use {#config.collection_serializer} instead of this. Is
126+
# compatibilty layer for ArraySerializer.
127+
def config.array_serializer
128+
collection_serializer
129+
end
130+
131+
config.default_includes = '*'
132+
config.adapter = :attributes
133+
config.key_transform = nil
134+
config.jsonapi_pagination_links_enabled = true
135+
config.jsonapi_resource_type = :plural
136+
config.jsonapi_namespace_separator = '-'.freeze
137+
config.jsonapi_version = '1.0'
138+
config.jsonapi_toplevel_meta = {}
139+
# Make JSON API top-level jsonapi member opt-in
140+
# ref: http://jsonapi.org/format/#document-top-level
141+
config.jsonapi_include_toplevel_object = false
142+
config.include_data_default = true
143+
144+
# For configuring how serializers are found.
145+
# This should be an array of procs.
146+
#
147+
# The priority of the output is that the first item
148+
# in the evaluated result array will take precedence
149+
# over other possible serializer paths.
150+
#
151+
# i.e.: First match wins.
152+
#
153+
# @example output
154+
# => [
155+
# "CustomNamespace::ResourceSerializer",
156+
# "ParentSerializer::ResourceSerializer",
157+
# "ResourceNamespace::ResourceSerializer" ,
158+
# "ResourceSerializer"]
159+
#
160+
# If CustomNamespace::ResourceSerializer exists, it will be used
161+
# for serialization
162+
config.serializer_lookup_chain = ActiveModelSerializers::LookupChain::DEFAULT.dup
163+
164+
config.schema_path = 'test/support/schemas'
165+
# END DEFAULT CONFIGURATION
166+
167+
with_options instance_writer: false, instance_reader: false do |serializer|
168+
serializer.class_attribute :_attributes_data # @api private
169+
self._attributes_data ||= {}
170+
end
171+
with_options instance_writer: false, instance_reader: true do |serializer|
172+
serializer.class_attribute :_reflections
173+
self._reflections ||= {}
174+
serializer.class_attribute :_links # @api private
175+
self._links ||= {}
176+
serializer.class_attribute :_meta # @api private
177+
serializer.class_attribute :_type # @api private
178+
end
179+
180+
def self.inherited(base)
181+
super
182+
base._attributes_data = _attributes_data.dup
183+
base._reflections = _reflections.dup
184+
base._links = _links.dup
185+
end
186+
187+
# @return [Array<Symbol>] Key names of declared attributes
188+
# @see Serializer::attribute
189+
def self._attributes
190+
_attributes_data.keys
191+
end
192+
193+
# BEGIN SERIALIZER MACROS
194+
195+
# @example
196+
# class AdminAuthorSerializer < ActiveModel::Serializer
197+
# attributes :id, :name, :recent_edits
198+
def self.attributes(*attrs)
199+
attrs = attrs.first if attrs.first.class == Array
200+
201+
attrs.each do |attr|
202+
attribute(attr)
203+
end
204+
end
205+
206+
# @example
207+
# class AdminAuthorSerializer < ActiveModel::Serializer
208+
# attributes :id, :recent_edits
209+
# attribute :name, key: :title
210+
#
211+
# attribute :full_name do
212+
# "#{object.first_name} #{object.last_name}"
213+
# end
214+
#
215+
# def recent_edits
216+
# object.edits.last(5)
217+
# end
218+
def self.attribute(attr, options = {}, &block)
219+
key = options.fetch(:key, attr)
220+
_attributes_data[key] = Attribute.new(attr, options, block)
221+
end
222+
223+
# @param [Symbol] name of the association
224+
# @param [Hash<Symbol => any>] options for the reflection
225+
# @return [void]
226+
#
227+
# @example
228+
# has_many :comments, serializer: CommentSummarySerializer
229+
#
230+
def self.has_many(name, options = {}, &block) # rubocop:disable Style/PredicateName
231+
associate(HasManyReflection.new(name, options, block))
232+
end
233+
234+
# @param [Symbol] name of the association
235+
# @param [Hash<Symbol => any>] options for the reflection
236+
# @return [void]
237+
#
238+
# @example
239+
# belongs_to :author, serializer: AuthorSerializer
240+
#
241+
def self.belongs_to(name, options = {}, &block)
242+
associate(BelongsToReflection.new(name, options, block))
243+
end
244+
245+
# @param [Symbol] name of the association
246+
# @param [Hash<Symbol => any>] options for the reflection
247+
# @return [void]
248+
#
249+
# @example
250+
# has_one :author, serializer: AuthorSerializer
251+
#
252+
def self.has_one(name, options = {}, &block) # rubocop:disable Style/PredicateName
253+
associate(HasOneReflection.new(name, options, block))
254+
end
255+
256+
# Add reflection and define {name} accessor.
257+
# @param [ActiveModel::Serializer::Reflection] reflection
258+
# @return [void]
259+
#
260+
# @api private
261+
def self.associate(reflection)
262+
key = reflection.options[:key] || reflection.name
263+
self._reflections[key] = reflection
264+
end
265+
private_class_method :associate
266+
267+
# Define a link on a serializer.
268+
# @example
269+
# link(:self) { resource_url(object) }
270+
# @example
271+
# link(:self) { "http://example.com/resource/#{object.id}" }
272+
# @example
273+
# link :resource, "http://example.com/resource"
274+
#
275+
def self.link(name, value = nil, &block)
276+
_links[name] = block || value
277+
end
278+
279+
# Set the JSON API meta attribute of a serializer.
280+
# @example
281+
# class AdminAuthorSerializer < ActiveModel::Serializer
282+
# meta { stuff: 'value' }
283+
# @example
284+
# meta do
285+
# { comment_count: object.comments.count }
286+
# end
287+
def self.meta(value = nil, &block)
288+
self._meta = block || value
289+
end
290+
291+
# Set the JSON API type of a serializer.
292+
# @example
293+
# class AdminAuthorSerializer < ActiveModel::Serializer
294+
# type 'authors'
295+
def self.type(type)
296+
self._type = type && type.to_s
297+
end
298+
299+
# END SERIALIZER MACROS
300+
117301
attr_accessor :object, :root, :scope
118302

119303
# `scope_name` is set as :current_user by default in the controller.
@@ -134,6 +318,36 @@ def success?
134318
true
135319
end
136320

321+
# Return the +attributes+ of +object+ as presented
322+
# by the serializer.
323+
def attributes(requested_attrs = nil, reload = false)
324+
@attributes = nil if reload
325+
@attributes ||= self.class._attributes_data.each_with_object({}) do |(key, attr), hash|
326+
next if attr.excluded?(self)
327+
next unless requested_attrs.nil? || requested_attrs.include?(key)
328+
hash[key] = attr.value(self)
329+
end
330+
end
331+
332+
# @param [JSONAPI::IncludeDirective] include_directive (defaults to the
333+
# +default_include_directive+ config value when not provided)
334+
# @return [Enumerator<Association>]
335+
#
336+
def associations(include_directive = ActiveModelSerializers.default_include_directive, include_slice = nil)
337+
include_slice ||= include_directive
338+
return unless object
339+
340+
Enumerator.new do |y|
341+
self.class._reflections.values.each do |reflection|
342+
next if reflection.excluded?(self)
343+
key = reflection.options.fetch(:key, reflection.name)
344+
next unless include_directive.key?(key)
345+
346+
y.yield reflection.build_association(self, instance_options, include_slice)
347+
end
348+
end
349+
end
350+
137351
# @return [Hash] containing the attributes and first level
138352
# associations, similar to how ActiveModel::Serializers::JSON is used
139353
# in ActiveRecord::Base.

lib/active_model/serializer/concerns/associations.rb

Lines changed: 0 additions & 102 deletions
This file was deleted.

0 commit comments

Comments
 (0)