Skip to content

Commit 9217bc2

Browse files
authored
Merge pull request #1844 from bkoltai/bk_upgrade_0_8_to_0_10_doc
Add documentation on upgrading from `0.8` to `0.10` safely
2 parents e8343a9 + d9ba5cd commit 9217bc2

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ This is the documentation of ActiveModelSerializers, it's focused on the **0.10.
2828
- [Testing ActiveModelSerializers](howto/test.md)
2929
- [Passing Arbitrary Options](howto/passing_arbitrary_options.md)
3030
- [How to serialize a Plain-Old Ruby Object (PORO)](howto/serialize_poro.md)
31+
- [How to upgrade from `0.8` to `0.10` safely](howto/upgrade_from_0_8_to_0_10.md)
3132

3233
## Integrations
3334

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
[Back to Guides](../README.md)
2+
3+
# How to migrate from `0.8` to `0.10` safely
4+
5+
## Disclaimer
6+
### Proceed at your own risk
7+
This document attempts to outline steps to upgrade your app based on the collective experience of
8+
developers who have done this already. It may not cover all edge cases and situations that may cause issues,
9+
so please proceed with a certain level of caution.
10+
11+
## Overview
12+
This document outlines the steps needed to migrate from `0.8` to `0.10`. The method described
13+
below has been created via the collective knowledge of contributions of those who have done
14+
the migration successfully. The method has been tested specifically for migrating from `0.8.3`
15+
to `0.10.2`.
16+
17+
The high level approach is to upgrade to `0.10` and change all serializers to use
18+
a backwards-compatible `ActiveModel::V08::Serializer`or `ActiveModel::V08::CollectionSerializer`
19+
and a `ActiveModelSerializers::Adapter::V08Adapter`. After a few more manual changes, you should have the same
20+
functionality as you had with `AMS 0.8`. Then, you can continue to develop in your app by creating
21+
new serializers that don't use these backwards compatible versions and slowly migrate
22+
existing serializers to the `0.10` versions as needed.
23+
24+
### `0.10` breaking changes
25+
- Passing a serializer to `render json:` is no longer supported
26+
27+
```ruby
28+
render json: CustomerSerializer.new(customer) # rendered in 0.8, errors in 0.10
29+
```
30+
31+
- Passing a nil resource to serializer now fails
32+
33+
```ruby
34+
CustomerSerializer.new(nil) # returned nil in 0.8, throws error in 0.10
35+
```
36+
37+
- Attribute methods are no longer defined on the serializer, and must be explicitly
38+
accessed through `object`
39+
40+
```ruby
41+
class MySerializer
42+
attributes :foo, :bar
43+
44+
def foo
45+
bar + 1 # bar does not work, needs to be object.bar in 0.10
46+
end
47+
end
48+
```
49+
50+
- `root` option to collection serializer behaves differently
51+
52+
```ruby
53+
# in 0.8
54+
ActiveModel::ArraySerializer.new(resources, root: "resources")
55+
# resulted in { "resources": <serialized_resources> }, does not work in 0.10
56+
```
57+
58+
- No default serializer when serializer doesn't exist
59+
- `@options` changed to `instance_options`
60+
61+
## Steps to migrate
62+
63+
### 1. Upgrade the `active_model_serializer` gem in you `Gemfile`
64+
Change to `gem 'active_model_serializers', '~> 0.10'` and run `bundle install`
65+
66+
### 2. Add `ActiveModel::V08::Serializer`
67+
68+
```ruby
69+
module ActiveModel
70+
module V08
71+
class Serializer < ActiveModel::Serializer
72+
include Rails.application.routes.url_helpers
73+
74+
# AMS 0.8 would delegate method calls from within the serializer to the
75+
# object.
76+
def method_missing(*args)
77+
method = args.first
78+
read_attribute_for_serialization(method)
79+
end
80+
81+
alias_method :options, :instance_options
82+
83+
# Since attributes could be read from the `object` via `method_missing`,
84+
# the `try` method did not behave as before. This patches `try` with the
85+
# original implementation plus the addition of
86+
# ` || object.respond_to?(a.first, true)` to check if the object responded to
87+
# the given method.
88+
def try(*a, &b)
89+
if a.empty? || respond_to?(a.first, true) || object.respond_to?(a.first, true)
90+
try!(*a, &b)
91+
end
92+
end
93+
94+
# AMS 0.8 would return nil if the serializer was initialized with a nil
95+
# resource.
96+
def serializable_hash(adapter_options = nil,
97+
options = {},
98+
adapter_instance =
99+
self.class.serialization_adapter_instance)
100+
object.nil? ? nil : super
101+
end
102+
end
103+
end
104+
end
105+
106+
```
107+
Add this class to your app however you see fit. This is the class that your existing serializers
108+
that inherit from `ActiveMode::Serializer` should inherit from.
109+
110+
### 3. Add `ActiveModel::V08::CollectionSerializer`
111+
```ruby
112+
module ActiveModel
113+
module V08
114+
class CollectionSerializer < ActiveModel::Serializer::CollectionSerializer
115+
# In AMS 0.8, passing an ArraySerializer instance with a `root` option
116+
# properly nested the serialized resources within the given root.
117+
# Ex.
118+
#
119+
# class MyController < ActionController::Base
120+
# def index
121+
# render json: ActiveModel::Serializer::ArraySerializer
122+
# .new(resources, root: "resources")
123+
# end
124+
# end
125+
#
126+
# Produced
127+
#
128+
# {
129+
# "resources": [
130+
# <serialized_resource>,
131+
# ...
132+
# ]
133+
# }
134+
def as_json(options = {})
135+
if root
136+
{
137+
root => super
138+
}
139+
else
140+
super
141+
end
142+
end
143+
144+
# AMS 0.8 used `DefaultSerializer` if it couldn't find a serializer for
145+
# the given resource. When not using an adapter, this is not true in
146+
# `0.10`
147+
def serializer_from_resource(resource, serializer_context_class, options)
148+
serializer_class =
149+
options.fetch(:serializer) { serializer_context_class.serializer_for(resource) }
150+
151+
if serializer_class.nil? # rubocop:disable Style/GuardClause
152+
DefaultSerializer.new(resource, options)
153+
else
154+
serializer_class.new(resource, options.except(:serializer))
155+
end
156+
end
157+
158+
class DefaultSerializer
159+
attr_reader :object, :options
160+
161+
def initialize(object, options={})
162+
@object, @options = object, options
163+
end
164+
165+
def serializable_hash
166+
@object.as_json(@options)
167+
end
168+
end
169+
end
170+
end
171+
end
172+
```
173+
Add this class to your app however you see fit. This is the class that existing uses of
174+
`ActiveModel::ArraySerializer` should be changed to use.
175+
176+
### 4. Add `ActiveModelSerializers::Adapter::V08Adapter`
177+
```ruby
178+
module ActiveModelSerializers
179+
module Adapter
180+
class V08Adapter < ActiveModelSerializers::Adapter::Base
181+
def serializable_hash(options = nil)
182+
options ||= {}
183+
184+
if serializer.respond_to?(:each)
185+
if serializer.root
186+
delegate_to_json_adapter(options)
187+
else
188+
serializable_hash_for_collection(options)
189+
end
190+
else
191+
serializable_hash_for_single_resource(options)
192+
end
193+
end
194+
195+
def serializable_hash_for_collection(options)
196+
serializer.map do |s|
197+
V08Adapter.new(s, instance_options)
198+
.serializable_hash(options)
199+
end
200+
end
201+
202+
def serializable_hash_for_single_resource(options)
203+
if serializer.object.is_a?(ActiveModel::Serializer)
204+
# It is recommended that you add some logging here to indicate
205+
# places that should get converted to eventually allow for this
206+
# adapter to get removed.
207+
@serializer = serializer.object
208+
end
209+
210+
if serializer.root
211+
delegate_to_json_adapter(options)
212+
else
213+
options = serialization_options(options)
214+
serializer.serializable_hash(instance_options, options, self)
215+
end
216+
end
217+
218+
def delegate_to_json_adapter(options)
219+
ActiveModelSerializers::Adapter::Json
220+
.new(serializer, instance_options)
221+
.serializable_hash(options)
222+
end
223+
end
224+
end
225+
end
226+
```
227+
Add this class to your app however you see fit.
228+
229+
Add
230+
```ruby
231+
ActiveModelSerializers.config.adapter =
232+
ActiveModelSerializers::Adapter::V08Adapter
233+
```
234+
to `config/active_model_serializer.rb` to configure AMS to use this
235+
class as the default adapter.
236+
237+
### 5. Change inheritors of `ActiveModel::Serializer` to inherit from `ActiveModel::V08::Serializer`
238+
Simple find/replace
239+
240+
### 6. Remove `private` keyword from serializers
241+
Simple find/replace. This is required to allow the `ActiveModel::V08::Serializer`
242+
to have proper access to the methods defined in the serializer.
243+
244+
You may be able to change the `private` to `protected`, but this is hasn't been tested yet.
245+
246+
### 7. Remove references to `ActiveRecord::Base#active_model_serializer`
247+
This method is no longer supported in `0.10`.
248+
249+
`0.10` does a good job of discovering serializers for `ActiveRecord` objects.
250+
251+
### 8. Rename `ActiveModel::ArraySerializer` to `ActiveModel::V08::CollectionSerializer`
252+
Find/replace uses of `ActiveModel::ArraySerializer` with `ActiveModel::V08::CollectionSerializer`.
253+
254+
Also, be sure to change the `each_serializer` keyword to `serializer` when calling making the replacement.
255+
256+
### 9. Replace uses of `@options` to `instance_options` in serializers
257+
Simple find/replace
258+
259+
## Conclusion
260+
After you've done the steps above, you should test your app to ensure that everything is still working properly.
261+
262+
If you run into issues, please contribute back to this document so others can benefit from your knowledge.
263+

0 commit comments

Comments
 (0)