Skip to content

Commit 36b4eac

Browse files
committed
Make serializer interface more obvious
1 parent e325b32 commit 36b4eac

File tree

8 files changed

+234
-345
lines changed

8 files changed

+234
-345
lines changed

lib/active_model/serializer.rb

Lines changed: 233 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]
@@ -111,6 +108,200 @@ def self.serialization_adapter_instance
111108
@serialization_adapter_instance ||= ActiveModelSerializers::Adapter::Attributes
112109
end
113110

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

116307
# `scope_name` is set as :current_user by default in the controller.
@@ -131,6 +322,36 @@ def success?
131322
true
132323
end
133324

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