Skip to content

Commit a04ae2c

Browse files
committed
Merge pull request #609 from polmiro/feature/support_polymorphic_associations_v2
Feature/support polymorphic associations v2
2 parents 2fec511 + 26d5fb7 commit a04ae2c

File tree

9 files changed

+583
-64
lines changed

9 files changed

+583
-64
lines changed

README.md

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
[![Build Status](https://api.travis-ci.org/rails-api/active_model_serializers.png?branch=0-9-stable)](https://travis-ci.org/rails-api/active_model_serializers)
2-
[![Code Climate](https://codeclimate.com/github/rails-api/active_model_serializers.png)](https://codeclimate.com/github/rails-api/active_model_serializers)
1+
[![Build Status](https://api.travis-ci.org/rails-api/active_model_serializers.png?branch=0-9-stable)](https://travis-ci.org/rails-api/active_model_serializers)
2+
[![Code Climate](https://codeclimate.com/github/rails-api/active_model_serializers.png)](https://codeclimate.com/github/rails-api/active_model_serializers)
33

44
# ActiveModel::Serializers
55

66
## Purpose
77

8-
`ActiveModel::Serializers` encapsulates the JSON serialization of objects.
9-
Objects that respond to read\_attribute\_for\_serialization
8+
`ActiveModel::Serializers` encapsulates the JSON serialization of objects.
9+
Objects that respond to read\_attribute\_for\_serialization
1010
(including `ActiveModel` and `ActiveRecord` objects) are supported.
1111

1212
Serializers know about both a model and the `current_user`, so you can
@@ -229,7 +229,7 @@ ActiveModel::Serializer.setup do |config|
229229
config.key_format = :lower_camel
230230
end
231231

232-
class BlogLowerCamelSerializer < ActiveModel::Serializer
232+
class BlogLowerCamelSerializer < ActiveModel::Serializer
233233
format_keys :lower_camel
234234
end
235235

@@ -467,9 +467,6 @@ You may also use the `:serializer` option to specify a custom serializer class a
467467

468468
Serializers are only concerned with multiplicity, and not ownership. `belongs_to` ActiveRecord associations can be included using `has_one` in your serializer.
469469

470-
NOTE: polymorphic was removed because was only supported for has\_one
471-
associations and is in the TODO list of the project.
472-
473470
## Embedding Associations
474471

475472
By default, associations will be embedded inside the serialized object. So if
@@ -646,8 +643,8 @@ The above would yield the following JSON document:
646643
}
647644
```
648645

649-
When side-loading data, your serializer cannot have the `{ root: false }` option,
650-
as this would lead to invalid JSON. If you do not have a root key, the `include`
646+
When side-loading data, your serializer cannot have the `{ root: false }` option,
647+
as this would lead to invalid JSON. If you do not have a root key, the `include`
651648
instruction will be ignored
652649

653650
You can also specify a different root for the embedded objects than the key
@@ -714,6 +711,78 @@ data looking for information, is extremely useful.
714711
If you are mostly working with the data in simple scenarios and manually making
715712
Ajax requests, you probably just want to use the default embedded behavior.
716713

714+
715+
## Embedding Polymorphic Associations
716+
717+
Because we need both the id and the type to be able to identify a polymorphic associated model, these are serialized in a slightly different format than common ones.
718+
719+
When embedding entire objects:
720+
721+
```ruby
722+
class PostSerializer < ActiveModel::Serializer
723+
attributes :id, :title
724+
has_many :attachments, polymorphic: true
725+
end
726+
```
727+
728+
```json
729+
{
730+
"post": {
731+
"id": 1,
732+
"title": "New post",
733+
"attachments": [
734+
{
735+
"type": "image"
736+
"image": {
737+
"id": 3
738+
"name": "logo"
739+
"url": "http://images.com/logo.jpg"
740+
}
741+
},
742+
{
743+
"type": "video"
744+
"video": {
745+
"id": 12
746+
"uid": "XCSSMDFWW"
747+
"source": "youtube"
748+
}
749+
}
750+
]
751+
}
752+
}
753+
```
754+
755+
When embedding ids:
756+
757+
```ruby
758+
class PostSerializer < ActiveModel::Serializer
759+
embed :ids
760+
761+
attributes :id, :title
762+
has_many :attachments, polymorphic: true
763+
end
764+
```
765+
766+
```json
767+
{
768+
"post": {
769+
"id": 1,
770+
"title": "New post",
771+
"attachment_ids": [
772+
{
773+
"type": "image"
774+
"id": 12
775+
},
776+
{
777+
"type": "video"
778+
"id": 3
779+
}
780+
]
781+
}
782+
}
783+
```
784+
785+
717786
## Customizing Scope
718787

719788
In a serializer, `current_user` is the current authorization scope which the controller

lib/active_model/array_serializer.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def initialize(object, options={})
1515
@object = object
1616
@scope = options[:scope]
1717
@root = options.fetch(:root, self.class._root)
18+
@polymorphic = options.fetch(:polymorphic, false)
1819
@meta_key = options[:meta_key] || :meta
1920
@meta = options[@meta_key]
2021
@each_serializer = options[:each_serializer]
@@ -33,7 +34,7 @@ def json_key
3334

3435
def serializer_for(item)
3536
serializer_class = @each_serializer || Serializer.serializer_for(item) || DefaultSerializer
36-
serializer_class.new(item, scope: scope, key_format: key_format, only: @only, except: @except)
37+
serializer_class.new(item, scope: scope, key_format: key_format, only: @only, except: @except, polymorphic: @polymorphic)
3738
end
3839

3940
def serializable_object

lib/active_model/serializer.rb

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
require 'active_model/array_serializer'
22
require 'active_model/serializable'
3-
require 'active_model/serializer/associations'
3+
require 'active_model/serializer/association'
44
require 'active_model/serializer/config'
55

66
require 'thread'
@@ -130,6 +130,7 @@ def initialize(object, options={})
130130
@object = object
131131
@scope = options[:scope]
132132
@root = options.fetch(:root, self.class._root)
133+
@polymorphic = options.fetch(:polymorphic, false)
133134
@meta_key = options[:meta_key] || :meta
134135
@meta = options[@meta_key]
135136
@wrap_in_array = options[:_wrap_in_array]
@@ -138,7 +139,7 @@ def initialize(object, options={})
138139
@key_format = options[:key_format]
139140
@context = options[:context]
140141
end
141-
attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format, :context
142+
attr_accessor :object, :scope, :root, :meta_key, :meta, :key_format, :context, :polymorphic
142143

143144
def json_key
144145
key = if root == true || root.nil?
@@ -225,9 +226,9 @@ def serialize(association)
225226
def serialize_ids(association)
226227
associated_data = send(association.name)
227228
if associated_data.respond_to?(:to_ary)
228-
associated_data.map { |elem| elem.read_attribute_for_serialization(association.embed_key) }
229+
associated_data.map { |elem| serialize_id(elem, association) }
229230
else
230-
associated_data.read_attribute_for_serialization(association.embed_key) if associated_data
231+
serialize_id(associated_data, association) if associated_data
231232
end
232233
end
233234

@@ -260,9 +261,19 @@ def serializable_object(options={})
260261
hash = attributes
261262
hash.merge! associations
262263
hash = convert_keys(hash) if key_format.present?
264+
hash = { :type => type_name(@object), type_name(@object) => hash } if @polymorphic
263265
@wrap_in_array ? [hash] : hash
264266
end
265267
alias_method :serializable_hash, :serializable_object
268+
269+
def serialize_id(elem, association)
270+
id = elem.read_attribute_for_serialization(association.embed_key)
271+
association.polymorphic? ? { id: id, type: type_name(elem) } : id
272+
end
273+
274+
def type_name(elem)
275+
elem.class.to_s.demodulize.underscore.to_sym
276+
end
266277
end
267278

268279
end
Lines changed: 5 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
require 'active_model/default_serializer'
2+
require 'active_model/serializer/association/has_one'
3+
require 'active_model/serializer/association/has_many'
24

35
module ActiveModel
46
class Serializer
@@ -13,6 +15,7 @@ def initialize(name, options={})
1315
@name = name.to_s
1416
@options = options
1517
self.embed = options.fetch(:embed) { CONFIG.embed }
18+
@polymorphic = options.fetch(:polymorphic, false)
1619
@embed_in_root = options.fetch(:embed_in_root) { options.fetch(:include) { CONFIG.embed_in_root } }
1720
@key_format = options.fetch(:key_format) { CONFIG.key_format }
1821
@embed_key = options[:embed_key] || :id
@@ -25,13 +28,14 @@ def initialize(name, options={})
2528
@serializer_from_options = serializer.is_a?(String) ? serializer.constantize : serializer
2629
end
2730

28-
attr_reader :name, :embed_ids, :embed_objects
31+
attr_reader :name, :embed_ids, :embed_objects, :polymorphic
2932
attr_accessor :embed_in_root, :embed_key, :key, :embedded_key, :root_key, :serializer_from_options, :options, :key_format, :embed_in_root_key, :embed_namespace
3033
alias embed_ids? embed_ids
3134
alias embed_objects? embed_objects
3235
alias embed_in_root? embed_in_root
3336
alias embed_in_root_key? embed_in_root_key
3437
alias embed_namespace? embed_namespace
38+
alias polymorphic? polymorphic
3539

3640
def embed=(embed)
3741
@embed_ids = embed == :id || embed == :ids
@@ -49,54 +53,6 @@ def default_serializer
4953
def build_serializer(object, options = {})
5054
serializer_class(object).new(object, options.merge(self.options))
5155
end
52-
53-
class HasOne < Association
54-
def initialize(name, *args)
55-
super
56-
@root_key = @embedded_key.to_s.pluralize
57-
@key ||= "#{name}_id"
58-
end
59-
60-
def serializer_class(object)
61-
serializer_from_options || serializer_from_object(object) || default_serializer
62-
end
63-
64-
def build_serializer(object, options = {})
65-
options[:_wrap_in_array] = embed_in_root?
66-
super
67-
end
68-
end
69-
70-
class HasMany < Association
71-
def initialize(name, *args)
72-
super
73-
@root_key = @embedded_key
74-
@key ||= "#{name.to_s.singularize}_ids"
75-
end
76-
77-
def serializer_class(object)
78-
if use_array_serializer?
79-
ArraySerializer
80-
else
81-
serializer_from_options
82-
end
83-
end
84-
85-
def options
86-
if use_array_serializer?
87-
{ each_serializer: serializer_from_options }.merge! super
88-
else
89-
super
90-
end
91-
end
92-
93-
private
94-
95-
def use_array_serializer?
96-
!serializer_from_options ||
97-
serializer_from_options && !(serializer_from_options <= ArraySerializer)
98-
end
99-
end
10056
end
10157
end
10258
end
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module ActiveModel
2+
class Serializer
3+
class Association
4+
class HasMany < Association
5+
def initialize(name, *args)
6+
super
7+
@root_key = @embedded_key
8+
@key ||= "#{name.to_s.singularize}_ids"
9+
end
10+
11+
def serializer_class(object)
12+
if use_array_serializer?
13+
ArraySerializer
14+
else
15+
serializer_from_options
16+
end
17+
end
18+
19+
def options
20+
if use_array_serializer?
21+
{ each_serializer: serializer_from_options }.merge! super
22+
else
23+
super
24+
end
25+
end
26+
27+
private
28+
29+
def use_array_serializer?
30+
!serializer_from_options ||
31+
serializer_from_options && !(serializer_from_options <= ArraySerializer)
32+
end
33+
end
34+
end
35+
end
36+
end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module ActiveModel
2+
class Serializer
3+
class Association
4+
class HasOne < Association
5+
def initialize(name, *args)
6+
super
7+
@root_key = @embedded_key.to_s.pluralize
8+
@key ||= "#{name}_id"
9+
end
10+
11+
def serializer_class(object)
12+
serializer_from_options || serializer_from_object(object) || default_serializer
13+
end
14+
15+
def build_serializer(object, options = {})
16+
options[:_wrap_in_array] = embed_in_root?
17+
super
18+
end
19+
end
20+
end
21+
end
22+
end

test/fixtures/poro.rb

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,25 @@ class Comment < Model
3838
class WebLog < Model
3939
end
4040

41+
class Interview < Model
42+
def attachment
43+
@attachment ||= Image.new(url: 'U1')
44+
end
45+
end
46+
47+
class Mail < Model
48+
def attachments
49+
@attachments ||= [Image.new(url: 'U1'),
50+
Video.new(html: 'H1')]
51+
end
52+
end
53+
54+
class Image < Model
55+
end
56+
57+
class Video < Model
58+
end
59+
4160
###
4261
## Serializers
4362
###
@@ -73,3 +92,23 @@ class WebLogSerializer < ActiveModel::Serializer
7392
class WebLogLowerCamelSerializer < WebLogSerializer
7493
format_keys :lower_camel
7594
end
95+
96+
class InterviewSerializer < ActiveModel::Serializer
97+
attributes :text
98+
99+
has_one :attachment, polymorphic: true
100+
end
101+
102+
class MailSerializer < ActiveModel::Serializer
103+
attributes :body
104+
105+
has_many :attachments, polymorphic: true
106+
end
107+
108+
class ImageSerializer < ActiveModel::Serializer
109+
attributes :url
110+
end
111+
112+
class VideoSerializer < ActiveModel::Serializer
113+
attributes :html
114+
end

0 commit comments

Comments
 (0)