From 3ecc3ed0c173b40be28226f025fa787ee9d05b73 Mon Sep 17 00:00:00 2001 From: bkoltai Date: Wed, 13 Jul 2016 09:48:07 -0700 Subject: [PATCH 1/4] Add documentation on upgrading from `0.8` to `0.10` safely --- docs/README.md | 1 + docs/howto/upgrade_from_0_8_to_0_10.md | 227 +++++++++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 docs/howto/upgrade_from_0_8_to_0_10.md diff --git a/docs/README.md b/docs/README.md index 62e779de..57abb0fa 100644 --- a/docs/README.md +++ b/docs/README.md @@ -28,6 +28,7 @@ This is the documentation of ActiveModelSerializers, it's focused on the **0.10. - [Testing ActiveModelSerializers](howto/test.md) - [Passing Arbitrary Options](howto/passing_arbitrary_options.md) - [How to serialize a Plain-Old Ruby Object (PORO)](howto/serialize_poro.md) +- [How to upgrade from `0.8` to `0.10` safely](howto/upgrade_from_0_8_to_0_10.md) ## Integrations diff --git a/docs/howto/upgrade_from_0_8_to_0_10.md b/docs/howto/upgrade_from_0_8_to_0_10.md new file mode 100644 index 00000000..0c76fcb6 --- /dev/null +++ b/docs/howto/upgrade_from_0_8_to_0_10.md @@ -0,0 +1,227 @@ +[Back to Guides](../README.md) + +# How to migrate from `0.8` to `0.10` safely + +## Disclaimer +### Proceed at your own risk +This document attempts to outline steps to upgrade your app based on the collective experience of +developers who have done this already. It may not cover all edge cases and situation that may cause issues, +so please proceed with a certain level of caution. + +## Overview +This document outlines the steps needed to migrate from `0.8` to `0.10`. The method described +below has been created via the collective knowledge of contributions of those who have done +the migration successfully. The method has been tested specifically for migrating from `0.8.3` +to `0.10.2`. + +The high level approach is to upgrade to `0.10` and change all serializers to use +a backwards-compatible `VersionEightSerializer`or `VersionEightCollectionSerializer` +and a `VersionEightAdapter`. After a few more manual changes, you should have the same +functionality as you had with `AMS 0.8`. Then, you can continue to develop in your app by creating +new serializers that don't use these backwards compatible versions and slowly migrate +existing serializers to the `0.10` versions as needed. + +## Steps to migrate + +### 1. Upgrade the `active_model_serializer` gem in you `Gemfile` +Change to `gem 'active_model_serializers', '~> 0.10'` and run `bundle install` + +### 2. Add `VersionEightSerializer` + +#### Code +```ruby +module ActiveModel + class VersionEightSerializer < Serializer + include Rails.application.routes.url_helpers + + # AMS 0.8 would delegate method calls from within the serializer to the + # object. + def method_missing(*args) + method = args.first + read_attribute_for_serialization(method) + end + + alias_method :options, :instance_options + + # Since attributes could be read from the `object` via `method_missing`, + # the `try` method did not behave as before. This patches `try` with the + # original implementation plus the addition of + # ` || object.respond_to?(a.first)` to check if the object responded to + # the given method. + def try(*a, &b) + if a.empty? || respond_to?(a.first) || object.respond_to?(a.first) + try!(*a, &b) + end + end + + # AMS 0.8 would return nil if the serializer was initialized with a nil + # resource. + def serializable_hash(adapter_options = nil, + options = {}, + adapter_instance = + self.class.serialization_adapter_instance) + object.nil? ? nil : super + end + end +end + +``` +Add this class to your app however you see fit. This is the class that your existing serializers +that inherit from `ActiveMode::Serializer` should inherit from. + +### 3. Add `VersionEightCollectionSerializer` +#### Code +```ruby +module ActiveModel + class Serializer + class VersionEightCollectionSerializer < CollectionSerializer + # In AMS 0.8, passing an ArraySerializer instance with a `root` option + # properly nested the serialized resources within the given root. + # Ex. + # + # class MyController < ActionController::Base + # def index + # render json: ActiveModel::Serializer::ArraySerializer + # .new(resources, root: "resources") + # end + # end + # + # Produced + # + # { + # "resources": [ + # , + # ... + # ] + # } + def as_json(options = {}) + if root + { + root => super + } + else + super + end + end + + # AMS 0.8 used `DefaultSerializer` if it couldn't find a serializer for + # the given resource. When not using an adapter, this is not true in + # `0.10` + def serializer_from_resource(resource, serializer_context_class, options) + serializer_class = + options.fetch(:serializer) { serializer_context_class.serializer_for(resource) } + + if serializer_class.nil? # rubocop:disable Style/GuardClause + DefaultSerializer.new(resource, options) + else + serializer_class.new(resource, options.except(:serializer)) + end + end + + class DefaultSerializer + attr_reader :object, :options + + def initialize(object, options={}) + @object, @options = object, options + end + + def serializable_hash + @object.as_json(@options) + end + end + end + end +end +``` +Add this class to your app however you see fit. This is the class that existing uses of +`ActiveMode::ArraySerializer` should be changed to use. + +### 4. Add `VersionEightAdapter` +#### Code +```ruby +module ActiveModelSerializers + module Adapter + class VersionEightAdapter < Base + def serializable_hash(options = nil) + options ||= {} + + if serializer.respond_to?(:each) + if serializer.root + delegate_to_json_adapter(options) + else + serializable_hash_for_collection(options) + end + else + serializable_hash_for_single_resource(options) + end + end + + def serializable_hash_for_collection(options) + serializer.map do |s| + VersionEightAdapter.new(s, instance_options) + .serializable_hash(options) + end + end + + def serializable_hash_for_single_resource(options) + if serializer.object.is_a?(ActiveModel::Serializer) + # It is recommended that you add some logging here to indicate + # places that should get converted to eventually allow for this + # adapter to get removed. + @serializer = serializer.object + end + + if serializer.root + delegate_to_json_adapter(options) + else + options = serialization_options(options) + serializer.serializable_hash(instance_options, options, self) + end + end + + def delegate_to_json_adapter(options) + ActiveModelSerializers::Adapter::Json + .new(serializer, instance_options) + .serializable_hash(options) + end + end + end +end +``` +Add this class to your app however you see fit. + +Add +```ruby +ActiveModelSerializers.config.adapter = + ActiveModelSerializers::Adapter::VersionEightAdapter +``` +to `config/active_model_serializer.rb` to configure AMS to use this +class as the default adapter. + +### 5. Change inheritors of `ActiveModel::Serializer` to inherit from `ActiveModel::VersionEightSerializer` +Simple find/replace + +### 6. Remove `private` keyword from serializers +Simple find/replace. This is required to allow the `ActiveModel::VersionEightSerializer` +to have proper access to the methods defined in the serializer. + +You may be able to change the `private` to `protected`, but this is hasn't been tested yet. + +### 7. Remove references to `ActiveRecord::Base#active_model_serializer` +This method is no longer supported in `0.10`. + +`0.10` does a good job of discovering serializers for `ActiveRecord` objects. + +### 8. Rename `ActiveModel::ArraySerializer` to `ActiveModel::Serializer::Version8CollectionSerializer` +Find/replace uses of `ActiveModel::ArraySerializer` with `ActiveModel::Serializer::Version8CollectionSerializer`. + +Also, be sure to change the `each_serializer` keyword to `serializer` when calling making the replacement. + +### 9. Replace uses of `@options` to `instance_options` in serializers +Simple find/replace + +## Conclusion +After you've done the steps above, you should test your app to ensure that everything is still working properly. + +If you run into issues, please contribute back to this document so others can benefit from your knowledge. + From e966d07b2c55ae30e9563c835f2b89e8333717bb Mon Sep 17 00:00:00 2001 From: bkoltai Date: Wed, 13 Jul 2016 10:10:50 -0700 Subject: [PATCH 2/4] PR comments - Add list of breaking changes - Add `true` param to `responds_to?` calls in overriden `try` --- docs/howto/upgrade_from_0_8_to_0_10.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/docs/howto/upgrade_from_0_8_to_0_10.md b/docs/howto/upgrade_from_0_8_to_0_10.md index 0c76fcb6..619713c1 100644 --- a/docs/howto/upgrade_from_0_8_to_0_10.md +++ b/docs/howto/upgrade_from_0_8_to_0_10.md @@ -21,6 +21,27 @@ functionality as you had with `AMS 0.8`. Then, you can continue to develop in yo new serializers that don't use these backwards compatible versions and slowly migrate existing serializers to the `0.10` versions as needed. +### Basic list `0.10` breaking changes +- Passing a serializer to `render json:` is no longer supported + - Ex. `render json: CustomerSerializer.new(customer)` +- Passing a nil resource to serializer now fails + - Ex. `CustomerSerializer.new(nil)` +- Attribute methods are no longer accessible from other serializer methods + - Ex. + ```ruby + class MySerializer + attributes :foo, :bar + + def foo + bar + 1 + end + end + ``` + - `root` option to collection serializer behaves differently + - Ex. `ActiveModel::ArraySerializer.new(resources, root: "resources")` +- No default serializer when serializer doesn't exist +- `@options` changed to `instance_options` + ## Steps to migrate ### 1. Upgrade the `active_model_serializer` gem in you `Gemfile` @@ -46,10 +67,10 @@ module ActiveModel # Since attributes could be read from the `object` via `method_missing`, # the `try` method did not behave as before. This patches `try` with the # original implementation plus the addition of - # ` || object.respond_to?(a.first)` to check if the object responded to + # ` || object.respond_to?(a.first, true)` to check if the object responded to # the given method. def try(*a, &b) - if a.empty? || respond_to?(a.first) || object.respond_to?(a.first) + if a.empty? || respond_to?(a.first, true) || object.respond_to?(a.first, true) try!(*a, &b) end end From 5e72ec4be73e2af21eb22fa2836fac6944580bff Mon Sep 17 00:00:00 2001 From: bkoltai Date: Wed, 13 Jul 2016 10:12:41 -0700 Subject: [PATCH 3/4] Fix small typo --- docs/howto/upgrade_from_0_8_to_0_10.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/upgrade_from_0_8_to_0_10.md b/docs/howto/upgrade_from_0_8_to_0_10.md index 619713c1..b26eb4a1 100644 --- a/docs/howto/upgrade_from_0_8_to_0_10.md +++ b/docs/howto/upgrade_from_0_8_to_0_10.md @@ -21,7 +21,7 @@ functionality as you had with `AMS 0.8`. Then, you can continue to develop in yo new serializers that don't use these backwards compatible versions and slowly migrate existing serializers to the `0.10` versions as needed. -### Basic list `0.10` breaking changes +### `0.10` breaking changes - Passing a serializer to `render json:` is no longer supported - Ex. `render json: CustomerSerializer.new(customer)` - Passing a nil resource to serializer now fails From d9ba5cd768db2c3670d36a7a676a9e37fa484c23 Mon Sep 17 00:00:00 2001 From: bkoltai Date: Wed, 20 Jul 2016 20:58:24 -0700 Subject: [PATCH 4/4] Fix typos and make examples more explicit --- docs/howto/upgrade_from_0_8_to_0_10.md | 139 ++++++++++++++----------- 1 file changed, 77 insertions(+), 62 deletions(-) diff --git a/docs/howto/upgrade_from_0_8_to_0_10.md b/docs/howto/upgrade_from_0_8_to_0_10.md index b26eb4a1..2530e239 100644 --- a/docs/howto/upgrade_from_0_8_to_0_10.md +++ b/docs/howto/upgrade_from_0_8_to_0_10.md @@ -5,7 +5,7 @@ ## Disclaimer ### Proceed at your own risk This document attempts to outline steps to upgrade your app based on the collective experience of -developers who have done this already. It may not cover all edge cases and situation that may cause issues, +developers who have done this already. It may not cover all edge cases and situations that may cause issues, so please proceed with a certain level of caution. ## Overview @@ -15,30 +15,46 @@ the migration successfully. The method has been tested specifically for migratin to `0.10.2`. The high level approach is to upgrade to `0.10` and change all serializers to use -a backwards-compatible `VersionEightSerializer`or `VersionEightCollectionSerializer` -and a `VersionEightAdapter`. After a few more manual changes, you should have the same +a backwards-compatible `ActiveModel::V08::Serializer`or `ActiveModel::V08::CollectionSerializer` +and a `ActiveModelSerializers::Adapter::V08Adapter`. After a few more manual changes, you should have the same functionality as you had with `AMS 0.8`. Then, you can continue to develop in your app by creating new serializers that don't use these backwards compatible versions and slowly migrate existing serializers to the `0.10` versions as needed. ### `0.10` breaking changes - Passing a serializer to `render json:` is no longer supported - - Ex. `render json: CustomerSerializer.new(customer)` + +```ruby +render json: CustomerSerializer.new(customer) # rendered in 0.8, errors in 0.10 +``` + - Passing a nil resource to serializer now fails - - Ex. `CustomerSerializer.new(nil)` -- Attribute methods are no longer accessible from other serializer methods - - Ex. - ```ruby - class MySerializer - attributes :foo, :bar - - def foo - bar + 1 - end - end - ``` + +```ruby +CustomerSerializer.new(nil) # returned nil in 0.8, throws error in 0.10 +``` + +- Attribute methods are no longer defined on the serializer, and must be explicitly + accessed through `object` + +```ruby +class MySerializer + attributes :foo, :bar + + def foo + bar + 1 # bar does not work, needs to be object.bar in 0.10 + end +end +``` + - `root` option to collection serializer behaves differently - - Ex. `ActiveModel::ArraySerializer.new(resources, root: "resources")` + +```ruby +# in 0.8 +ActiveModel::ArraySerializer.new(resources, root: "resources") +# resulted in { "resources": }, does not work in 0.10 +``` + - No default serializer when serializer doesn't exist - `@options` changed to `instance_options` @@ -47,41 +63,42 @@ existing serializers to the `0.10` versions as needed. ### 1. Upgrade the `active_model_serializer` gem in you `Gemfile` Change to `gem 'active_model_serializers', '~> 0.10'` and run `bundle install` -### 2. Add `VersionEightSerializer` +### 2. Add `ActiveModel::V08::Serializer` -#### Code ```ruby module ActiveModel - class VersionEightSerializer < Serializer - include Rails.application.routes.url_helpers - - # AMS 0.8 would delegate method calls from within the serializer to the - # object. - def method_missing(*args) - method = args.first - read_attribute_for_serialization(method) - end - - alias_method :options, :instance_options - - # Since attributes could be read from the `object` via `method_missing`, - # the `try` method did not behave as before. This patches `try` with the - # original implementation plus the addition of - # ` || object.respond_to?(a.first, true)` to check if the object responded to - # the given method. - def try(*a, &b) - if a.empty? || respond_to?(a.first, true) || object.respond_to?(a.first, true) - try!(*a, &b) + module V08 + class Serializer < ActiveModel::Serializer + include Rails.application.routes.url_helpers + + # AMS 0.8 would delegate method calls from within the serializer to the + # object. + def method_missing(*args) + method = args.first + read_attribute_for_serialization(method) + end + + alias_method :options, :instance_options + + # Since attributes could be read from the `object` via `method_missing`, + # the `try` method did not behave as before. This patches `try` with the + # original implementation plus the addition of + # ` || object.respond_to?(a.first, true)` to check if the object responded to + # the given method. + def try(*a, &b) + if a.empty? || respond_to?(a.first, true) || object.respond_to?(a.first, true) + try!(*a, &b) + end + end + + # AMS 0.8 would return nil if the serializer was initialized with a nil + # resource. + def serializable_hash(adapter_options = nil, + options = {}, + adapter_instance = + self.class.serialization_adapter_instance) + object.nil? ? nil : super end - end - - # AMS 0.8 would return nil if the serializer was initialized with a nil - # resource. - def serializable_hash(adapter_options = nil, - options = {}, - adapter_instance = - self.class.serialization_adapter_instance) - object.nil? ? nil : super end end end @@ -90,12 +107,11 @@ end Add this class to your app however you see fit. This is the class that your existing serializers that inherit from `ActiveMode::Serializer` should inherit from. -### 3. Add `VersionEightCollectionSerializer` -#### Code +### 3. Add `ActiveModel::V08::CollectionSerializer` ```ruby module ActiveModel - class Serializer - class VersionEightCollectionSerializer < CollectionSerializer + module V08 + class CollectionSerializer < ActiveModel::Serializer::CollectionSerializer # In AMS 0.8, passing an ArraySerializer instance with a `root` option # properly nested the serialized resources within the given root. # Ex. @@ -155,14 +171,13 @@ module ActiveModel end ``` Add this class to your app however you see fit. This is the class that existing uses of -`ActiveMode::ArraySerializer` should be changed to use. +`ActiveModel::ArraySerializer` should be changed to use. -### 4. Add `VersionEightAdapter` -#### Code +### 4. Add `ActiveModelSerializers::Adapter::V08Adapter` ```ruby module ActiveModelSerializers module Adapter - class VersionEightAdapter < Base + class V08Adapter < ActiveModelSerializers::Adapter::Base def serializable_hash(options = nil) options ||= {} @@ -179,7 +194,7 @@ module ActiveModelSerializers def serializable_hash_for_collection(options) serializer.map do |s| - VersionEightAdapter.new(s, instance_options) + V08Adapter.new(s, instance_options) .serializable_hash(options) end end @@ -214,16 +229,16 @@ Add this class to your app however you see fit. Add ```ruby ActiveModelSerializers.config.adapter = - ActiveModelSerializers::Adapter::VersionEightAdapter + ActiveModelSerializers::Adapter::V08Adapter ``` to `config/active_model_serializer.rb` to configure AMS to use this class as the default adapter. -### 5. Change inheritors of `ActiveModel::Serializer` to inherit from `ActiveModel::VersionEightSerializer` +### 5. Change inheritors of `ActiveModel::Serializer` to inherit from `ActiveModel::V08::Serializer` Simple find/replace ### 6. Remove `private` keyword from serializers -Simple find/replace. This is required to allow the `ActiveModel::VersionEightSerializer` +Simple find/replace. This is required to allow the `ActiveModel::V08::Serializer` to have proper access to the methods defined in the serializer. You may be able to change the `private` to `protected`, but this is hasn't been tested yet. @@ -233,8 +248,8 @@ This method is no longer supported in `0.10`. `0.10` does a good job of discovering serializers for `ActiveRecord` objects. -### 8. Rename `ActiveModel::ArraySerializer` to `ActiveModel::Serializer::Version8CollectionSerializer` -Find/replace uses of `ActiveModel::ArraySerializer` with `ActiveModel::Serializer::Version8CollectionSerializer`. +### 8. Rename `ActiveModel::ArraySerializer` to `ActiveModel::V08::CollectionSerializer` +Find/replace uses of `ActiveModel::ArraySerializer` with `ActiveModel::V08::CollectionSerializer`. Also, be sure to change the `each_serializer` keyword to `serializer` when calling making the replacement.