From 1c820a9ba7ca80f9bd06f2916cda41d71c96ca53 Mon Sep 17 00:00:00 2001 From: Peter Harkins Date: Thu, 21 Jun 2012 11:12:13 -0500 Subject: [PATCH 01/37] Allow setting the serializer for ArraySerializer. --- lib/active_model/serializer.rb | 8 +++++++- test/serializer_test.rb | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index fa1cfd19..6c63f654 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -40,7 +40,13 @@ module ActiveModel def serializable_array @object.map do |item| - if item.respond_to?(:active_model_serializer) && (serializer = item.active_model_serializer) + if @options.has_key? :each_serializer + serializer = @options[:each_serializer] + elsif item.respond_to?(:active_model_serializer) + serializer = item.active_model_serializer + end + + if serializer serializer.new(item, @options) else item diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 082cd6bf..4d02715e 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -411,6 +411,24 @@ class SerializerTest < ActiveModel::TestCase assert_equal({ :items => [ hash.as_json ]}, serializer.as_json) end + class CustomPostSerializer < ActiveModel::Serializer + attributes :title + end + + def test_array_serializer_with_specified_seriailizer + post1 = Post.new(:title => "Post1", :author => "Author1", :id => 1) + post2 = Post.new(:title => "Post2", :author => "Author2", :id => 2) + + array = [ post1, post2 ] + + serializer = array.active_model_serializer.new array, :each_serializer => CustomPostSerializer + + assert_equal([ + { :title => "Post1" }, + { :title => "Post2" } + ], serializer.as_json) + end + class CustomBlog < Blog attr_accessor :public_posts, :public_user end From 7eb2b90b7c8f9d561e49d2da914ad843a2f88723 Mon Sep 17 00:00:00 2001 From: Bradley Priest Date: Tue, 10 Jul 2012 10:17:54 +0800 Subject: [PATCH 02/37] Automatically include ArraySerializer in ActiveRecord::Relation fixes #81 --- lib/active_model/serializer.rb | 10 ++++++++-- test/serializer_support_test.rb | 21 ++++++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index fa1cfd19..de54646f 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -524,9 +524,15 @@ module ActiveModel end end -class Array - # Array uses ActiveModel::ArraySerializer. +module ActiveModel::ArraySerializerSupport def active_model_serializer ActiveModel::ArraySerializer end end + +Array.send(:include, ActiveModel::ArraySerializerSupport) + +ActiveSupport.on_load(:active_record) do + ActiveRecord::Relation.send(:include, ActiveModel::ArraySerializerSupport) +end + diff --git a/test/serializer_support_test.rb b/test/serializer_support_test.rb index b2d2b8be..2e963027 100644 --- a/test/serializer_support_test.rb +++ b/test/serializer_support_test.rb @@ -4,8 +4,27 @@ class RandomModel include ActiveModel::SerializerSupport end +class RandomModelCollection + include ActiveModel::ArraySerializerSupport +end + +module ActiveRecord + class Relation + end +end + class SerializerSupportTest < ActiveModel::TestCase test "it returns nil if no serializer exists" do assert_equal nil, RandomModel.new.active_model_serializer end -end \ No newline at end of file + + test "it returns ArraySerializer for a collection" do + assert_equal ActiveModel::ArraySerializer, RandomModelCollection.new.active_model_serializer + end + + test "it automatically includes array_serializer in active_record/relation" do + ActiveSupport.run_load_hooks(:active_record) + assert_equal ActiveModel::ArraySerializer, ActiveRecord::Relation.new.active_model_serializer + end +end + From f41978b8de5bc58a0ee7338e3245d4d99a0dde80 Mon Sep 17 00:00:00 2001 From: Bradley Priest Date: Tue, 10 Jul 2012 10:23:45 +0800 Subject: [PATCH 03/37] move array_serializer logic to active_model_serializer.rb --- lib/active_model/serializer.rb | 15 +-------------- lib/active_model_serializers.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index de54646f..bc96fbfa 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -522,17 +522,4 @@ module ActiveModel ActiveSupport::Notifications.instrument("#{name}.serializer", payload, &block) end end -end - -module ActiveModel::ArraySerializerSupport - def active_model_serializer - ActiveModel::ArraySerializer - end -end - -Array.send(:include, ActiveModel::ArraySerializerSupport) - -ActiveSupport.on_load(:active_record) do - ActiveRecord::Relation.send(:include, ActiveModel::ArraySerializerSupport) -end - +end \ No newline at end of file diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index da4c1780..18679652 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -56,6 +56,18 @@ ActiveSupport.on_load(:active_record) do include ActiveModel::SerializerSupport end +module ActiveModel::ArraySerializerSupport + def active_model_serializer + ActiveModel::ArraySerializer + end +end + +Array.send(:include, ActiveModel::ArraySerializerSupport) + +ActiveSupport.on_load(:active_record) do + ActiveRecord::Relation.send(:include, ActiveModel::ArraySerializerSupport) +end + begin require 'action_controller' require 'action_controller/serialization' From 2b9cd974368a44983a52853adcbcd31958931b84 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Sat, 14 Jul 2012 13:12:26 +0200 Subject: [PATCH 04/37] Close #86 --- lib/active_model/serializer.rb | 4 ++-- test/serializer_test.rb | 44 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index bc96fbfa..b103f8a4 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -257,7 +257,7 @@ module ActiveModel end def attribute(attr, options={}) - self._attributes = _attributes.merge(attr => options[:key] || attr) + self._attributes = _attributes.merge(attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym) unless method_defined?(attr) class_eval "def #{attr}() object.read_attribute_for_serialization(:#{attr}) end", __FILE__, __LINE__ @@ -522,4 +522,4 @@ module ActiveModel ActiveSupport::Notifications.instrument("#{name}.serializer", payload, &block) end end -end \ No newline at end of file +end diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 082cd6bf..0928f5c0 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -890,4 +890,48 @@ class SerializerTest < ActiveModel::TestCase end assert_equal ActiveModel::Serializer, loaded end + + def tests_query_attributes_strip_question_mark + todo = Class.new do + def overdue? + true + end + + def read_attribute_for_serialization(name) + send name + end + end + + serializer = Class.new(ActiveModel::Serializer) do + attribute :overdue? + end + + actual = serializer.new(todo.new).as_json + + assert_equal({ + :overdue => true + }, actual) + end + + def tests_query_attributes_allow_key_option + todo = Class.new do + def overdue? + true + end + + def read_attribute_for_serialization(name) + send name + end + end + + serializer = Class.new(ActiveModel::Serializer) do + attribute :overdue?, :key => :foo + end + + actual = serializer.new(todo.new).as_json + + assert_equal({ + :foo => true + }, actual) + end end From 7936e3efbaada219b64e20e9491537bff084c724 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Sat, 14 Jul 2012 14:54:23 +0200 Subject: [PATCH 05/37] Add "scope" method --- README.markdown | 4 ++-- lib/active_model/serializer.rb | 7 ++++++- test/serializer_test.rb | 5 +++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/README.markdown b/README.markdown index 94292668..5fab7d2d 100644 --- a/README.markdown +++ b/README.markdown @@ -126,12 +126,12 @@ class PostSerializer < ActiveModel::Serializer # only let the user see comments he created. def comments - post.comments.where(:created_by => options[:scope]) + post.comments.where(:created_by => scope) end end ``` -In a serializer, `options[:scope]` is the current authorization scope (usually +In a serializer, `scope` is the current authorization scope (usually `current_user`), which the controller gives to the serializer when you call `render :json` diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 8c177581..b2eac4ee 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -103,7 +103,7 @@ module ActiveModel # end # # def author? - # post.author == options[:scope] + # post.author == scope # end # end # @@ -520,6 +520,11 @@ module ActiveModel hash end + # Returns options[:scope] + def scope + @options[:scope] + end + alias :read_attribute_for_serialization :send # Use ActiveSupport::Notifications to send events to external systems. diff --git a/test/serializer_test.rb b/test/serializer_test.rb index bdd20ae3..75c148b3 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -94,6 +94,11 @@ class SerializerTest < ActiveModel::TestCase has_many :comments, :serializer => CommentSerializer end + def test_scope_works_correct + serializer = ActiveModel::Serializer.new :foo, :scope => :bar + asser_equal serializer.scope, :bar + end + def test_attributes user = User.new user_serializer = DefaultUserSerializer.new(user, {}) From 56824f055b4899b6be60c330cccb85f599aed6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 14 Jul 2012 18:17:36 +0300 Subject: [PATCH 06/37] Update master --- test/serializer_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 75c148b3..288af48a 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -96,7 +96,7 @@ class SerializerTest < ActiveModel::TestCase def test_scope_works_correct serializer = ActiveModel::Serializer.new :foo, :scope => :bar - asser_equal serializer.scope, :bar + assert_equal serializer.scope, :bar end def test_attributes From 0832e4291736f652d296f1387a6891557fc8f8b0 Mon Sep 17 00:00:00 2001 From: Tee Parham Date: Sat, 14 Jul 2012 21:44:23 -0600 Subject: [PATCH 07/37] add class attribute :root to ArraySerializer You can now set the default behavior for Array serialization in a single place --- lib/action_controller/serialization.rb | 12 ++++++---- lib/active_model/serializer.rb | 9 +++++++- test/serialization_test.rb | 31 ++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index 4230e1d0..d52c6800 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -40,13 +40,17 @@ module ActionController end def _render_option_json(json, options) - if json.respond_to?(:to_ary) - options[:root] ||= controller_name unless options[:root] == false - end - serializer = options.delete(:serializer) || (json.respond_to?(:active_model_serializer) && json.active_model_serializer) + if json.respond_to?(:to_ary) + if options[:root] != false && serializer.root != false + # default root element for arrays is serializer's root or the controller name + # the serializer for an Array is ActiveModel::ArraySerializer + options[:root] ||= serializer.root || controller_name + end + end + if serializer options[:scope] = serialization_scope options[:url_options] = url_options diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index b2eac4ee..6868369a 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -29,11 +29,18 @@ module ActiveModel # Active Model Array Serializer # - # It serializes an array checking if each element that implements + # It serializes an Array, checking if each element that implements # the +active_model_serializer+ method. + # + # To disable serialization of root elements, in an initializer: + # + # ActiveModel::ArraySerializer.root = false + # class ArraySerializer attr_reader :object, :options + class_attribute :root + def initialize(object, options={}) @object, @options = object, options end diff --git a/test/serialization_test.rb b/test/serialization_test.rb index d382f2ce..3e7d72ad 100644 --- a/test/serialization_test.rb +++ b/test/serialization_test.rb @@ -66,6 +66,10 @@ class RenderJsonTest < ActionController::TestCase end end + class CustomArraySerializer < ActiveModel::ArraySerializer + self.root = "items" + end + class TestController < ActionController::Base protect_from_forgery @@ -144,6 +148,14 @@ class RenderJsonTest < ActionController::TestCase render :json => [], :root => false end + def render_json_empty_array + render :json => [] + end + + def render_json_array_with_custom_array_serializer + render :json => [], :serializer => CustomArraySerializer + end + private def default_serializer_options @@ -260,4 +272,23 @@ class RenderJsonTest < ActionController::TestCase get :render_json_array_with_no_root assert_equal '[]', @response.body end + + def test_render_json_empty_array + get :render_json_empty_array + assert_equal '{"test":[]}', @response.body + end + + def test_render_json_empty_arry_with_array_serializer_root_false + ActiveModel::ArraySerializer.root = false + get :render_json_empty_array + assert_equal '[]', @response.body + ensure # teardown + ActiveModel::ArraySerializer.root = nil + end + + def test_render_json_array_with_custom_array_serializer + get :render_json_array_with_custom_array_serializer + assert_equal '{"items":[]}', @response.body + end + end From 84c7cfa988b9c65d6d05f7aa3ed817ed96c625d2 Mon Sep 17 00:00:00 2001 From: Tee Parham Date: Sat, 14 Jul 2012 22:03:14 -0600 Subject: [PATCH 08/37] fix test for custom serializer, add test for :each_serializer --- test/serialization_test.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/serialization_test.rb b/test/serialization_test.rb index 3e7d72ad..9706f70a 100644 --- a/test/serialization_test.rb +++ b/test/serialization_test.rb @@ -136,8 +136,14 @@ class RenderJsonTest < ActionController::TestCase render :json => JsonSerializable.new(true) end + # To specify a custom serializer for an object, use :serializer. def render_json_with_custom_serializer - render :json => [], :serializer => CustomSerializer + render :json => Object.new, :serializer => CustomSerializer + end + + # To specify a custom serializer for each item in the Array, use :each_serializer. + def render_json_array_with_custom_serializer + render :json => [Object.new], :each_serializer => CustomSerializer end def render_json_with_links @@ -263,6 +269,11 @@ class RenderJsonTest < ActionController::TestCase assert_match '{"hello":true}', @response.body end + def test_render_json_array_with_custom_serializer + get :render_json_array_with_custom_serializer + assert_match '{"test":[{"hello":true}]}', @response.body + end + def test_render_json_with_links get :render_json_with_links assert_match '{"link":"http://www.nextangle.com/hypermedia"}', @response.body From 32f87791149598dbcb840e1994f45b73a1b9a044 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Sun, 22 Apr 2012 18:11:16 -0700 Subject: [PATCH 09/37] Basic rooted polymorphism --- lib/active_model/serializer.rb | 15 +++++++++++- test/serializer_test.rb | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index b2eac4ee..7ac2874a 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -216,13 +216,26 @@ module ActiveModel end class HasOne < Config #:nodoc: + def polymorphic? + option :polymorphic + end + + def polymoprhic_key + associated_object.class.to_s.demodulize.underscore.to_sym + end + def plural_key key.to_s.pluralize.to_sym end def serialize object = associated_object - object && find_serializable(object).serializable_hash + + if object && polymorphic? + { polymoprhic_key => find_serializable(object).serializable_hash } + elsif object + find_serializable(object).serializable_hash + end end def serialize_many diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 288af48a..d1473f1a 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -957,4 +957,46 @@ class SerializerTest < ActiveModel::TestCase :foo => true }, actual) end + + # Set up some classes for polymorphic testing + class Attachment < Model + def attachable + @attributes[:attachable] + end + end + + def tests_can_handle_polymorphism + email_serializer = Class.new(ActiveModel::Serializer) do + attributes :subject, :body + end + + email_class = Class.new(Model) do + def self.to_s + "Email" + end + + define_method :active_model_serializer do + email_serializer + end + end + + attachment_serializer = Class.new(ActiveModel::Serializer) do + attributes :name, :url + has_one :attachable, :polymorphic => true + end + + email = email_class.new :subject => 'foo', :body => 'bar' + + attachment = Attachment.new :name => 'logo.png', :url => 'http://example.com/logo.png', :attachable => email + + actual = attachment_serializer.new(attachment, {}).as_json + + assert_equal({ + :name => 'logo.png', + :url => 'http://example.com/logo.png', + :attachable => { + :email => { :subject => 'foo', :body => 'bar' } + } + }, actual) + end end From 7e96856b87ebb91f4ae453261661bbe7bdc9062c Mon Sep 17 00:00:00 2001 From: twinturbo Date: Sun, 22 Apr 2012 18:17:10 -0700 Subject: [PATCH 10/37] Support serialize polymorphic id --- lib/active_model/serializer.rb | 6 +++++- test/serializer_test.rb | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 7ac2874a..8d6bf83a 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -245,7 +245,11 @@ module ActiveModel end def serialize_ids - if object = associated_object + object = associated_object + + if object && polymorphic? + { polymoprhic_key => object.read_attribute_for_serialization(:id) } + elsif object object.read_attribute_for_serialization(:id) else nil diff --git a/test/serializer_test.rb b/test/serializer_test.rb index d1473f1a..77dc32e7 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -999,4 +999,40 @@ class SerializerTest < ActiveModel::TestCase } }, actual) end + + def test_can_handle_polymoprhic_ids + email_serializer = Class.new(ActiveModel::Serializer) do + attributes :subject, :body + end + + email_class = Class.new(Model) do + def self.to_s + "Email" + end + + define_method :active_model_serializer do + email_serializer + end + end + + attachment_serializer = Class.new(ActiveModel::Serializer) do + embed :ids + attributes :name, :url + has_one :attachable, :polymorphic => true + end + + email = email_class.new :id => 1 + + attachment = Attachment.new :name => 'logo.png', :url => 'http://example.com/logo.png', :attachable => email + + actual = attachment_serializer.new(attachment, {}).as_json + + assert_equal({ + :name => 'logo.png', + :url => 'http://example.com/logo.png', + :attachable => { + :email => 1 + } + }, actual) + end end From 3ca1621011c67b2612197f8371b2ce9029695dc9 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Mon, 23 Apr 2012 20:37:32 -0500 Subject: [PATCH 11/37] Add failing test for polymorphic with include --- test/serializer_test.rb | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 77dc32e7..132d0b85 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -1035,4 +1035,45 @@ class SerializerTest < ActiveModel::TestCase } }, actual) end + + def test_polymorphic_associations_are_included_at_root + email_serializer = Class.new(ActiveModel::Serializer) do + attributes :subject, :body + end + + email_class = Class.new(Model) do + def self.to_s + "Email" + end + + define_method :active_model_serializer do + email_serializer + end + end + + attachment_serializer = Class.new(ActiveModel::Serializer) do + embed :ids, :include => true + attributes :name, :url + has_one :attachable, :polymorphic => true + end + + email = email_class.new :id => 1, :subject => "Hello", :body => "World" + + attachment = Attachment.new :name => 'logo.png', :url => 'http://example.com/logo.png', :attachable => email + + actual = attachment_serializer.new(attachment, {}).as_json + + assert_equal({ + :name => 'logo.png', + :url => 'http://example.com/logo.png', + :attachable => { + :email => 1 + }, + :emails => [{ + :id => 1, + :subject => "Hello", + :body => "World" + }] + }, actual) + end end From 9f20fe8b3667bc99465562b806544bdcfe27a2be Mon Sep 17 00:00:00 2001 From: twinturbo Date: Mon, 23 Apr 2012 21:22:07 -0500 Subject: [PATCH 12/37] Test passes --- lib/active_model/serializer.rb | 6 +++++- test/serializer_test.rb | 14 ++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 8d6bf83a..6ca07ee9 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -225,7 +225,11 @@ module ActiveModel end def plural_key - key.to_s.pluralize.to_sym + if polymorphic? + associated_object.class.to_s.pluralize.demodulize.underscore.to_sym + else + key.to_s.pluralize.to_sym + end end def serialize diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 132d0b85..16890aee 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -1038,7 +1038,7 @@ class SerializerTest < ActiveModel::TestCase def test_polymorphic_associations_are_included_at_root email_serializer = Class.new(ActiveModel::Serializer) do - attributes :subject, :body + attributes :subject, :body, :id end email_class = Class.new(Model) do @@ -1052,6 +1052,7 @@ class SerializerTest < ActiveModel::TestCase end attachment_serializer = Class.new(ActiveModel::Serializer) do + root :attachment embed :ids, :include => true attributes :name, :url has_one :attachable, :polymorphic => true @@ -1064,11 +1065,12 @@ class SerializerTest < ActiveModel::TestCase actual = attachment_serializer.new(attachment, {}).as_json assert_equal({ - :name => 'logo.png', - :url => 'http://example.com/logo.png', - :attachable => { - :email => 1 - }, + :attachment => { + :name => 'logo.png', + :url => 'http://example.com/logo.png', + :attachable => { + :email => 1 + }}, :emails => [{ :id => 1, :subject => "Hello", From c4e5cd547bc11b97fba87dafab79d9904a06711c Mon Sep 17 00:00:00 2001 From: twinturbo Date: Mon, 23 Apr 2012 22:01:13 -0500 Subject: [PATCH 13/37] Add test for complex polymorphic associations --- test/serializer_test.rb | 81 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 16890aee..ec11751a 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -963,6 +963,14 @@ class SerializerTest < ActiveModel::TestCase def attachable @attributes[:attachable] end + + def readable + @attributes[:readable] + end + + def edible + @attributes[:edible] + end end def tests_can_handle_polymorphism @@ -1078,4 +1086,77 @@ class SerializerTest < ActiveModel::TestCase }] }, actual) end + + def test_multiple_polymorphic_associations + email_serializer = Class.new(ActiveModel::Serializer) do + attributes :subject, :body, :id + end + + orange_serializer = Class.new(ActiveModel::Serializer) do + attributes :plu, :id + end + + email_class = Class.new(Model) do + def self.to_s + "Email" + end + + define_method :active_model_serializer do + email_serializer + end + end + + orange_class = Class.new(Model) do + def self.to_s + "Orange" + end + + define_method :active_model_serializer do + orange_serializer + end + end + + attachment_serializer = Class.new(ActiveModel::Serializer) do + root :attachment + embed :ids, :include => true + + attributes :name, :url + + has_one :attachable, :polymorphic => true + has_one :readable, :polymorphic => true + has_one :edible, :polymorphic => true + end + + email = email_class.new :id => 1, :subject => "Hello", :body => "World" + orange = orange_class.new :id => 1, :plu => "3027" + + attachment = Attachment.new({ + :name => 'logo.png', + :url => 'http://example.com/logo.png', + :attachable => email, + :readable => email, + :edible => orange + }) + + actual = attachment_serializer.new(attachment, {}).as_json + + assert_equal({ + :attachment => { + :name => 'logo.png', + :url => 'http://example.com/logo.png', + :attachable => { :email => 1 }, + :readable => { :email => 1 }, + :edible => { :orange => 1 } + }, + :emails => [{ + :id => 1, + :subject => "Hello", + :body => "World" + }], + :oranges => [{ + :id => 1, + :plu => "3027" + }] + }, actual) + end end From cbd7d7d38553bdbf70ff35e46dd419a09aa712f9 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Mon, 23 Apr 2012 22:14:30 -0500 Subject: [PATCH 14/37] Add test for nested included polymoprhic associations --- test/serializer_test.rb | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/test/serializer_test.rb b/test/serializer_test.rb index ec11751a..b181b959 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -1093,7 +1093,10 @@ class SerializerTest < ActiveModel::TestCase end orange_serializer = Class.new(ActiveModel::Serializer) do + embed :ids, :include => true + attributes :plu, :id + has_one :readable, :polymorphic => true end email_class = Class.new(Model) do @@ -1111,8 +1114,12 @@ class SerializerTest < ActiveModel::TestCase "Orange" end + def readable + @attributes[:readable] + end + define_method :active_model_serializer do - orange_serializer + orange_serializer end end @@ -1128,7 +1135,7 @@ class SerializerTest < ActiveModel::TestCase end email = email_class.new :id => 1, :subject => "Hello", :body => "World" - orange = orange_class.new :id => 1, :plu => "3027" + orange = orange_class.new :id => 1, :plu => "3027", readable: email attachment = Attachment.new({ :name => 'logo.png', @@ -1141,22 +1148,25 @@ class SerializerTest < ActiveModel::TestCase actual = attachment_serializer.new(attachment, {}).as_json assert_equal({ + :emails => [{ + :subject => "Hello", + :body => "World", + :id => 1 + }], + + :oranges => [{ + :plu => "3027", + :id => 1, + :readable => { :email => 1 } + }], + :attachment => { :name => 'logo.png', :url => 'http://example.com/logo.png', :attachable => { :email => 1 }, :readable => { :email => 1 }, :edible => { :orange => 1 } - }, - :emails => [{ - :id => 1, - :subject => "Hello", - :body => "World" - }], - :oranges => [{ - :id => 1, - :plu => "3027" - }] + } }, actual) end end From f01fe149724f69c8c2df741e4b281938ca0a308b Mon Sep 17 00:00:00 2001 From: twinturbo Date: Wed, 25 Apr 2012 13:17:01 -0500 Subject: [PATCH 15/37] Fix spelling mistakes --- lib/active_model/serializer.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 6ca07ee9..21e73358 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -220,7 +220,7 @@ module ActiveModel option :polymorphic end - def polymoprhic_key + def polymorphic_key associated_object.class.to_s.demodulize.underscore.to_sym end @@ -236,7 +236,7 @@ module ActiveModel object = associated_object if object && polymorphic? - { polymoprhic_key => find_serializable(object).serializable_hash } + { polymorphic_key => find_serializable(object).serializable_hash } elsif object find_serializable(object).serializable_hash end @@ -252,7 +252,7 @@ module ActiveModel object = associated_object if object && polymorphic? - { polymoprhic_key => object.read_attribute_for_serialization(:id) } + { polymorphic_key => object.read_attribute_for_serialization(:id) } elsif object object.read_attribute_for_serialization(:id) else From 7072e797879da87d260e0122ca186ba790c79429 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Sun, 15 Jul 2012 12:18:41 +0200 Subject: [PATCH 16/37] Close #90 --- lib/active_model_serializers.rb | 2 ++ test/serializer_test.rb | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 18679652..55cca1f1 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -3,6 +3,7 @@ require "active_support/core_ext/string/inflections" require "active_support/notifications" require "active_model" require "active_model/serializer" +require "set" if defined?(Rails) module ActiveModel @@ -63,6 +64,7 @@ module ActiveModel::ArraySerializerSupport end Array.send(:include, ActiveModel::ArraySerializerSupport) +Set.send(:include, ActiveModel::ArraySerializerSupport) ActiveSupport.on_load(:active_record) do ActiveRecord::Relation.send(:include, ActiveModel::ArraySerializerSupport) diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 288af48a..f740de59 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -434,6 +434,10 @@ class SerializerTest < ActiveModel::TestCase ], serializer.as_json) end + def test_sets_use_an_array_serializer + assert_equal Set.new.active_model_serializer, ActiveModel::ArraySerializer + end + class CustomBlog < Blog attr_accessor :public_posts, :public_user end From f85c624d02687211c36947b6b8f14fb4448793e7 Mon Sep 17 00:00:00 2001 From: Bradley Priest Date: Sun, 15 Jul 2012 18:50:47 +0800 Subject: [PATCH 17/37] Fix travis --- test/serializer_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/serializer_test.rb b/test/serializer_test.rb index b181b959..60bb123e 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -1135,7 +1135,7 @@ class SerializerTest < ActiveModel::TestCase end email = email_class.new :id => 1, :subject => "Hello", :body => "World" - orange = orange_class.new :id => 1, :plu => "3027", readable: email + orange = orange_class.new :id => 1, :plu => "3027", :readable => email attachment = Attachment.new({ :name => 'logo.png', From 5111615ac1ea86dc8a7eaa702903b679e03248e3 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Sun, 15 Jul 2012 13:42:29 +0200 Subject: [PATCH 18/37] Actually test set serialization --- test/serializer_test.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/test/serializer_test.rb b/test/serializer_test.rb index f740de59..5d832044 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -434,8 +434,20 @@ class SerializerTest < ActiveModel::TestCase ], serializer.as_json) end - def test_sets_use_an_array_serializer - assert_equal Set.new.active_model_serializer, ActiveModel::ArraySerializer + def test_sets_can_be_serialized + post1 = Post.new(:title => "Post1", :author => "Author1", :id => 1) + post2 = Post.new(:title => "Post2", :author => "Author2", :id => 2) + + set = Set.new + set << post1 + set << post2 + + serializer = set.active_model_serializer.new set, :each_serializer => CustomPostSerializer + + assert_equal([ + { :title => "Post1" }, + { :title => "Post2" } + ], serializer.as_json) end class CustomBlog < Blog From 486d282922efa33760a5a58ff8911fe158465cc4 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Mon, 16 Jul 2012 13:41:15 +0200 Subject: [PATCH 19/37] Raise error when associations cannot be included include! only works when the source serializer has a root set. The as_json method sets up some state for the include! method. If a child association has associations with `:include => true` or `root foo, :include => true` would cause an undefined method error for `NilClass`. This is entirely unhelpful for the end user. This commit raise an error when this situation occurs. It makes it clear that it's not a problem with AMS but the serialization graph. --- lib/active_model/serializer.rb | 16 +++++++- test/serializer_test.rb | 75 ++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 21e73358..af2ec277 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -108,6 +108,18 @@ module ActiveModel # end # class Serializer + class IncludeError < StandardError + attr_reader :source, :association + + def initialize(source, association) + @source, @association = source, association + end + + def to_s + "Cannot serialize #{association} when #{source} does not have a root!" + end + end + module Associations #:nodoc: class Config #:nodoc: class_attribute :options @@ -502,7 +514,9 @@ module ActiveModel if association.embed_ids? node[association.key] = association.serialize_ids - if association.embed_in_root? + if association.embed_in_root? && hash.nil? + raise IncludeError.new(self.class, association.name) + elsif association.embed_in_root? merge_association hash, association.root, association.serialize_many, unique_values end elsif association.embed_objects? diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 60bb123e..3e269be8 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -1169,4 +1169,79 @@ class SerializerTest < ActiveModel::TestCase } }, actual) end + + def test_raises_an_error_when_a_child_serializer_includes_associations_when_the_source_doesnt + attachment_serializer = Class.new(ActiveModel::Serializer) do + attributes :name + end + + fruit_serializer = Class.new(ActiveModel::Serializer) do + embed :ids, :include => true + has_one :attachment, :serializer => attachment_serializer + attribute :color + end + + banana_class = Class.new Model do + def self.to_s + 'banana' + end + + def attachment + @attributes[:attachment] + end + + define_method :active_model_serializer do + fruit_serializer + end + end + + strawberry_class = Class.new Model do + def self.to_s + 'strawberry' + end + + def attachment + @attributes[:attachment] + end + + define_method :active_model_serializer do + fruit_serializer + end + end + + smoothie = Class.new do + attr_reader :base, :flavor + + def initialize(base, flavor) + @base, @flavor = base, flavor + end + end + + smoothie_serializer = Class.new(ActiveModel::Serializer) do + root false + embed :ids, :include => true + + has_one :base, :polymorphic => true + has_one :flavor, :polymorphic => true + end + + banana_attachment = Attachment.new({ + :name => 'banana_blending.md', + :id => 3, + }) + + strawberry_attachment = Attachment.new({ + :name => 'strawberry_cleaning.doc', + :id => 4 + }) + + banana = banana_class.new :color => "yellow", :id => 1, :attachment => banana_attachment + strawberry = strawberry_class.new :color => "red", :id => 2, :attachment => strawberry_attachment + + smoothie = smoothie_serializer.new(smoothie.new(banana, strawberry)) + + assert_raise ActiveModel::Serializer::IncludeError do + smoothie.as_json + end + end end From 6f3b250dc99c0e8780f93a6c416ebfe3e5d0bfcd Mon Sep 17 00:00:00 2001 From: twinturbo Date: Mon, 16 Jul 2012 15:08:01 +0200 Subject: [PATCH 20/37] Don't include empty polymoprhic associations Take this serializer: class TodoSerializer < ActiveModel::Serializer root :todo, :include => true has_one :reference, :polymorphic => true end A nil reference would generate this JSON: { "todo": { "reference": null }, "nil_classes": [] } This commit prevents the `nil_classes` key from being added when serializing and including nil polymoprhic associations. --- lib/active_model/serializer.rb | 14 +++++++++++++- test/serializer_test.rb | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index af2ec277..117b89de 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -194,6 +194,10 @@ module ActiveModel option(:include, source_serializer._root_embed) end + def embeddable? + !associated_object.nil? + end + protected def find_serializable(object) @@ -228,6 +232,14 @@ module ActiveModel end class HasOne < Config #:nodoc: + def embeddable? + if polymorphic? && associated_object.nil? + false + else + true + end + end + def polymorphic? option :polymorphic end @@ -516,7 +528,7 @@ module ActiveModel if association.embed_in_root? && hash.nil? raise IncludeError.new(self.class, association.name) - elsif association.embed_in_root? + elsif association.embed_in_root? && association.embeddable? merge_association hash, association.root, association.serialize_many, unique_values end elsif association.embed_objects? diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 3e269be8..b785e3b6 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -1244,4 +1244,24 @@ class SerializerTest < ActiveModel::TestCase smoothie.as_json end end + + def tests_includes_does_not_include_nil_polymoprhic_associations + post_serializer = Class.new(ActiveModel::Serializer) do + root :post + embed :ids, :include => true + has_one :author, :polymorphic => true + attributes :title + end + + post = Post.new(:title => 'Foo') + + actual = post_serializer.new(post).as_json + + assert_equal({ + :post => { + :title => 'Foo', + :author => nil + } + }, actual) + end end From a6473e70c4da1174934788fea3b992ceeae72b79 Mon Sep 17 00:00:00 2001 From: Chris Schmitz Date: Mon, 16 Jul 2012 09:29:12 -0500 Subject: [PATCH 21/37] Fix a couple typos. --- lib/active_model/serializer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 21e73358..4d1a2faf 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -78,7 +78,7 @@ module ActiveModel # # Provides a basic serializer implementation that allows you to easily # control how a given object is going to be serialized. On initialization, - # it expects to object as arguments, a resource and options. For example, + # it expects two objects as arguments, a resource and options. For example, # one may do in a controller: # # PostSerializer.new(@post, :scope => current_user).to_json @@ -87,7 +87,7 @@ module ActiveModel # in for authorization purposes. # # We use the scope to check if a given attribute should be serialized or not. - # For example, some attributes maybe only be returned if +current_user+ is the + # For example, some attributes may only be returned if +current_user+ is the # author of the post: # # class PostSerializer < ActiveModel::Serializer From 91945f02c814645b83e4dc1ac3ac834ceba3c290 Mon Sep 17 00:00:00 2001 From: Matthew Robertson Date: Fri, 20 Jul 2012 15:18:30 -0700 Subject: [PATCH 22/37] Added a section to the README about defining custom attributes by overriding `serializable_hash` --- README.markdown | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.markdown b/README.markdown index 5fab7d2d..5ccaa222 100644 --- a/README.markdown +++ b/README.markdown @@ -107,6 +107,37 @@ class PostSerializer < ActiveModel::Serializer end ``` +## Custom Attributes + +If you would like customize your JSON to include things beyond the simple +attributes of the model, you can override its `serializable_hash` method +to return anything you need. For example: + +```ruby +class Person < ActiveRecord::Base + + def full_name + "#{first_name} #{last_name}" + end + + def serializable_hash options = nil + hash = super(options) + hash["full_name"] = full_name + hash + end + +end +``` + +The attributes returned by `serializable_hash` are then available in your serializer +as usual: + +```ruby +class PersonSerializer < ActiveModel::Serializer + attributes :first_name, :last_name, :full_name +end +``` + ## Associations For specified associations, the serializer will look up the association and From 0123e80dd3a37765b3b836f6acd8d8fe4b2b68f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 21 Jul 2012 10:35:25 +0300 Subject: [PATCH 23/37] Update README.markdown --- README.markdown | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/README.markdown b/README.markdown index 5ccaa222..9e28d082 100644 --- a/README.markdown +++ b/README.markdown @@ -39,7 +39,7 @@ $ rails g resource post title:string body:string This will generate a serializer in `app/serializers/post_serializer.rb` for your new model. You can also generate a serializer for an existing model with -the `serializer generator`: +the serializer generator: ``` $ rails g serializer post @@ -66,7 +66,7 @@ end In this case, Rails will look for a serializer named `PostSerializer`, and if it exists, use it to serialize the `Post`. -This also works with `render_with`, which uses `to_json` under the hood. Also +This also works with `respond_with`, which uses `to_json` under the hood. Also note that any options passed to `render :json` will be passed to your serializer and available as `@options` inside. @@ -110,31 +110,35 @@ end ## Custom Attributes If you would like customize your JSON to include things beyond the simple -attributes of the model, you can override its `serializable_hash` method -to return anything you need. For example: +attributes of the model, you can override its `attributes` method +to return anything you need. + +The most common scenario to use this feature is when an attribute +depends on a serialization scope. By default, the current user of your +application will be available in your serializer under the method +`scope`. This allows you to check for permissions before adding +an attribute. For example: ```ruby class Person < ActiveRecord::Base - def full_name "#{first_name} #{last_name}" end - def serializable_hash options = nil + def attributes hash = super(options) - hash["full_name"] = full_name + hash["full_name"] = full_name if scope.admin? hash end - end ``` -The attributes returned by `serializable_hash` are then available in your serializer -as usual: +The serialization scope can be customized in your controller by +calling `serialization_scope`: ```ruby -class PersonSerializer < ActiveModel::Serializer - attributes :first_name, :last_name, :full_name +class ApplicationController < ActionController::Base + serialization_scope :current_admin end ``` From e886f597c7b651a36ddbe60386db62dc152e2f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 21 Jul 2012 09:48:12 +0200 Subject: [PATCH 24/37] Sets are not ordered, do not depend on the order --- test/serializer_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 86206158..5eab5236 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -444,10 +444,10 @@ class SerializerTest < ActiveModel::TestCase serializer = set.active_model_serializer.new set, :each_serializer => CustomPostSerializer - assert_equal([ - { :title => "Post1" }, - { :title => "Post2" } - ], serializer.as_json) + as_json = serializer.as_json + assert_equal 2, as_json.size + assert_include(as_json, { :title => "Post1" }) + assert_include(as_json, { :title => "Post2" }) end class CustomBlog < Blog From 77b873a9a4e863e29ed09393bee82c6db90cc8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 21 Jul 2012 09:52:39 +0200 Subject: [PATCH 25/37] Update release notes for upcoming release --- README.markdown => README.md | 0 RELEASE_NOTES.md | 11 +++++++++++ 2 files changed, 11 insertions(+) rename README.markdown => README.md (100%) diff --git a/README.markdown b/README.md similarity index 100% rename from README.markdown rename to README.md diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 00061b74..47bd7100 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,14 @@ +# VERSION 0.6 (to be released) + +* Serialize sets properly +* Add root option to ArraySerializer +* Support polymorphic associations +* Support :each_serializer in ArraySerializer +* Add `scope` method to easily access the scope in the serializer +* Fix regression with Rails 3.2.6 +* Allow serialization_scope to be disabled with serialization_scope nil +* Array serializer should support pure ruby objects besides serializers + # VERSION 0.5 (May 16, 2012) * First tagged version From b813646076a962e0ea979c4d77cc7490cb6d96b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sat, 21 Jul 2012 10:01:27 +0200 Subject: [PATCH 26/37] assert_include is not available before 1.9 --- test/serializer_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 5eab5236..3a708580 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -446,8 +446,8 @@ class SerializerTest < ActiveModel::TestCase as_json = serializer.as_json assert_equal 2, as_json.size - assert_include(as_json, { :title => "Post1" }) - assert_include(as_json, { :title => "Post2" }) + assert as_json.include?({ :title => "Post1" }) + assert as_json.include?({ :title => "Post2" }) end class CustomBlog < Blog From bf2fa2d31c2d13c9509606c1329a14ff23fc6714 Mon Sep 17 00:00:00 2001 From: Tee Parham Date: Sat, 21 Jul 2012 15:25:08 -0700 Subject: [PATCH 27/37] add docs for custom serializers --- README.markdown | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.markdown b/README.markdown index 5fab7d2d..b2083e86 100644 --- a/README.markdown +++ b/README.markdown @@ -70,6 +70,24 @@ This also works with `render_with`, which uses `to_json` under the hood. Also note that any options passed to `render :json` will be passed to your serializer and available as `@options` inside. +To specify a custom serializer for an object, there are 2 options: + +1. Specify the serializer in your model: + +```ruby + class Post < ActiveRecord::Base + def active_model_serializer + FancyPostSerializer + end + end +``` + +2. Specify the serializer when you render the object: + +```ruby + render :json => @post, :serializer => FancyPostSerializer +``` + ## Getting the old version If you find that your project is already relying on the old rails to_json From 53da0b12fdf34a15fdd8997368608c1aab481052 Mon Sep 17 00:00:00 2001 From: Tee Parham Date: Sat, 21 Jul 2012 15:25:41 -0700 Subject: [PATCH 28/37] add docs for serializing arrays --- README.markdown | 80 ++++++++++++++++++++++++++++++++++ lib/active_model/serializer.rb | 2 +- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index b2083e86..8d990075 100644 --- a/README.markdown +++ b/README.markdown @@ -88,6 +88,86 @@ To specify a custom serializer for an object, there are 2 options: render :json => @post, :serializer => FancyPostSerializer ``` +## Arrays + +In your controllers, when you use `render :json` for an array of objects, AMS will +use ActiveModel::ArraySerializer (included in this project) as the base serializer, +and the individual Serializer for the objects contained in that array. + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :title, :body +end + +class PostsController < ApplicationController + def index + @posts = Post.all + render :json => @posts + end +end +``` + +Given the example above, the index action will return + +```json +{ + "posts": + [ + { "title": "Post 1", "body": "Hello!" }, + { "title": "Post 2", "body": "Goodbye!" } + ] +} +``` + +By default, the root element is the name of the controller. For example, PostsController +generates a root element "posts". To change it: + +```ruby + render :json => @posts, :root => "some_posts" +``` + +You may disable the root element for arrays at the top level, which will result in +more concise json. To disable the root element for arrays, you have 3 options: + +1. Disable root globally for in ArraySerializer. In an initializer: + +```ruby + ActiveModel::ArraySerializer.root = false +``` + +2. Disable root per render call in your controller: + +```ruby + render :json => @posts, :root => false +``` + +3. Create a custom ArraySerializer and render arrays with it: + +```ruby + class CustomArraySerializer < ActiveModel::ArraySerializer + self.root = "items" + end + + # controller: + render :json => @posts, :serializer => CustomArraySerializer +``` + +Disabling the root element of the array with any of the above 3 methods +will produce + +```json +[ + { "title": "Post 1", "body": "Hello!" }, + { "title": "Post 2", "body": "Goodbye!" } +] +``` + +To specify a custom serializer for the items within an array: + +```ruby + render :json => @posts, :each_serializer => FancyPostSerializer +``` + ## Getting the old version If you find that your project is already relying on the old rails to_json diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 6868369a..5faf3958 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -32,7 +32,7 @@ module ActiveModel # It serializes an Array, checking if each element that implements # the +active_model_serializer+ method. # - # To disable serialization of root elements, in an initializer: + # To disable serialization of root elements: # # ActiveModel::ArraySerializer.root = false # From f8263153b6a9c6f2a92c71e5061a54f42c63bce3 Mon Sep 17 00:00:00 2001 From: Tee Parham Date: Mon, 23 Jul 2012 12:49:02 -0600 Subject: [PATCH 29/37] cleanup markdown in docs --- README.markdown | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/README.markdown b/README.markdown index 8d990075..ec5a2bab 100644 --- a/README.markdown +++ b/README.markdown @@ -72,20 +72,20 @@ serializer and available as `@options` inside. To specify a custom serializer for an object, there are 2 options: -1. Specify the serializer in your model: +#### 1. Specify the serializer in your model: ```ruby - class Post < ActiveRecord::Base - def active_model_serializer - FancyPostSerializer - end +class Post < ActiveRecord::Base + def active_model_serializer + FancyPostSerializer end +end ``` -2. Specify the serializer when you render the object: +#### 2. Specify the serializer when you render the object: ```ruby - render :json => @post, :serializer => FancyPostSerializer +render :json => @post, :serializer => FancyPostSerializer ``` ## Arrays @@ -123,33 +123,33 @@ By default, the root element is the name of the controller. For example, PostsCo generates a root element "posts". To change it: ```ruby - render :json => @posts, :root => "some_posts" +render :json => @posts, :root => "some_posts" ``` You may disable the root element for arrays at the top level, which will result in more concise json. To disable the root element for arrays, you have 3 options: -1. Disable root globally for in ArraySerializer. In an initializer: +#### 1. Disable root globally for in ArraySerializer. In an initializer: ```ruby - ActiveModel::ArraySerializer.root = false +ActiveModel::ArraySerializer.root = false ``` -2. Disable root per render call in your controller: +#### 2. Disable root per render call in your controller: ```ruby - render :json => @posts, :root => false +render :json => @posts, :root => false ``` -3. Create a custom ArraySerializer and render arrays with it: +#### 3. Create a custom ArraySerializer and render arrays with it: ```ruby - class CustomArraySerializer < ActiveModel::ArraySerializer - self.root = "items" - end +class CustomArraySerializer < ActiveModel::ArraySerializer + self.root = "items" +end - # controller: - render :json => @posts, :serializer => CustomArraySerializer +# controller: +render :json => @posts, :serializer => CustomArraySerializer ``` Disabling the root element of the array with any of the above 3 methods @@ -165,7 +165,7 @@ will produce To specify a custom serializer for the items within an array: ```ruby - render :json => @posts, :each_serializer => FancyPostSerializer +render :json => @posts, :each_serializer => FancyPostSerializer ``` ## Getting the old version From 3ecf22a2490f111cccbf025d5b1264d2f9f58736 Mon Sep 17 00:00:00 2001 From: Tee Parham Date: Mon, 23 Jul 2012 12:55:47 -0600 Subject: [PATCH 30/37] more README markdown cleanup --- README.markdown | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.markdown b/README.markdown index ec5a2bab..371374ed 100644 --- a/README.markdown +++ b/README.markdown @@ -91,8 +91,8 @@ render :json => @post, :serializer => FancyPostSerializer ## Arrays In your controllers, when you use `render :json` for an array of objects, AMS will -use ActiveModel::ArraySerializer (included in this project) as the base serializer, -and the individual Serializer for the objects contained in that array. +use `ActiveModel::ArraySerializer` (included in this project) as the base serializer, +and the individual `Serializer` for the objects contained in that array. ```ruby class PostSerializer < ActiveModel::Serializer @@ -112,14 +112,14 @@ Given the example above, the index action will return ```json { "posts": - [ - { "title": "Post 1", "body": "Hello!" }, - { "title": "Post 2", "body": "Goodbye!" } - ] + [ + { "title": "Post 1", "body": "Hello!" }, + { "title": "Post 2", "body": "Goodbye!" } + ] } ``` -By default, the root element is the name of the controller. For example, PostsController +By default, the root element is the name of the controller. For example, `PostsController` generates a root element "posts". To change it: ```ruby @@ -129,7 +129,7 @@ render :json => @posts, :root => "some_posts" You may disable the root element for arrays at the top level, which will result in more concise json. To disable the root element for arrays, you have 3 options: -#### 1. Disable root globally for in ArraySerializer. In an initializer: +#### 1. Disable root globally for in `ArraySerializer`. In an initializer: ```ruby ActiveModel::ArraySerializer.root = false @@ -141,7 +141,7 @@ ActiveModel::ArraySerializer.root = false render :json => @posts, :root => false ``` -#### 3. Create a custom ArraySerializer and render arrays with it: +#### 3. Create a custom `ArraySerializer` and render arrays with it: ```ruby class CustomArraySerializer < ActiveModel::ArraySerializer From 6a37cfe219d24b3cf267e7ff99f86005e2532150 Mon Sep 17 00:00:00 2001 From: Tee Parham Date: Mon, 23 Jul 2012 14:07:49 -0600 Subject: [PATCH 31/37] fix typo in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 56575813..a0cd0792 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ render :json => @posts, :root => false ```ruby class CustomArraySerializer < ActiveModel::ArraySerializer - self.root = "items" + self.root = false end # controller: From e8f2ecebfb86d33b9fd38a7788b3b0b7868d3f63 Mon Sep 17 00:00:00 2001 From: Jo Liss Date: Thu, 26 Jul 2012 00:27:17 +0200 Subject: [PATCH 32/37] Use blocks instead of eval to define methods --- lib/active_model/serializer.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 2547f680..edd99e46 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -318,7 +318,9 @@ module ActiveModel self._attributes = _attributes.merge(attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym) unless method_defined?(attr) - class_eval "def #{attr}() object.read_attribute_for_serialization(:#{attr}) end", __FILE__, __LINE__ + define_method attr do + object.read_attribute_for_serialization(attr.to_sym) + end end end @@ -328,7 +330,9 @@ module ActiveModel attrs.each do |attr| unless method_defined?(attr) - class_eval "def #{attr}() object.#{attr} end", __FILE__, __LINE__ + define_method attr do + object.send attr + end end self._associations[attr] = klass.refine(attr, options) @@ -575,7 +579,7 @@ module ActiveModel end # Returns options[:scope] - def scope + def scope @options[:scope] end From 63465fda6870d9adbbdde4639b41188e9d6e7478 Mon Sep 17 00:00:00 2001 From: Ray Cohen Date: Sat, 28 Jul 2012 13:01:06 -0400 Subject: [PATCH 33/37] fix custom attributes with scope readme section --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a0cd0792..aeb19a39 100644 --- a/README.md +++ b/README.md @@ -222,10 +222,14 @@ class Person < ActiveRecord::Base def full_name "#{first_name} #{last_name}" end - +end + +class PersonSerializer < ActiveModel::Serializer + attributes :first_name, :last_name + def attributes - hash = super(options) - hash["full_name"] = full_name if scope.admin? + hash = super + hash["full_name"] = object.full_name if scope.admin? hash end end From 1e7c69c729842dce0cd1e23baedb50907a08032e Mon Sep 17 00:00:00 2001 From: Ray Cohen Date: Sat, 28 Jul 2012 19:05:45 -0400 Subject: [PATCH 34/37] Test for having scope option to render override the controller's serialization scope --- test/serialization_test.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/serialization_test.rb b/test/serialization_test.rb index 9706f70a..6b1a5712 100644 --- a/test/serialization_test.rb +++ b/test/serialization_test.rb @@ -131,6 +131,12 @@ class RenderJsonTest < ActionController::TestCase render :json => JsonSerializable.new, :options => true end + def render_json_with_serializer_and_scope_option + @current_user = Struct.new(:as_json).new(:current_user => true) + @scope = Struct.new(:as_json).new(:current_user => false) + render :json => JsonSerializable.new, :scope => @scope + end + def render_json_with_serializer_api_but_without_serializer @current_user = Struct.new(:as_json).new(:current_user => true) render :json => JsonSerializable.new(true) @@ -259,6 +265,11 @@ class RenderJsonTest < ActionController::TestCase assert_match '"options":true', @response.body end + def test_render_json_with_serializer_and_scope_option + get :render_json_with_serializer_and_scope_option + assert_match '"scope":{"current_user":false}', @response.body + end + def test_render_json_with_serializer_api_but_without_serializer get :render_json_with_serializer_api_but_without_serializer assert_match '{"serializable_object":true}', @response.body From 424dacb457098dac427d74622b6f7fc9a6bba0fd Mon Sep 17 00:00:00 2001 From: Ray Cohen Date: Sat, 28 Jul 2012 19:05:45 -0400 Subject: [PATCH 35/37] scope option to render takes precedence over serialization_scope --- lib/action_controller/serialization.rb | 2 +- test/serialization_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index d52c6800..b4cbc73d 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -52,7 +52,7 @@ module ActionController end if serializer - options[:scope] = serialization_scope + options[:scope] = serialization_scope unless options.has_key?(:scope) options[:url_options] = url_options json = serializer.new(json, options.merge(default_serializer_options || {})) end diff --git a/test/serialization_test.rb b/test/serialization_test.rb index 6b1a5712..2bcc1c9a 100644 --- a/test/serialization_test.rb +++ b/test/serialization_test.rb @@ -133,8 +133,8 @@ class RenderJsonTest < ActionController::TestCase def render_json_with_serializer_and_scope_option @current_user = Struct.new(:as_json).new(:current_user => true) - @scope = Struct.new(:as_json).new(:current_user => false) - render :json => JsonSerializable.new, :scope => @scope + scope = Struct.new(:as_json).new(:current_user => false) + render :json => JsonSerializable.new, :scope => scope end def render_json_with_serializer_api_but_without_serializer From 754aa295ba71798c58bdaa407770e22751945459 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski & Will Bagby Date: Tue, 7 Aug 2012 12:56:58 -0400 Subject: [PATCH 36/37] Return empty hash when url_options not provided --- lib/active_model/serializer.rb | 2 +- test/serializer_test.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index edd99e46..7920d591 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -449,7 +449,7 @@ module ActiveModel end def url_options - @options[:url_options] + @options[:url_options] || {} end # Returns a json representation of the serializable diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 3a708580..beab64fd 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -143,6 +143,12 @@ class SerializerTest < ActiveModel::TestCase assert_equal({ :host => "test.local" }, user_serializer.url_options) end + def test_serializer_returns_empty_hash_without_url_options + user = User.new + user_serializer = UserSerializer.new(user) + assert_equal({}, user_serializer.url_options) + end + def test_pretty_accessors user = User.new user.superuser = true From 38859d1f3da7751749836467d982e4dd4ac6a586 Mon Sep 17 00:00:00 2001 From: twinturbo Date: Sat, 25 Aug 2012 17:39:39 +0200 Subject: [PATCH 37/37] Easier to work with polymorphic interface --- lib/active_model/serializer.rb | 10 ++++++++-- test/serializer_test.rb | 15 +++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 7920d591..cd743e4c 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -267,7 +267,10 @@ module ActiveModel object = associated_object if object && polymorphic? - { polymorphic_key => find_serializable(object).serializable_hash } + { + :type => polymorphic_key, + polymorphic_key => find_serializable(object).serializable_hash + } elsif object find_serializable(object).serializable_hash end @@ -283,7 +286,10 @@ module ActiveModel object = associated_object if object && polymorphic? - { polymorphic_key => object.read_attribute_for_serialization(:id) } + { + :type => polymorphic_key, + :id => object.read_attribute_for_serialization(:id) + } elsif object object.read_attribute_for_serialization(:id) else diff --git a/test/serializer_test.rb b/test/serializer_test.rb index beab64fd..8358178d 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -1025,6 +1025,7 @@ class SerializerTest < ActiveModel::TestCase :name => 'logo.png', :url => 'http://example.com/logo.png', :attachable => { + :type => :email, :email => { :subject => 'foo', :body => 'bar' } } }, actual) @@ -1061,7 +1062,8 @@ class SerializerTest < ActiveModel::TestCase :name => 'logo.png', :url => 'http://example.com/logo.png', :attachable => { - :email => 1 + :type => :email, + :id => 1 } }, actual) end @@ -1099,7 +1101,8 @@ class SerializerTest < ActiveModel::TestCase :name => 'logo.png', :url => 'http://example.com/logo.png', :attachable => { - :email => 1 + :type => :email, + :id => 1 }}, :emails => [{ :id => 1, @@ -1179,15 +1182,15 @@ class SerializerTest < ActiveModel::TestCase :oranges => [{ :plu => "3027", :id => 1, - :readable => { :email => 1 } + :readable => { :type => :email, :id => 1 } }], :attachment => { :name => 'logo.png', :url => 'http://example.com/logo.png', - :attachable => { :email => 1 }, - :readable => { :email => 1 }, - :edible => { :orange => 1 } + :attachable => { :type => :email, :id => 1 }, + :readable => { :type => :email, :id => 1 }, + :edible => { :type => :orange, :id => 1 } } }, actual) end