Skip to content

Commit 2ed52f9

Browse files
committed
merge upstream update fieldset
2 parents fc1562c + c53f1da commit 2ed52f9

22 files changed

+603
-128
lines changed

.travis.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
language: ruby
2+
3+
sudo: false
4+
25
rvm:
36
- 1.9.3
47
- 2.0.0
58
- 2.1.1
69
- jruby-19mode
710
- rbx-2
811
- ruby-head
12+
913
install:
1014
- bundle install --retry=3
15+
1116
env:
1217
- "RAILS_VERSION=3.2"
1318
- "RAILS_VERSION=4.0"
1419
- "RAILS_VERSION=4.1"
1520
- "RAILS_VERSION=master"
21+
1622
matrix:
1723
allow_failures:
1824
- rvm: ruby-head

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,18 @@ ActiveModel::Serializer.config.adapter = :hal
6969
You won't need to implement an adapter unless you wish to use a new format or
7070
media type with AMS.
7171

72+
If you would like the key in the outputted JSON to be different from its name in ActiveRecord, you can use the :key option to customize it:
73+
74+
```ruby
75+
class PostSerializer < ActiveModel::Serializer
76+
attributes :id, :body
77+
78+
# look up :subject on the model, but use +title+ in the JSON
79+
attribute :subject, :key => :title
80+
has_many :comments
81+
end
82+
```
83+
7284
In your controllers, when you use `render :json`, Rails will now first search
7385
for a serializer for the object and use it if available.
7486

@@ -94,6 +106,27 @@ member when the resource names are included in the `include` option.
94106
render @posts, include: 'authors,comments'
95107
```
96108

109+
### Specify a serializer
110+
111+
If you wish to use a serializer other than the default, you can explicitly pass it to the renderer.
112+
113+
#### 1. For a resource:
114+
115+
```ruby
116+
render json: @post, serializer: PostPreviewSerializer
117+
```
118+
119+
#### 2. For an array resource:
120+
121+
```ruby
122+
# Use the default `ArraySerializer`, which will use `each_serializer` to
123+
# serialize each element
124+
render json: @posts, each_serializer: PostPreviewSerializer
125+
126+
# Or, you can explicitly provide the collection serializer as well
127+
render json: @posts, serializer: PaginatedSerializer, each_serializer: PostPreviewSerializer
128+
```
129+
97130
## Installation
98131

99132
Add this line to your application's Gemfile:

lib/action_controller/serialization.rb

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,32 @@ module Serialization
66

77
include ActionController::Renderers
88

9-
ADAPTER_OPTION_KEYS = [:include, :fields, :root]
9+
ADAPTER_OPTION_KEYS = [:include, :fields, :root, :adapter]
10+
11+
def get_serializer(resource)
12+
@_serializer ||= @_serializer_opts.delete(:serializer)
13+
@_serializer ||= ActiveModel::Serializer.serializer_for(resource)
14+
15+
if @_serializer_opts.key?(:each_serializer)
16+
@_serializer_opts[:serializer] = @_serializer_opts.delete(:each_serializer)
17+
end
18+
19+
@_serializer
20+
end
21+
22+
def use_adapter?
23+
!(@_adapter_opts.key?(:adapter) && !@_adapter_opts[:adapter])
24+
end
1025

1126
[:_render_option_json, :_render_with_renderer_json].each do |renderer_method|
1227
define_method renderer_method do |resource, options|
13-
serializer = ActiveModel::Serializer.serializer_for(resource)
28+
@_adapter_opts, @_serializer_opts =
29+
options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }.map { |h| Hash[h] }
1430

15-
if serializer
16-
adapter_opts, serializer_opts =
17-
options.partition { |k, _| ADAPTER_OPTION_KEYS.include? k }
31+
if use_adapter? && (serializer = get_serializer(resource))
1832
# omg hax
19-
object = serializer.new(resource, Hash[serializer_opts])
20-
adapter = ActiveModel::Serializer.adapter.new(object, Hash[adapter_opts])
33+
object = serializer.new(resource, @_serializer_opts)
34+
adapter = ActiveModel::Serializer::Adapter.create(object, @_adapter_opts)
2135
super(adapter, options)
2236
else
2337
super(resource, options)

lib/active_model/serializer.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,21 @@ def self.inherited(base)
2121
def self.attributes(*attrs)
2222
@_attributes.concat attrs
2323

24-
2524
attrs.each do |attr|
2625
define_method attr do
2726
object.read_attribute_for_serialization(attr)
2827
end unless method_defined?(attr)
2928
end
3029
end
3130

31+
def self.attribute(attr, options = {})
32+
key = options.fetch(:key, attr)
33+
@_attributes.concat [key]
34+
define_method key do
35+
object.read_attribute_for_serialization(attr)
36+
end unless method_defined?(key)
37+
end
38+
3239
# Defines an association in the object should be rendered.
3340
#
3441
# The serializer object should implement the association name
@@ -83,8 +90,7 @@ def self.serializer_for(resource)
8390
def self.adapter
8491
adapter_class = case config.adapter
8592
when Symbol
86-
class_name = "ActiveModel::Serializer::Adapter::#{config.adapter.to_s.classify}"
87-
class_name.safe_constantize
93+
ActiveModel::Serializer::Adapter.adapter_class(config.adapter)
8894
when Class
8995
config.adapter
9096
end

lib/active_model/serializer/adapter.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ def serializable_hash(options = {})
2020
def as_json(options = {})
2121
serializable_hash(options)
2222
end
23+
24+
def self.create(resource, options = {})
25+
override = options.delete(:adapter)
26+
klass = override ? adapter_class(override) : ActiveModel::Serializer.adapter
27+
klass.new(resource, options)
28+
end
29+
30+
def self.adapter_class(adapter)
31+
"ActiveModel::Serializer::Adapter::#{adapter.to_s.classify}".safe_constantize
32+
end
2333
end
2434
end
2535
end

lib/active_model/serializer/adapter/json_api.rb

Lines changed: 93 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -25,91 +25,129 @@ def serializable_hash(options = {})
2525
else
2626
@hash[@root] = attributes_for_serializer(serializer, @options)
2727

28-
serializer.each_association do |name, association, opts|
29-
@hash[@root][:links] ||= {}
30-
31-
if association.respond_to?(:each)
32-
add_links(name, association, opts)
33-
else
34-
add_link(name, association, opts)
35-
end
36-
end
28+
add_resource_links(@hash[@root], serializer)
3729
end
3830

3931
@hash
4032
end
4133

42-
def add_links(name, serializers, options)
43-
if serializers.first
44-
type = serializers.first.object.class.to_s.underscore.pluralize
45-
end
34+
private
35+
36+
def add_links(resource, name, serializers)
37+
type = serialized_object_type(serializers)
38+
resource[:links] ||= {}
39+
4640
if name.to_s == type || !type
47-
@hash[@root][:links][name] ||= []
48-
@hash[@root][:links][name] += serializers.map{|serializer| serializer.id.to_s }
41+
resource[:links][name] ||= []
42+
resource[:links][name] += serializers.map{|serializer| serializer.id.to_s }
4943
else
50-
@hash[@root][:links][name] ||= {}
51-
@hash[@root][:links][name][:type] = type
52-
@hash[@root][:links][name][:ids] ||= []
53-
@hash[@root][:links][name][:ids] += serializers.map{|serializer| serializer.id.to_s }
54-
end
55-
56-
unless serializers.none? || @options[:embed] == :ids
57-
serializers.each do |serializer|
58-
add_linked(name, serializer)
59-
end
44+
resource[:links][name] ||= {}
45+
resource[:links][name][:type] = type
46+
resource[:links][name][:ids] ||= []
47+
resource[:links][name][:ids] += serializers.map{|serializer| serializer.id.to_s }
6048
end
6149
end
6250

63-
def add_link(name, serializer, options)
51+
def add_link(resource, name, serializer)
52+
resource[:links] ||= {}
53+
resource[:links][name] = nil
54+
6455
if serializer
65-
type = serializer.object.class.to_s.underscore
56+
type = serialized_object_type(serializer)
6657
if name.to_s == type || !type
67-
@hash[@root][:links][name] = serializer.id.to_s
58+
resource[:links][name] = serializer.id.to_s
6859
else
69-
@hash[@root][:links][name] ||= {}
70-
@hash[@root][:links][name][:type] = type
71-
@hash[@root][:links][name][:id] = serializer.id.to_s
60+
resource[:links][name] ||= {}
61+
resource[:links][name][:type] = type
62+
resource[:links][name][:id] = serializer.id.to_s
7263
end
73-
74-
unless @options[:embed] == :ids
75-
add_linked(name, serializer)
76-
end
77-
else
78-
@hash[@root][:links][name] = nil
7964
end
8065
end
8166

82-
def add_linked(resource, serializer, parent = nil)
83-
resource_path = [parent, resource].compact.join('.')
84-
if include_assoc? resource_path
85-
plural_name = resource.to_s.pluralize.to_sym
86-
attrs = attributes_for_serializer(serializer, @options)
67+
def add_linked(resource_name, serializer, parent = nil)
68+
resource_path = [parent, resource_name].compact.join('.')
69+
70+
if include_assoc?(resource_path)
71+
plural_name = serialized_object_type(serializer).pluralize.to_sym
72+
attrs = [attributes_for_serializer(serializer, @options)].flatten
8773
@top[:linked] ||= {}
8874
@top[:linked][plural_name] ||= []
89-
@top[:linked][plural_name].push attrs unless @top[:linked][plural_name].include? attrs
90-
end
9175

92-
unless serializer.respond_to?(:each)
93-
serializer.each_association do |name, association, opts|
94-
add_linked(name, association, resource) if association
76+
attrs.each do |attrs|
77+
add_resource_links(attrs, serializer, add_linked: false)
78+
79+
@top[:linked][plural_name].push(attrs) unless @top[:linked][plural_name].include?(attrs)
9580
end
9681
end
82+
83+
serializer.each_association do |name, association, opts|
84+
add_linked(name, association, resource_path) if association
85+
end if include_nested_assoc? resource_path
9786
end
9887

99-
private
10088

10189
def attributes_for_serializer(serializer, options)
102-
if fields = @fieldset && @fieldset.fields_for(serializer)
103-
options[:fields] = fields
90+
if serializer.respond_to?(:each)
91+
result = []
92+
serializer.each do |object|
93+
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
94+
attributes = object.attributes(options)
95+
attributes[:id] = attributes[:id].to_s if attributes[:id]
96+
result << attributes
97+
end
98+
else
99+
options[:fields] = @fieldset && @fieldset.fields_for(serializer)
100+
result = serializer.attributes(options)
101+
result[:id] = result[:id].to_s if result[:id]
102+
end
103+
104+
result
105+
end
106+
107+
def include_assoc?(assoc)
108+
return false unless @options[:include]
109+
check_assoc("#{assoc}$")
110+
end
111+
112+
def include_nested_assoc?(assoc)
113+
return false unless @options[:include]
114+
check_assoc("#{assoc}.")
115+
end
116+
117+
def check_assoc(assoc)
118+
@options[:include].split(',').any? do |s|
119+
s.match(/^#{assoc.gsub('.', '\.')}/)
104120
end
121+
end
105122

106-
attributes = serializer.attributes(options)
107-
attributes[:id] = attributes[:id].to_s if attributes[:id]
108-
attributes
123+
def serialized_object_type(serializer)
124+
return false unless Array(serializer).first
125+
type_name = Array(serializer).first.object.class.to_s.underscore
126+
if serializer.respond_to?(:first)
127+
type_name.pluralize
128+
else
129+
type_name
130+
end
109131
end
110132

111-
def include_assoc? assoc
112-
@options[:include] && @options[:include].split(',').include?(assoc.to_s)
133+
def add_resource_links(attrs, serializer, options = {})
134+
options[:add_linked] = options.fetch(:add_linked, true)
135+
136+
Array(serializer).first.each_association do |name, association, opts|
137+
attrs[:links] ||= {}
138+
139+
if association.respond_to?(:each)
140+
add_links(attrs, name, association)
141+
else
142+
add_link(attrs, name, association)
143+
end
144+
145+
if @options[:embed] != :ids && options[:add_linked]
146+
Array(association).each do |association|
147+
add_linked(name, association)
148+
end
149+
end
150+
end
113151
end
114152
end
115153
end

lib/active_model/serializer/array_serializer.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ class ArraySerializer
66

77
def initialize(objects, options = {})
88
@objects = objects.map do |object|
9-
serializer_class = ActiveModel::Serializer.serializer_for(object)
9+
serializer_class = options.fetch(
10+
:serializer,
11+
ActiveModel::Serializer.serializer_for(object)
12+
)
1013
serializer_class.new(object)
1114
end
1215
end

lib/active_model/serializer/fieldset.rb

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ module ActiveModel
22
class Serializer
33
class Fieldset
44

5-
attr_reader :fields, :root
6-
75
def initialize(fields, root = nil)
8-
@root = root
9-
@fields = parse(fields)
6+
@root = root
7+
@raw_fields = fields
8+
end
9+
10+
def fields
11+
@fields ||= parsed_fields
1012
end
1113

1214
def fields_for(serializer)
@@ -16,15 +18,17 @@ def fields_for(serializer)
1618

1719
private
1820

19-
def parse(fields)
20-
if fields.is_a?(Hash)
21-
fields.inject({}) { |h,(k,v)| h[k.to_sym] = v.map(&:to_sym); h}
22-
elsif fields.is_a?(Array)
21+
attr_reader :raw_fields, :root
22+
23+
def parsed_fields
24+
if raw_fields.is_a?(Hash)
25+
raw_fields.inject({}) { |h,(k,v)| h[k.to_sym] = v.map(&:to_sym); h}
26+
elsif raw_fields.is_a?(Array)
2327
if root.nil?
2428
raise ArgumentError, 'The root argument must be specified if the fileds argument is an array.'
2529
end
2630
hash = {}
27-
hash[root.to_sym] = fields.map(&:to_sym)
31+
hash[root.to_sym] = raw_fields.map(&:to_sym)
2832
hash
2933
else
3034
{}

0 commit comments

Comments
 (0)