From c3de52d7ed470433e9ecd44226518797c0a9f389 Mon Sep 17 00:00:00 2001 From: Jose and Yehuda Date: Mon, 26 Sep 2011 19:29:37 -0400 Subject: Initial implementation of ActiveModel::Serializer --- activemodel/lib/active_model.rb | 1 + activemodel/lib/active_model/serializer.rb | 46 ++++++++++++++++++ activemodel/test/cases/serializer_test.rb | 75 ++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 activemodel/lib/active_model/serializer.rb create mode 100644 activemodel/test/cases/serializer_test.rb (limited to 'activemodel') diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index d0e2a6f39c..28765b00bb 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -43,6 +43,7 @@ module ActiveModel autoload :Observer, 'active_model/observing' autoload :Observing autoload :SecurePassword + autoload :Serializer autoload :Serialization autoload :TestCase autoload :Translation diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb new file mode 100644 index 0000000000..2eca3ebc5e --- /dev/null +++ b/activemodel/lib/active_model/serializer.rb @@ -0,0 +1,46 @@ +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/string/inflections" +require "set" + +module ActiveModel + class Serializer + class_attribute :_attributes + self._attributes = Set.new + + def self.attributes(*attrs) + self._attributes += attrs + end + + attr_reader :object, :scope + + def self.inherited(klass) + name = klass.name.demodulize.underscore.sub(/_serializer$/, '') + + klass.class_eval do + alias_method name.to_sym, :object + end + end + + def initialize(object, scope) + @object, @scope = object, scope + end + + def as_json(*) + serializable_hash + end + + def serializable_hash(*) + attributes + end + + def attributes + hash = {} + + _attributes.each do |name| + hash[name] = @object.read_attribute_for_serialization(name) + end + + hash + end + end +end diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb new file mode 100644 index 0000000000..91ed6987a1 --- /dev/null +++ b/activemodel/test/cases/serializer_test.rb @@ -0,0 +1,75 @@ +require "cases/helper" + +class SerializerTest < ActiveModel::TestCase + class User + attr_accessor :superuser + + def super_user? + @superuser + end + + def read_attribute_for_serialization(name) + hash = { :first_name => "Jose", :last_name => "Valim", :password => "oh noes yugive my password" } + hash[name] + end + end + + class UserSerializer < ActiveModel::Serializer + attributes :first_name, :last_name + end + + class User2Serializer < ActiveModel::Serializer + attributes :first_name, :last_name + + def serializable_hash(*) + attributes.merge(:ok => true).merge(scope) + end + end + + class MyUserSerializer < ActiveModel::Serializer + attributes :first_name, :last_name + + def serializable_hash(*) + hash = attributes + hash = hash.merge(:super_user => true) if my_user.super_user? + hash + end + end + + def test_attributes + user = User.new + user_serializer = UserSerializer.new(user, nil) + + hash = user_serializer.as_json + + assert_equal({ :first_name => "Jose", :last_name => "Valim" }, hash) + end + + def test_attributes_method + user = User.new + user_serializer = User2Serializer.new(user, {}) + + hash = user_serializer.as_json + + assert_equal({ :first_name => "Jose", :last_name => "Valim", :ok => true }, hash) + end + + def test_serializer_receives_scope + user = User.new + user_serializer = User2Serializer.new(user, {:scope => true}) + + hash = user_serializer.as_json + + assert_equal({ :first_name => "Jose", :last_name => "Valim", :ok => true, :scope => true }, hash) + end + + def test_pretty_accessors + user = User.new + user.superuser = true + user_serializer = MyUserSerializer.new(user, nil) + + hash = user_serializer.as_json + + assert_equal({ :first_name => "Jose", :last_name => "Valim", :super_user => true }, hash) + end +end -- cgit v1.2.3 From e407dfb9bf6ee3b12d699511ef05e6f260c5edf1 Mon Sep 17 00:00:00 2001 From: Jose and Yehuda Date: Tue, 27 Sep 2011 17:34:47 -0400 Subject: Don't require serializable_hash to take options. --- activemodel/lib/active_model/serializer.rb | 2 +- activemodel/test/cases/serializer_test.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb index 2eca3ebc5e..a2035ae2cb 100644 --- a/activemodel/lib/active_model/serializer.rb +++ b/activemodel/lib/active_model/serializer.rb @@ -29,7 +29,7 @@ module ActiveModel serializable_hash end - def serializable_hash(*) + def serializable_hash attributes end diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index 91ed6987a1..1c1da588f7 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -21,7 +21,7 @@ class SerializerTest < ActiveModel::TestCase class User2Serializer < ActiveModel::Serializer attributes :first_name, :last_name - def serializable_hash(*) + def serializable_hash attributes.merge(:ok => true).merge(scope) end end @@ -29,7 +29,7 @@ class SerializerTest < ActiveModel::TestCase class MyUserSerializer < ActiveModel::Serializer attributes :first_name, :last_name - def serializable_hash(*) + def serializable_hash hash = attributes hash = hash.merge(:super_user => true) if my_user.super_user? hash -- cgit v1.2.3 From 2a4aaae72af037715db81fda332190df62f3ec44 Mon Sep 17 00:00:00 2001 From: Jose and Yehuda Date: Sat, 15 Oct 2011 16:20:45 +0200 Subject: Added has_one and has_many --- activemodel/lib/active_model/serializer.rb | 56 +++++++++++++++++---- activemodel/test/cases/serializer_test.rb | 81 ++++++++++++++++++++++++++++-- 2 files changed, 123 insertions(+), 14 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb index a2035ae2cb..dc5e2aadb3 100644 --- a/activemodel/lib/active_model/serializer.rb +++ b/activemodel/lib/active_model/serializer.rb @@ -7,20 +7,41 @@ module ActiveModel class_attribute :_attributes self._attributes = Set.new - def self.attributes(*attrs) - self._attributes += attrs - end + class_attribute :_associations + self._associations = {} - attr_reader :object, :scope + class << self + def attributes(*attrs) + self._attributes += attrs + end + + def has_many(*attrs) + options = attrs.extract_options! + options[:has_many] = true + hash = {} + attrs.each { |attr| hash[attr] = options } + self._associations = _associations.merge(hash) + end + + def has_one(*attrs) + options = attrs.extract_options! + options[:has_one] = true + hash = {} + attrs.each { |attr| hash[attr] = options } + self._associations = _associations.merge(hash) + end - def self.inherited(klass) - name = klass.name.demodulize.underscore.sub(/_serializer$/, '') + def inherited(klass) + name = klass.name.demodulize.underscore.sub(/_serializer$/, '') - klass.class_eval do - alias_method name.to_sym, :object + klass.class_eval do + alias_method name.to_sym, :object + end end end + attr_reader :object, :scope + def initialize(object, scope) @object, @scope = object, scope end @@ -30,7 +51,24 @@ module ActiveModel end def serializable_hash - attributes + hash = attributes + + _associations.each do |association, options| + associated_object = object.send(association) + serializer = options[:serializer] + + if options[:has_many] + serialized_array = associated_object.map do |item| + serializer.new(item, scope).serializable_hash + end + + hash[association] = serialized_array + elsif options[:has_one] + hash[association] = serializer.new(associated_object, scope).serializable_hash + end + end + + hash end def attributes diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index 1c1da588f7..24de81d48f 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -1,17 +1,33 @@ require "cases/helper" class SerializerTest < ActiveModel::TestCase - class User + class Model + def initialize(hash) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end + end + + class User < Model attr_accessor :superuser + def initialize(hash={}) + super hash.merge(:first_name => "Jose", :last_name => "Valim", :password => "oh noes yugive my password") + end + def super_user? @superuser end + end - def read_attribute_for_serialization(name) - hash = { :first_name => "Jose", :last_name => "Valim", :password => "oh noes yugive my password" } - hash[name] - end + class Post < Model + attr_accessor :comments + end + + class Comment < Model end class UserSerializer < ActiveModel::Serializer @@ -36,6 +52,25 @@ class SerializerTest < ActiveModel::TestCase end end + class CommentSerializer + def initialize(comment, scope) + @comment, @scope = comment, scope + end + + def serializable_hash + { title: @comment.read_attribute_for_serialization(:title) } + end + + def as_json + { comment: serializable_hash } + end + end + + class PostSerializer < ActiveModel::Serializer + attributes :title, :body + has_many :comments, :serializer => CommentSerializer + end + def test_attributes user = User.new user_serializer = UserSerializer.new(user, nil) @@ -72,4 +107,40 @@ class SerializerTest < ActiveModel::TestCase assert_equal({ :first_name => "Jose", :last_name => "Valim", :super_user => true }, hash) end + + def test_has_many + user = User.new + + post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") + comments = [Comment.new(:title => "Comment1"), Comment.new(:title => "Comment2")] + post.comments = comments + + post_serializer = PostSerializer.new(post, user) + + assert_equal({ + :title => "New Post", + :body => "Body of new post", + :comments => [ + { :title => "Comment1" }, + { :title => "Comment2" } + ] + }, post_serializer.as_json) + end + + class Blog < Model + attr_accessor :author + end + + class AuthorSerializer < ActiveModel::Serializer + attributes :first_name, :last_name + end + + class BlogSerializer < ActiveModel::Serializer + has_one :author, :serializer => AuthorSerializer + end + + def test_has_one + user = User.new + blog = Blog.new(:author => user) + end end -- cgit v1.2.3 From 776da539d72c98d077f97789a1265dd23a79711f Mon Sep 17 00:00:00 2001 From: Jose and Yehuda Date: Sat, 15 Oct 2011 16:53:22 +0200 Subject: Add support for implicit serializers --- activemodel/lib/active_model/serializer.rb | 63 ++++++++++++++++++------------ activemodel/test/cases/serializer_test.rb | 35 ++++++++++++++++- 2 files changed, 72 insertions(+), 26 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb index dc5e2aadb3..c5b433df51 100644 --- a/activemodel/lib/active_model/serializer.rb +++ b/activemodel/lib/active_model/serializer.rb @@ -1,37 +1,62 @@ require "active_support/core_ext/class/attribute" require "active_support/core_ext/string/inflections" +require "active_support/core_ext/module/anonymous" require "set" module ActiveModel class Serializer + module Associations + class Config < Struct.new(:name, :options) + def serializer + options[:serializer] + end + end + + class HasMany < Config + def serialize(collection, scope) + collection.map do |item| + serializer.new(item, scope).serializable_hash + end + end + end + + class HasOne < Config + def serialize(object, scope) + serializer.new(object, scope).serializable_hash + end + end + end + class_attribute :_attributes self._attributes = Set.new class_attribute :_associations - self._associations = {} + self._associations = [] class << self def attributes(*attrs) self._attributes += attrs end - def has_many(*attrs) + def associate(klass, attrs) options = attrs.extract_options! - options[:has_many] = true - hash = {} - attrs.each { |attr| hash[attr] = options } - self._associations = _associations.merge(hash) + self._associations += attrs.map do |attr| + options[:serializer] ||= const_get("#{attr.to_s.camelize}Serializer") + klass.new(attr, options) + end + end + + def has_many(*attrs) + associate(Associations::HasMany, attrs) end def has_one(*attrs) - options = attrs.extract_options! - options[:has_one] = true - hash = {} - attrs.each { |attr| hash[attr] = options } - self._associations = _associations.merge(hash) + associate(Associations::HasOne, attrs) end def inherited(klass) + return if klass.anonymous? + name = klass.name.demodulize.underscore.sub(/_serializer$/, '') klass.class_eval do @@ -53,19 +78,9 @@ module ActiveModel def serializable_hash hash = attributes - _associations.each do |association, options| - associated_object = object.send(association) - serializer = options[:serializer] - - if options[:has_many] - serialized_array = associated_object.map do |item| - serializer.new(item, scope).serializable_hash - end - - hash[association] = serialized_array - elsif options[:has_one] - hash[association] = serializer.new(associated_object, scope).serializable_hash - end + _associations.each do |association| + associated_object = object.send(association.name) + hash[association.name] = association.serialize(associated_object, scope) end hash diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index 24de81d48f..37a675bc20 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -2,7 +2,7 @@ require "cases/helper" class SerializerTest < ActiveModel::TestCase class Model - def initialize(hash) + def initialize(hash={}) @attributes = hash end @@ -141,6 +141,37 @@ class SerializerTest < ActiveModel::TestCase def test_has_one user = User.new - blog = Blog.new(:author => user) + blog = Blog.new + blog.author = user + + json = BlogSerializer.new(blog, user).as_json + assert_equal({ + :author => { + :first_name => "Jose", + :last_name => "Valim" + } + }, json) + end + + def test_implicit_serializer + author_serializer = Class.new(ActiveModel::Serializer) do + attributes :first_name + end + + blog_serializer = Class.new(ActiveModel::Serializer) do + const_set(:AuthorSerializer, author_serializer) + has_one :author + end + + user = User.new + blog = Blog.new + blog.author = user + + json = blog_serializer.new(blog, user).as_json + assert_equal({ + :author => { + :first_name => "Jose" + } + }, json) end end -- cgit v1.2.3 From 22c322f056f42d95b0421e6608f404134463de13 Mon Sep 17 00:00:00 2001 From: Jose and Yehuda Date: Sat, 15 Oct 2011 17:01:08 +0200 Subject: Add support for overriding associations, mostly used for authorization --- activemodel/lib/active_model/serializer.rb | 6 +++++- activemodel/test/cases/serializer_test.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb index c5b433df51..6e89eec09c 100644 --- a/activemodel/lib/active_model/serializer.rb +++ b/activemodel/lib/active_model/serializer.rb @@ -41,6 +41,10 @@ module ActiveModel def associate(klass, attrs) options = attrs.extract_options! self._associations += attrs.map do |attr| + unless method_defined?(attr) + class_eval "def #{attr}() object.#{attr} end", __FILE__, __LINE__ + end + options[:serializer] ||= const_get("#{attr.to_s.camelize}Serializer") klass.new(attr, options) end @@ -79,7 +83,7 @@ module ActiveModel hash = attributes _associations.each do |association| - associated_object = object.send(association.name) + associated_object = send(association.name) hash[association.name] = association.serialize(associated_object, scope) end diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index 37a675bc20..08dd73e03b 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -174,4 +174,30 @@ class SerializerTest < ActiveModel::TestCase } }, json) end + + def test_overridden_associations + author_serializer = Class.new(ActiveModel::Serializer) do + attributes :first_name + end + + blog_serializer = Class.new(ActiveModel::Serializer) do + const_set(:PersonSerializer, author_serializer) + has_one :person + + def person + object.author + end + end + + user = User.new + blog = Blog.new + blog.author = user + + json = blog_serializer.new(blog, user).as_json + assert_equal({ + :person => { + :first_name => "Jose" + } + }, json) + end end -- cgit v1.2.3 From 322f47898e80af3fcdc3cb3db35e177d8216a2d2 Mon Sep 17 00:00:00 2001 From: Jose and Yehuda Date: Sat, 15 Oct 2011 18:27:56 +0200 Subject: Add association_ids --- activemodel/lib/active_model/serializer.rb | 30 ++++++++++++++- activemodel/test/cases/serializer_test.rb | 61 +++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb index 6e89eec09c..99a007de31 100644 --- a/activemodel/lib/active_model/serializer.rb +++ b/activemodel/lib/active_model/serializer.rb @@ -18,12 +18,25 @@ module ActiveModel serializer.new(item, scope).serializable_hash end end + + def serialize_ids(collection, scope) + # use named scopes if they are present + return collection.ids if collection.respond_to?(:ids) + + collection.map do |item| + item.read_attribute_for_serialization(:id) + end + end end class HasOne < Config def serialize(object, scope) serializer.new(object, scope).serializable_hash end + + def serialize_ids(object, scope) + object.read_attribute_for_serialization(:id) + end end end @@ -80,7 +93,11 @@ module ActiveModel end def serializable_hash - hash = attributes + attributes.merge(associations) + end + + def associations + hash = {} _associations.each do |association| associated_object = send(association.name) @@ -90,6 +107,17 @@ module ActiveModel hash end + def association_ids + hash = {} + + _associations.each do |association| + associated_object = send(association.name) + hash[association.name] = association.serialize_ids(associated_object, scope) + end + + hash + end + def attributes hash = {} diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index 08dd73e03b..168a77838f 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -182,11 +182,12 @@ class SerializerTest < ActiveModel::TestCase blog_serializer = Class.new(ActiveModel::Serializer) do const_set(:PersonSerializer, author_serializer) - has_one :person def person object.author end + + has_one :person end user = User.new @@ -200,4 +201,62 @@ class SerializerTest < ActiveModel::TestCase } }, json) end + + def post_serializer(type) + Class.new(ActiveModel::Serializer) do + attributes :title, :body + has_many :comments, :serializer => CommentSerializer + + define_method :serializable_hash do + post_hash = attributes + post_hash.merge!(send(type)) + post_hash + end + end + end + + def test_associations + post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") + comments = [Comment.new(:title => "Comment1"), Comment.new(:title => "Comment2")] + post.comments = comments + + serializer = post_serializer(:associations).new(post, nil) + + assert_equal({ + :title => "New Post", + :body => "Body of new post", + :comments => [ + { :title => "Comment1" }, + { :title => "Comment2" } + ] + }, serializer.as_json) + end + + def test_association_ids + serializer = post_serializer(:association_ids) + + serializer.class_eval do + def as_json(*) + { post: serializable_hash }.merge(associations) + end + end + + post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") + comments = [Comment.new(:title => "Comment1", :id => 1), Comment.new(:title => "Comment2", :id => 2)] + post.comments = comments + + serializer = serializer.new(post, nil) + + assert_equal({ + :post => { + :title => "New Post", + :body => "Body of new post", + :comments => [1, 2] + }, + :comments => [ + { :title => "Comment1" }, + { :title => "Comment2" } + ] + }, serializer.as_json) + end end -- cgit v1.2.3 From 7a28498b55913aa0fd1d3529909ab57eaf64af0e Mon Sep 17 00:00:00 2001 From: Jose and Yehuda Date: Sat, 15 Oct 2011 18:37:12 +0200 Subject: Fix nil has_one association --- activemodel/lib/active_model/serializer.rb | 4 ++-- activemodel/test/cases/serializer_test.rb | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb index 99a007de31..1d11870bb4 100644 --- a/activemodel/lib/active_model/serializer.rb +++ b/activemodel/lib/active_model/serializer.rb @@ -31,11 +31,11 @@ module ActiveModel class HasOne < Config def serialize(object, scope) - serializer.new(object, scope).serializable_hash + object && serializer.new(object, scope).serializable_hash end def serialize_ids(object, scope) - object.read_attribute_for_serialization(:id) + object && object.read_attribute_for_serialization(:id) end end end diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index 168a77838f..165c1d2490 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -259,4 +259,22 @@ class SerializerTest < ActiveModel::TestCase ] }, serializer.as_json) end + + def test_associations_with_nil_association + user = User.new + blog = Blog.new + + json = BlogSerializer.new(blog, user).as_json + assert_equal({ + :author => nil + }, json) + + serializer = Class.new(BlogSerializer) do + def serializable_hash + attributes.merge(association_ids) + end + end + + assert_equal({ :author => nil }, serializer.new(blog, user).as_json) + end end -- cgit v1.2.3 From a230f040ff61f069d46a9b86417a8e251016d5db Mon Sep 17 00:00:00 2001 From: Jose and Yehuda Date: Sat, 15 Oct 2011 18:54:20 +0200 Subject: Add support for the root attribute --- activemodel/lib/active_model/serializer.rb | 13 ++++- activemodel/test/cases/serializer_test.rb | 76 ++++++++++++++++++++++++------ 2 files changed, 74 insertions(+), 15 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb index 1d11870bb4..98801ae633 100644 --- a/activemodel/lib/active_model/serializer.rb +++ b/activemodel/lib/active_model/serializer.rb @@ -46,6 +46,8 @@ module ActiveModel class_attribute :_associations self._associations = [] + class_attribute :_root + class << self def attributes(*attrs) self._attributes += attrs @@ -71,6 +73,10 @@ module ActiveModel associate(Associations::HasOne, attrs) end + def root(name) + self._root = name + end + def inherited(klass) return if klass.anonymous? @@ -78,6 +84,7 @@ module ActiveModel klass.class_eval do alias_method name.to_sym, :object + root name.to_sym unless self._root == false end end end @@ -89,7 +96,11 @@ module ActiveModel end def as_json(*) - serializable_hash + if _root + { _root => serializable_hash } + else + serializable_hash + end end def serializable_hash diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index 165c1d2490..044c184829 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -77,7 +77,9 @@ class SerializerTest < ActiveModel::TestCase hash = user_serializer.as_json - assert_equal({ :first_name => "Jose", :last_name => "Valim" }, hash) + assert_equal({ + :user => { :first_name => "Jose", :last_name => "Valim" } + }, hash) end def test_attributes_method @@ -86,7 +88,9 @@ class SerializerTest < ActiveModel::TestCase hash = user_serializer.as_json - assert_equal({ :first_name => "Jose", :last_name => "Valim", :ok => true }, hash) + assert_equal({ + :user2 => { :first_name => "Jose", :last_name => "Valim", :ok => true } + }, hash) end def test_serializer_receives_scope @@ -95,7 +99,14 @@ class SerializerTest < ActiveModel::TestCase hash = user_serializer.as_json - assert_equal({ :first_name => "Jose", :last_name => "Valim", :ok => true, :scope => true }, hash) + assert_equal({ + :user2 => { + :first_name => "Jose", + :last_name => "Valim", + :ok => true, + :scope => true + } + }, hash) end def test_pretty_accessors @@ -105,7 +116,11 @@ class SerializerTest < ActiveModel::TestCase hash = user_serializer.as_json - assert_equal({ :first_name => "Jose", :last_name => "Valim", :super_user => true }, hash) + assert_equal({ + :my_user => { + :first_name => "Jose", :last_name => "Valim", :super_user => true + } + }, hash) end def test_has_many @@ -118,12 +133,14 @@ class SerializerTest < ActiveModel::TestCase post_serializer = PostSerializer.new(post, user) assert_equal({ - :title => "New Post", - :body => "Body of new post", - :comments => [ - { :title => "Comment1" }, - { :title => "Comment2" } - ] + :post => { + :title => "New Post", + :body => "Body of new post", + :comments => [ + { :title => "Comment1" }, + { :title => "Comment2" } + ] + } }, post_serializer.as_json) end @@ -146,9 +163,11 @@ class SerializerTest < ActiveModel::TestCase json = BlogSerializer.new(blog, user).as_json assert_equal({ - :author => { - :first_name => "Jose", - :last_name => "Valim" + :blog => { + :author => { + :first_name => "Jose", + :last_name => "Valim" + } } }, json) end @@ -266,15 +285,44 @@ class SerializerTest < ActiveModel::TestCase json = BlogSerializer.new(blog, user).as_json assert_equal({ - :author => nil + :blog => { :author => nil } }, json) serializer = Class.new(BlogSerializer) do + root :blog + def serializable_hash attributes.merge(association_ids) end end + json = serializer.new(blog, user).as_json + assert_equal({ :blog => { :author => nil } }, json) + end + + def test_custom_root + user = User.new + blog = Blog.new + + serializer = Class.new(BlogSerializer) do + root :my_blog + end + + assert_equal({ :my_blog => { :author => nil } }, serializer.new(blog, user).as_json) + end + + def test_false_root + user = User.new + blog = Blog.new + + serializer = Class.new(BlogSerializer) do + root false + end + + assert_equal({ :author => nil }, serializer.new(blog, user).as_json) + + # test inherited false root + serializer = Class.new(serializer) assert_equal({ :author => nil }, serializer.new(blog, user).as_json) end end -- cgit v1.2.3 From 2abb2e617af8e3353d4411a8bd51d03256e0274a Mon Sep 17 00:00:00 2001 From: Jose and Yehuda Date: Sat, 15 Oct 2011 19:22:16 +0200 Subject: Add initial support for embed API --- activemodel/lib/active_model/serializer.rb | 22 ++++++-- activemodel/test/cases/serializer_test.rb | 86 ++++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 7 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb index 98801ae633..6d0746a3e8 100644 --- a/activemodel/lib/active_model/serializer.rb +++ b/activemodel/lib/active_model/serializer.rb @@ -21,7 +21,7 @@ module ActiveModel def serialize_ids(collection, scope) # use named scopes if they are present - return collection.ids if collection.respond_to?(:ids) + #return collection.ids if collection.respond_to?(:ids) collection.map do |item| item.read_attribute_for_serialization(:id) @@ -47,6 +47,9 @@ module ActiveModel self._associations = [] class_attribute :_root + class_attribute :_embed + self._embed = :objects + class_attribute :_root_embed class << self def attributes(*attrs) @@ -73,6 +76,11 @@ module ActiveModel associate(Associations::HasOne, attrs) end + def embed(type, options={}) + self._embed = type + self._root_embed = true if options[:include] + end + def root(name) self._root = name end @@ -97,14 +105,22 @@ module ActiveModel def as_json(*) if _root - { _root => serializable_hash } + hash = { _root => serializable_hash } + hash.merge!(associations) if _root_embed + hash else serializable_hash end end def serializable_hash - attributes.merge(associations) + if _embed == :ids + attributes.merge(association_ids) + elsif _embed == :objects + attributes.merge(associations) + else + attributes + end end def associations diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index 044c184829..e6a0d0cdcf 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -226,10 +226,12 @@ class SerializerTest < ActiveModel::TestCase attributes :title, :body has_many :comments, :serializer => CommentSerializer - define_method :serializable_hash do - post_hash = attributes - post_hash.merge!(send(type)) - post_hash + if type != :super + define_method :serializable_hash do + post_hash = attributes + post_hash.merge!(send(type)) + post_hash + end end end end @@ -325,4 +327,80 @@ class SerializerTest < ActiveModel::TestCase serializer = Class.new(serializer) assert_equal({ :author => nil }, serializer.new(blog, user).as_json) end + + def test_embed_ids + serializer = post_serializer(:super) + + serializer.class_eval do + root :post + embed :ids + end + + post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") + comments = [Comment.new(:title => "Comment1", :id => 1), Comment.new(:title => "Comment2", :id => 2)] + post.comments = comments + + serializer = serializer.new(post, nil) + + assert_equal({ + :post => { + :title => "New Post", + :body => "Body of new post", + :comments => [1, 2] + } + }, serializer.as_json) + end + + def test_embed_ids_include_true + serializer = post_serializer(:super) + + serializer.class_eval do + root :post + embed :ids, :include => true + end + + post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") + comments = [Comment.new(:title => "Comment1", :id => 1), Comment.new(:title => "Comment2", :id => 2)] + post.comments = comments + + serializer = serializer.new(post, nil) + + assert_equal({ + :post => { + :title => "New Post", + :body => "Body of new post", + :comments => [1, 2] + }, + :comments => [ + { :title => "Comment1" }, + { :title => "Comment2" } + ] + }, serializer.as_json) + end + + def test_embed_objects + serializer = post_serializer(:super) + + serializer.class_eval do + root :post + embed :objects + end + + post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") + comments = [Comment.new(:title => "Comment1", :id => 1), Comment.new(:title => "Comment2", :id => 2)] + post.comments = comments + + serializer = serializer.new(post, nil) + + assert_equal({ + :post => { + :title => "New Post", + :body => "Body of new post", + :comments => [ + { :title => "Comment1" }, + { :title => "Comment2" } + ] + } + }, serializer.as_json) + end end -- cgit v1.2.3 From afd7140b66e7cb32e1be58d9e44489e6bcbde0dc Mon Sep 17 00:00:00 2001 From: Jose and Yehuda Date: Sat, 15 Oct 2011 22:33:58 +0200 Subject: Remove 1.9 Hash syntax - tests passing on 1.8.7 --- activemodel/test/cases/serializer_test.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activemodel') diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index e6a0d0cdcf..00d519dc1a 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -58,11 +58,11 @@ class SerializerTest < ActiveModel::TestCase end def serializable_hash - { title: @comment.read_attribute_for_serialization(:title) } + { :title => @comment.read_attribute_for_serialization(:title) } end def as_json - { comment: serializable_hash } + { :comment => serializable_hash } end end @@ -258,7 +258,7 @@ class SerializerTest < ActiveModel::TestCase serializer.class_eval do def as_json(*) - { post: serializable_hash }.merge(associations) + { :post => serializable_hash }.merge(associations) end end -- cgit v1.2.3 From af64ac4e5ce8406137d5520fa88e8f652ab703e9 Mon Sep 17 00:00:00 2001 From: Oscar Del Ben Date: Mon, 14 Nov 2011 16:56:05 +0100 Subject: use any? instead of !empty? --- activemodel/lib/active_model/dirty.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 166cccf161..026f077ee7 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -98,7 +98,7 @@ module ActiveModel # person.name = 'bob' # person.changed? # => true def changed? - !changed_attributes.empty? + changed_attributes.any? end # List of attributes with unsaved changes. -- cgit v1.2.3 From 9fa329b7544b15cdf5751d518e380abc82468df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 14 Nov 2011 20:12:17 +0100 Subject: Speed up attribute invocation by checking if both name and calls are compilable. --- activemodel/lib/active_model/attribute_methods.rb | 59 ++++++++++++----------- 1 file changed, 32 insertions(+), 27 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index ef0b95424e..e69cb5c459 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -57,7 +57,8 @@ module ActiveModel module AttributeMethods extend ActiveSupport::Concern - COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/ + NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/ + CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/ included do class_attribute :attribute_method_matchers, :instance_writer => false @@ -112,7 +113,7 @@ module ActiveModel # If we can compile the method name, do it. Otherwise use define_method. # This is an important *optimization*, please don't change it. define_method # has slower dispatch and consumes more memory. - if name =~ COMPILABLE_REGEXP + if name =~ NAME_COMPILABLE_REGEXP sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end RUBY @@ -240,18 +241,7 @@ module ActiveModel attribute_method_matchers.each do |matcher| matcher_new = matcher.method_name(new_name).to_s matcher_old = matcher.method_name(old_name).to_s - - if matcher_new =~ COMPILABLE_REGEXP && matcher_old =~ COMPILABLE_REGEXP - module_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{matcher_new}(*args) - send(:#{matcher_old}, *args) - end - RUBY - else - define_method(matcher_new) do |*args| - send(matcher_old, *args) - end - end + define_optimized_call self, matcher_new, matcher_old end end @@ -293,17 +283,7 @@ module ActiveModel if respond_to?(generate_method) send(generate_method, attr_name) else - if method_name =~ COMPILABLE_REGEXP - defn = "def #{method_name}(*args)" - else - defn = "define_method(:'#{method_name}') do |*args|" - end - - generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 - #{defn} - send(:#{matcher.method_missing_target}, '#{attr_name}', *args) - end - RUBY + define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s end end end @@ -342,11 +322,11 @@ module ActiveModel # used to alleviate the GC, which ultimately also speeds up the app # significantly (in our case our test suite finishes 10% faster with # this cache). - def attribute_method_matchers_cache + def attribute_method_matchers_cache #:nodoc: @attribute_method_matchers_cache ||= {} end - def attribute_method_matcher(method_name) + def attribute_method_matcher(method_name) #:nodoc: if attribute_method_matchers_cache.key?(method_name) attribute_method_matchers_cache[method_name] else @@ -359,6 +339,31 @@ module ActiveModel end end + # Define a method `name` in `mod` that dispatches to `send` + # using the given `extra` args. This fallbacks `define_method` + # and `send` if the given names cannot be compiled. + def define_optimized_call(mod, name, send, *extra) #:nodoc: + if name =~ NAME_COMPILABLE_REGEXP + defn = "def #{name}(*args)" + else + defn = "define_method(:'#{name}') do |*args|" + end + + extra = (extra.map(&:inspect) << "*args").join(", ") + + if send =~ CALL_COMPILABLE_REGEXP + target = "#{send}(#{extra})" + else + target = "send(:'#{send}', #{extra})" + end + + mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1 + #{defn} + #{target} + end + RUBY + end + class AttributeMethodMatcher attr_reader :prefix, :suffix, :method_missing_target -- cgit v1.2.3 From 0e2156d33431d2343dca57c54d8ccb3d558dff95 Mon Sep 17 00:00:00 2001 From: Prem Sichanugrist Date: Fri, 18 Nov 2011 11:51:05 -0500 Subject: Update variable's name in the test case to reflect the class we're testing --- .../cases/validations/exclusion_validation_test.rb | 12 +++++------ .../cases/validations/format_validation_test.rb | 24 +++++++++++----------- .../cases/validations/inclusion_validation_test.rb | 12 +++++------ 3 files changed, 24 insertions(+), 24 deletions(-) (limited to 'activemodel') diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb index 72a383f128..adab8ccb2b 100644 --- a/activemodel/test/cases/validations/exclusion_validation_test.rb +++ b/activemodel/test/cases/validations/exclusion_validation_test.rb @@ -46,12 +46,12 @@ class ExclusionValidationTest < ActiveModel::TestCase def test_validates_exclusion_of_with_lambda Topic.validates_exclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) } - p = Topic.new - p.title = "elephant" - p.author_name = "sikachu" - assert p.invalid? + t = Topic.new + t.title = "elephant" + t.author_name = "sikachu" + assert t.invalid? - p.title = "wasabi" - assert p.valid? + t.title = "wasabi" + assert t.valid? end end diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb index 2ce714fef0..41a1131bcb 100644 --- a/activemodel/test/cases/validations/format_validation_test.rb +++ b/activemodel/test/cases/validations/format_validation_test.rb @@ -101,25 +101,25 @@ class PresenceValidationTest < ActiveModel::TestCase def test_validates_format_of_with_lambda Topic.validates_format_of :content, :with => lambda{ |topic| topic.title == "digit" ? /\A\d+\Z/ : /\A\S+\Z/ } - p = Topic.new - p.title = "digit" - p.content = "Pixies" - assert p.invalid? + t = Topic.new + t.title = "digit" + t.content = "Pixies" + assert t.invalid? - p.content = "1234" - assert p.valid? + t.content = "1234" + assert t.valid? end def test_validates_format_of_without_lambda Topic.validates_format_of :content, :without => lambda{ |topic| topic.title == "characters" ? /\A\d+\Z/ : /\A\S+\Z/ } - p = Topic.new - p.title = "characters" - p.content = "1234" - assert p.invalid? + t = Topic.new + t.title = "characters" + t.content = "1234" + assert t.invalid? - p.content = "Pixies" - assert p.valid? + t.content = "Pixies" + assert t.valid? end def test_validates_format_of_for_ruby_class diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb index 413da92de4..851d345eab 100644 --- a/activemodel/test/cases/validations/inclusion_validation_test.rb +++ b/activemodel/test/cases/validations/inclusion_validation_test.rb @@ -78,12 +78,12 @@ class InclusionValidationTest < ActiveModel::TestCase def test_validates_inclusion_of_with_lambda Topic.validates_inclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) } - p = Topic.new - p.title = "wasabi" - p.author_name = "sikachu" - assert p.invalid? + t = Topic.new + t.title = "wasabi" + t.author_name = "sikachu" + assert t.invalid? - p.title = "elephant" - assert p.valid? + t.title = "elephant" + assert t.valid? end end -- cgit v1.2.3 From efbb73562dde7510d0a08e91a09f1545880b35cb Mon Sep 17 00:00:00 2001 From: Alexey Vakhov Date: Sat, 19 Nov 2011 12:19:59 +0600 Subject: Small docs fix in Active Model callbacks module --- activemodel/lib/active_model/callbacks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index 37d0c9a0b9..0621a175bd 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -41,7 +41,7 @@ module ActiveModel # You can choose not to have all three callbacks by passing a hash to the # define_model_callbacks method. # - # define_model_callbacks :create, :only => :after, :before + # define_model_callbacks :create, :only => [:after, :before] # # Would only create the after_create and before_create callback methods in your # class. -- cgit v1.2.3 From fd86a1b6b068df87164d5763bdcd4a323a1e76f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 23 Nov 2011 19:06:45 +0000 Subject: Rely on a public contract between railties instead of accessing railtie methods directly. --- activemodel/lib/active_model/naming.rb | 24 +++++++++++---------- activemodel/test/cases/naming_test.rb | 38 ++++++++++++++++++++++++++++++---- activemodel/test/models/blog_post.rb | 8 ++----- 3 files changed, 49 insertions(+), 21 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index f16459ede2..2566920d63 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -13,18 +13,18 @@ module ActiveModel def initialize(klass, namespace = nil, name = nil) name ||= klass.name super(name) - @unnamespaced = self.sub(/^#{namespace.name}::/, '') if namespace - @klass = klass - @singular = _singularize(self).freeze - @plural = ActiveSupport::Inflector.pluralize(@singular).freeze - @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze - @human = ActiveSupport::Inflector.humanize(@element).freeze - @collection = ActiveSupport::Inflector.tableize(self).freeze + @unnamespaced = self.sub(/^#{namespace.name}::/, '') if namespace + @klass = klass + @singular = _singularize(self).freeze + @plural = ActiveSupport::Inflector.pluralize(@singular).freeze + @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze + @human = ActiveSupport::Inflector.humanize(@element).freeze + @collection = ActiveSupport::Inflector.tableize(self).freeze @partial_path = "#{@collection}/#{@element}".freeze - @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze - @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze - @i18n_key = self.underscore.to_sym + @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze + @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze + @i18n_key = self.underscore.to_sym end # Transform the model name into a more humane format, using I18n. By default, @@ -79,7 +79,9 @@ module ActiveModel # used to retrieve all kinds of naming-related information. def model_name @_model_name ||= begin - namespace = self.parents.detect { |n| n.respond_to?(:_railtie) } + namespace = self.parents.detect do |n| + n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming? + end ActiveModel::Name.new(self, namespace) end end diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index 5f943729dd..a5ee2d6090 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -74,10 +74,6 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase def test_param_key assert_equal 'post', @model_name.param_key end - - def test_recognizing_namespace - assert_equal 'Post', Blog::Post.model_name.instance_variable_get("@unnamespaced") - end end class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase @@ -160,6 +156,40 @@ class NamingWithSuppliedModelNameTest < ActiveModel::TestCase end end +class NamingUsingRelativeModelNameTest < ActiveModel::TestCase + def setup + @model_name = Blog::Post.model_name + end + + def test_singular + assert_equal 'blog_post', @model_name.singular + end + + def test_plural + assert_equal 'blog_posts', @model_name.plural + end + + def test_element + assert_equal 'post', @model_name.element + end + + def test_collection + assert_equal 'blog/posts', @model_name.collection + end + + def test_human + assert_equal 'Post', @model_name.human + end + + def test_route_key + assert_equal 'posts', @model_name.route_key + end + + def test_param_key + assert_equal 'post', @model_name.param_key + end +end + class NamingHelpersTest < Test::Unit::TestCase def setup @klass = Contact diff --git a/activemodel/test/models/blog_post.rb b/activemodel/test/models/blog_post.rb index d289177259..46eba857df 100644 --- a/activemodel/test/models/blog_post.rb +++ b/activemodel/test/models/blog_post.rb @@ -1,10 +1,6 @@ module Blog - def self._railtie - Object.new - end - - def self.table_name_prefix - "blog_" + def self.use_relative_model_naming? + true end class Post -- cgit v1.2.3 From 8896b4fdc8a543157cdf4dfc378607ebf6c10ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 23 Nov 2011 23:18:13 +0000 Subject: Implement ArraySerializer and move old serialization API to a new namespace. The following constants were renamed: ActiveModel::Serialization => ActiveModel::Serializable ActiveModel::Serializers::JSON => ActiveModel::Serializable::JSON ActiveModel::Serializers::Xml => ActiveModel::Serializable::XML The main motivation for such a change is that `ActiveModel::Serializers::JSON` was not actually a serializer, but a module that when included allows the target to be serializable to JSON. With such changes, we were able to clean up the namespace to add true serializers as the ArraySerializer. --- activemodel/lib/active_model.rb | 4 +- activemodel/lib/active_model/serializable.rb | 156 +++++++++++++++ activemodel/lib/active_model/serializable/json.rb | 108 ++++++++++ activemodel/lib/active_model/serializable/xml.rb | 195 ++++++++++++++++++ activemodel/lib/active_model/serialization.rb | 139 +------------ activemodel/lib/active_model/serializer.rb | 60 ++++++ activemodel/lib/active_model/serializers/json.rb | 102 +--------- activemodel/lib/active_model/serializers/xml.rb | 191 +----------------- activemodel/test/cases/serializable/json_test.rb | 219 +++++++++++++++++++++ activemodel/test/cases/serializable/xml_test.rb | 206 +++++++++++++++++++ activemodel/test/cases/serializable_test.rb | 151 ++++++++++++++ activemodel/test/cases/serialization_test.rb | 151 -------------- activemodel/test/cases/serializer_test.rb | 65 +++++- .../cases/serializers/json_serialization_test.rb | 219 --------------------- .../cases/serializers/xml_serialization_test.rb | 206 ------------------- 15 files changed, 1173 insertions(+), 999 deletions(-) create mode 100644 activemodel/lib/active_model/serializable.rb create mode 100644 activemodel/lib/active_model/serializable/json.rb create mode 100644 activemodel/lib/active_model/serializable/xml.rb create mode 100644 activemodel/test/cases/serializable/json_test.rb create mode 100644 activemodel/test/cases/serializable/xml_test.rb create mode 100644 activemodel/test/cases/serializable_test.rb delete mode 100644 activemodel/test/cases/serialization_test.rb delete mode 100644 activemodel/test/cases/serializers/json_serialization_test.rb delete mode 100644 activemodel/test/cases/serializers/xml_serialization_test.rb (limited to 'activemodel') diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 28765b00bb..6c4fb44b0f 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -29,6 +29,7 @@ require 'active_model/version' module ActiveModel extend ActiveSupport::Autoload + autoload :ArraySerializer, 'active_model/serializer' autoload :AttributeMethods autoload :BlockValidator, 'active_model/validator' autoload :Callbacks @@ -43,8 +44,9 @@ module ActiveModel autoload :Observer, 'active_model/observing' autoload :Observing autoload :SecurePassword - autoload :Serializer + autoload :Serializable autoload :Serialization + autoload :Serializer autoload :TestCase autoload :Translation autoload :Validations diff --git a/activemodel/lib/active_model/serializable.rb b/activemodel/lib/active_model/serializable.rb new file mode 100644 index 0000000000..769e934dbe --- /dev/null +++ b/activemodel/lib/active_model/serializable.rb @@ -0,0 +1,156 @@ +require 'active_support/core_ext/hash/except' +require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/array/wrap' + +module ActiveModel + # == Active Model Serializable + # + # Provides a basic serialization to a serializable_hash for your object. + # + # A minimal implementation could be: + # + # class Person + # + # include ActiveModel::Serializable + # + # attr_accessor :name + # + # def attributes + # {'name' => name} + # end + # + # end + # + # Which would provide you with: + # + # person = Person.new + # person.serializable_hash # => {"name"=>nil} + # person.name = "Bob" + # person.serializable_hash # => {"name"=>"Bob"} + # + # You need to declare some sort of attributes hash which contains the attributes + # you want to serialize and their current value. + # + # Most of the time though, you will want to include the JSON or XML + # serializations. Both of these modules automatically include the + # ActiveModel::Serialization module, so there is no need to explicitly + # include it. + # + # So a minimal implementation including XML and JSON would be: + # + # class Person + # + # include ActiveModel::Serializable::JSON + # include ActiveModel::Serializable::XML + # + # attr_accessor :name + # + # def attributes + # {'name' => name} + # end + # + # end + # + # Which would provide you with: + # + # person = Person.new + # person.serializable_hash # => {"name"=>nil} + # person.as_json # => {"name"=>nil} + # person.to_json # => "{\"name\":null}" + # person.to_xml # => "\n {"name"=>"Bob"} + # person.as_json # => {"name"=>"Bob"} + # person.to_json # => "{\"name\":\"Bob\"}" + # person.to_xml # => "\n:only, :except and :methods . + module Serializable + extend ActiveSupport::Concern + + autoload :JSON, "active_model/serializable/json" + autoload :XML, "active_model/serializable/xml" + + include ActiveModel::Serializer::Scope + + module ClassMethods #:nodoc: + def _model_serializer + @_model_serializer ||= ActiveModel::Serializer::Finder.find(self, self) + end + end + + def serializable_hash(options = nil) + options ||= {} + + attribute_names = attributes.keys.sort + if only = options[:only] + attribute_names &= Array.wrap(only).map(&:to_s) + elsif except = options[:except] + attribute_names -= Array.wrap(except).map(&:to_s) + end + + hash = {} + attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) } + + method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) } + method_names.each { |n| hash[n] = send(n) } + + serializable_add_includes(options) do |association, records, opts| + hash[association] = if records.is_a?(Enumerable) + records.map { |a| a.serializable_hash(opts) } + else + records.serializable_hash(opts) + end + end + + hash + end + + # Returns a model serializer for this object considering its namespace. + def model_serializer + self.class._model_serializer + end + + private + + # Hook method defining how an attribute value should be retrieved for + # serialization. By default this is assumed to be an instance named after + # the attribute. Override this method in subclasses should you need to + # retrieve the value for a given attribute differently: + # + # class MyClass + # include ActiveModel::Validations + # + # def initialize(data = {}) + # @data = data + # end + # + # def read_attribute_for_serialization(key) + # @data[key] + # end + # end + # + alias :read_attribute_for_serialization :send + + # Add associations specified via the :include option. + # + # Expects a block that takes as arguments: + # +association+ - name of the association + # +records+ - the association record(s) to be serialized + # +opts+ - options for the association records + def serializable_add_includes(options = {}) #:nodoc: + return unless include = options[:include] + + unless include.is_a?(Hash) + include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }] + end + + include.each do |association, opts| + if records = send(association) + yield association, records, opts + end + end + end + end +end diff --git a/activemodel/lib/active_model/serializable/json.rb b/activemodel/lib/active_model/serializable/json.rb new file mode 100644 index 0000000000..79173929e4 --- /dev/null +++ b/activemodel/lib/active_model/serializable/json.rb @@ -0,0 +1,108 @@ +require 'active_support/json' +require 'active_support/core_ext/class/attribute' + +module ActiveModel + # == Active Model Serializable as JSON + module Serializable + module JSON + extend ActiveSupport::Concern + include ActiveModel::Serializable + + included do + extend ActiveModel::Naming + + class_attribute :include_root_in_json + self.include_root_in_json = true + end + + # Returns a hash representing the model. Some configuration can be + # passed through +options+. + # + # The option include_root_in_json controls the top-level behavior + # of +as_json+. If true (the default) +as_json+ will emit a single root + # node named after the object's type. For example: + # + # user = User.find(1) + # user.as_json + # # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16, + # "created_at": "2006/08/01", "awesome": true} } + # + # ActiveRecord::Base.include_root_in_json = false + # user.as_json + # # => {"id": 1, "name": "Konata Izumi", "age": 16, + # "created_at": "2006/08/01", "awesome": true} + # + # This behavior can also be achieved by setting the :root option to +false+ as in: + # + # user = User.find(1) + # user.as_json(root: false) + # # => {"id": 1, "name": "Konata Izumi", "age": 16, + # "created_at": "2006/08/01", "awesome": true} + # + # The remainder of the examples in this section assume include_root_in_json is set to + # false. + # + # Without any +options+, the returned Hash will include all the model's + # attributes. For example: + # + # user = User.find(1) + # user.as_json + # # => {"id": 1, "name": "Konata Izumi", "age": 16, + # "created_at": "2006/08/01", "awesome": true} + # + # The :only and :except options can be used to limit the attributes + # included, and work similar to the +attributes+ method. For example: + # + # user.as_json(:only => [ :id, :name ]) + # # => {"id": 1, "name": "Konata Izumi"} + # + # user.as_json(:except => [ :id, :created_at, :age ]) + # # => {"name": "Konata Izumi", "awesome": true} + # + # To include the result of some method calls on the model use :methods: + # + # user.as_json(:methods => :permalink) + # # => {"id": 1, "name": "Konata Izumi", "age": 16, + # "created_at": "2006/08/01", "awesome": true, + # "permalink": "1-konata-izumi"} + # + # To include associations use :include: + # + # user.as_json(:include => :posts) + # # => {"id": 1, "name": "Konata Izumi", "age": 16, + # "created_at": "2006/08/01", "awesome": true, + # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"}, + # {"id": 2, author_id: 1, "title": "So I was thinking"}]} + # + # Second level and higher order associations work as well: + # + # user.as_json(:include => { :posts => { + # :include => { :comments => { + # :only => :body } }, + # :only => :title } }) + # # => {"id": 1, "name": "Konata Izumi", "age": 16, + # "created_at": "2006/08/01", "awesome": true, + # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}], + # "title": "Welcome to the weblog"}, + # {"comments": [{"body": "Don't think too hard"}], + # "title": "So I was thinking"}]} + def as_json(options = nil) + root = include_root_in_json + root = options[:root] if options.try(:key?, :root) + if root + root = self.class.model_name.element if root == true + { root => serializable_hash(options) } + else + serializable_hash(options) + end + end + + def from_json(json, include_root=include_root_in_json) + hash = ActiveSupport::JSON.decode(json) + hash = hash.values.first if include_root + self.attributes = hash + self + end + end + end +end diff --git a/activemodel/lib/active_model/serializable/xml.rb b/activemodel/lib/active_model/serializable/xml.rb new file mode 100644 index 0000000000..d11cee9b42 --- /dev/null +++ b/activemodel/lib/active_model/serializable/xml.rb @@ -0,0 +1,195 @@ +require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/array/conversions' +require 'active_support/core_ext/hash/conversions' +require 'active_support/core_ext/hash/slice' + +module ActiveModel + # == Active Model Serializable as XML + module Serializable + module XML + extend ActiveSupport::Concern + include ActiveModel::Serializable + + class Serializer #:nodoc: + class Attribute #:nodoc: + attr_reader :name, :value, :type + + def initialize(name, serializable, value) + @name, @serializable = name, serializable + value = value.in_time_zone if value.respond_to?(:in_time_zone) + @value = value + @type = compute_type + end + + def decorations + decorations = {} + decorations[:encoding] = 'base64' if type == :binary + decorations[:type] = (type == :string) ? nil : type + decorations[:nil] = true if value.nil? + decorations + end + + protected + + def compute_type + return if value.nil? + type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] + type ||= :string if value.respond_to?(:to_str) + type ||= :yaml + type + end + end + + class MethodAttribute < Attribute #:nodoc: + end + + attr_reader :options + + def initialize(serializable, options = nil) + @serializable = serializable + @options = options ? options.dup : {} + end + + def serializable_hash + @serializable.serializable_hash(@options.except(:include)) + end + + def serializable_collection + methods = Array.wrap(options[:methods]).map(&:to_s) + serializable_hash.map do |name, value| + name = name.to_s + if methods.include?(name) + self.class::MethodAttribute.new(name, @serializable, value) + else + self.class::Attribute.new(name, @serializable, value) + end + end + end + + def serialize + require 'builder' unless defined? ::Builder + + options[:indent] ||= 2 + options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent]) + + @builder = options[:builder] + @builder.instruct! unless options[:skip_instruct] + + root = (options[:root] || @serializable.class.model_name.element).to_s + root = ActiveSupport::XmlMini.rename_key(root, options) + + args = [root] + args << {:xmlns => options[:namespace]} if options[:namespace] + args << {:type => options[:type]} if options[:type] && !options[:skip_types] + + @builder.tag!(*args) do + add_attributes_and_methods + add_includes + add_extra_behavior + add_procs + yield @builder if block_given? + end + end + + private + + def add_extra_behavior + end + + def add_attributes_and_methods + serializable_collection.each do |attribute| + key = ActiveSupport::XmlMini.rename_key(attribute.name, options) + ActiveSupport::XmlMini.to_tag(key, attribute.value, + options.merge(attribute.decorations)) + end + end + + def add_includes + @serializable.send(:serializable_add_includes, options) do |association, records, opts| + add_associations(association, records, opts) + end + end + + # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well. + def add_associations(association, records, opts) + merged_options = opts.merge(options.slice(:builder, :indent)) + merged_options[:skip_instruct] = true + + if records.is_a?(Enumerable) + tag = ActiveSupport::XmlMini.rename_key(association.to_s, options) + type = options[:skip_types] ? { } : {:type => "array"} + association_name = association.to_s.singularize + merged_options[:root] = association_name + + if records.empty? + @builder.tag!(tag, type) + else + @builder.tag!(tag, type) do + records.each do |record| + if options[:skip_types] + record_type = {} + else + record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name + record_type = {:type => record_class} + end + + record.to_xml merged_options.merge(record_type) + end + end + end + else + merged_options[:root] = association.to_s + records.to_xml(merged_options) + end + end + + def add_procs + if procs = options.delete(:procs) + Array.wrap(procs).each do |proc| + if proc.arity == 1 + proc.call(options) + else + proc.call(options, @serializable) + end + end + end + end + end + + # Returns XML representing the model. Configuration can be + # passed through +options+. + # + # Without any +options+, the returned XML string will include all the model's + # attributes. For example: + # + # user = User.find(1) + # user.to_xml + # + # + # + # 1 + # David + # 16 + # 2011-01-30T22:29:23Z + # + # + # The :only and :except options can be used to limit the attributes + # included, and work similar to the +attributes+ method. + # + # To include the result of some method calls on the model use :methods. + # + # To include associations use :include. + # + # For further documentation see activerecord/lib/active_record/serializers/xml_serializer.xml. + def to_xml(options = {}, &block) + Serializer.new(self, options).serialize(&block) + end + + def from_xml(xml) + self.attributes = Hash.from_xml(xml).values.first + self + end + end + end +end diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index a4b58ab456..439302c632 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -1,139 +1,10 @@ -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/array/wrap' - - module ActiveModel - # == Active Model Serialization - # - # Provides a basic serialization to a serializable_hash for your object. - # - # A minimal implementation could be: - # - # class Person - # - # include ActiveModel::Serialization - # - # attr_accessor :name - # - # def attributes - # {'name' => name} - # end - # - # end - # - # Which would provide you with: - # - # person = Person.new - # person.serializable_hash # => {"name"=>nil} - # person.name = "Bob" - # person.serializable_hash # => {"name"=>"Bob"} - # - # You need to declare some sort of attributes hash which contains the attributes - # you want to serialize and their current value. - # - # Most of the time though, you will want to include the JSON or XML - # serializations. Both of these modules automatically include the - # ActiveModel::Serialization module, so there is no need to explicitly - # include it. - # - # So a minimal implementation including XML and JSON would be: - # - # class Person - # - # include ActiveModel::Serializers::JSON - # include ActiveModel::Serializers::Xml - # - # attr_accessor :name - # - # def attributes - # {'name' => name} - # end - # - # end - # - # Which would provide you with: - # - # person = Person.new - # person.serializable_hash # => {"name"=>nil} - # person.as_json # => {"name"=>nil} - # person.to_json # => "{\"name\":null}" - # person.to_xml # => "\n {"name"=>"Bob"} - # person.as_json # => {"name"=>"Bob"} - # person.to_json # => "{\"name\":\"Bob\"}" - # person.to_xml # => "\n:only, :except and :methods . module Serialization - def serializable_hash(options = nil) - options ||= {} - - attribute_names = attributes.keys.sort - if only = options[:only] - attribute_names &= Array.wrap(only).map(&:to_s) - elsif except = options[:except] - attribute_names -= Array.wrap(except).map(&:to_s) - end - - hash = {} - attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) } - - method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) } - method_names.each { |n| hash[n] = send(n) } - - serializable_add_includes(options) do |association, records, opts| - hash[association] = if records.is_a?(Enumerable) - records.map { |a| a.serializable_hash(opts) } - else - records.serializable_hash(opts) - end - end + extend ActiveSupport::Concern + include ActiveModel::Serializable - hash + included do + ActiveSupport::Deprecation.warn "ActiveModel::Serialization is deprecated in favor of ActiveModel::Serializable" end - - private - - # Hook method defining how an attribute value should be retrieved for - # serialization. By default this is assumed to be an instance named after - # the attribute. Override this method in subclasses should you need to - # retrieve the value for a given attribute differently: - # - # class MyClass - # include ActiveModel::Validations - # - # def initialize(data = {}) - # @data = data - # end - # - # def read_attribute_for_serialization(key) - # @data[key] - # end - # end - # - alias :read_attribute_for_serialization :send - - # Add associations specified via the :include option. - # - # Expects a block that takes as arguments: - # +association+ - name of the association - # +records+ - the association record(s) to be serialized - # +opts+ - options for the association records - def serializable_add_includes(options = {}) #:nodoc: - return unless include = options[:include] - - unless include.is_a?(Hash) - include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }] - end - - include.each do |association, opts| - if records = send(association) - yield association, records, opts - end - end - end end -end +end \ No newline at end of file diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb index 6d0746a3e8..a541a1053d 100644 --- a/activemodel/lib/active_model/serializer.rb +++ b/activemodel/lib/active_model/serializer.rb @@ -1,10 +1,70 @@ require "active_support/core_ext/class/attribute" require "active_support/core_ext/string/inflections" require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/introspection" require "set" module ActiveModel + # Active Model Array Serializer + class ArraySerializer + attr_reader :object, :scope + + def initialize(object, scope) + @object, @scope = object, scope + end + + def serializable_array + @object.map do |item| + if serializer = Serializer::Finder.find(item, scope) + serializer.new(item, scope) + else + item + end + end + end + + def as_json(*args) + serializable_array.as_json(*args) + end + end + + # Active Model Serializer class Serializer + module Finder + mattr_accessor :constantizer + @@constantizer = ActiveSupport::Inflector + + # Finds a serializer for the given object in the given scope. + # If the object implements a +model_serializer+ method, it does + # not do a scope lookup but uses the model_serializer method instead. + def self.find(object, scope) + if object.respond_to?(:model_serializer) + object.model_serializer + else + scope = scope.class unless scope.respond_to?(:const_defined?) + object = object.class unless object.respond_to?(:name) + serializer = "#{object.name.demodulize}Serializer" + + begin + scope.const_get serializer + rescue NameError => e + raise unless e.message =~ /uninitialized constant ([\w_]+::)*#{serializer}$/ + scope.parents.each do |parent| + return parent.const_get(serializer) if parent.const_defined?(serializer) + end + nil + end + end + end + end + + # Defines the serialization scope. Core extension serializers + # are defined in this module so a scoped lookup is able to find + # core extension serializers. + module Scope + ArraySerializer = ::ActiveModel::ArraySerializer + end + module Associations class Config < Struct.new(:name, :options) def serializer diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index c845440120..9efd7c5f69 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -1,108 +1,12 @@ -require 'active_support/json' -require 'active_support/core_ext/class/attribute' - module ActiveModel - # == Active Model JSON Serializer module Serializers module JSON extend ActiveSupport::Concern - include ActiveModel::Serialization + include ActiveModel::Serializable::JSON included do - extend ActiveModel::Naming - - class_attribute :include_root_in_json - self.include_root_in_json = true - end - - # Returns a hash representing the model. Some configuration can be - # passed through +options+. - # - # The option include_root_in_json controls the top-level behavior - # of +as_json+. If true (the default) +as_json+ will emit a single root - # node named after the object's type. For example: - # - # user = User.find(1) - # user.as_json - # # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16, - # "created_at": "2006/08/01", "awesome": true} } - # - # ActiveRecord::Base.include_root_in_json = false - # user.as_json - # # => {"id": 1, "name": "Konata Izumi", "age": 16, - # "created_at": "2006/08/01", "awesome": true} - # - # This behavior can also be achieved by setting the :root option to +false+ as in: - # - # user = User.find(1) - # user.as_json(root: false) - # # => {"id": 1, "name": "Konata Izumi", "age": 16, - # "created_at": "2006/08/01", "awesome": true} - # - # The remainder of the examples in this section assume include_root_in_json is set to - # false. - # - # Without any +options+, the returned Hash will include all the model's - # attributes. For example: - # - # user = User.find(1) - # user.as_json - # # => {"id": 1, "name": "Konata Izumi", "age": 16, - # "created_at": "2006/08/01", "awesome": true} - # - # The :only and :except options can be used to limit the attributes - # included, and work similar to the +attributes+ method. For example: - # - # user.as_json(:only => [ :id, :name ]) - # # => {"id": 1, "name": "Konata Izumi"} - # - # user.as_json(:except => [ :id, :created_at, :age ]) - # # => {"name": "Konata Izumi", "awesome": true} - # - # To include the result of some method calls on the model use :methods: - # - # user.as_json(:methods => :permalink) - # # => {"id": 1, "name": "Konata Izumi", "age": 16, - # "created_at": "2006/08/01", "awesome": true, - # "permalink": "1-konata-izumi"} - # - # To include associations use :include: - # - # user.as_json(:include => :posts) - # # => {"id": 1, "name": "Konata Izumi", "age": 16, - # "created_at": "2006/08/01", "awesome": true, - # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"}, - # {"id": 2, author_id: 1, "title": "So I was thinking"}]} - # - # Second level and higher order associations work as well: - # - # user.as_json(:include => { :posts => { - # :include => { :comments => { - # :only => :body } }, - # :only => :title } }) - # # => {"id": 1, "name": "Konata Izumi", "age": 16, - # "created_at": "2006/08/01", "awesome": true, - # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}], - # "title": "Welcome to the weblog"}, - # {"comments": [{"body": "Don't think too hard"}], - # "title": "So I was thinking"}]} - def as_json(options = nil) - root = include_root_in_json - root = options[:root] if options.try(:key?, :root) - if root - root = self.class.model_name.element if root == true - { root => serializable_hash(options) } - else - serializable_hash(options) - end - end - - def from_json(json, include_root=include_root_in_json) - hash = ActiveSupport::JSON.decode(json) - hash = hash.values.first if include_root - self.attributes = hash - self + ActiveSupport::Deprecation.warn "ActiveModel::Serializers::JSON is deprecated in favor of ActiveModel::Serializable::JSON" end end end -end +end \ No newline at end of file diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index d61d9d7119..620390da6b 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -1,195 +1,14 @@ -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/class/attribute_accessors' -require 'active_support/core_ext/array/conversions' -require 'active_support/core_ext/hash/conversions' -require 'active_support/core_ext/hash/slice' - module ActiveModel - # == Active Model XML Serializer module Serializers module Xml extend ActiveSupport::Concern - include ActiveModel::Serialization - - class Serializer #:nodoc: - class Attribute #:nodoc: - attr_reader :name, :value, :type - - def initialize(name, serializable, value) - @name, @serializable = name, serializable - value = value.in_time_zone if value.respond_to?(:in_time_zone) - @value = value - @type = compute_type - end - - def decorations - decorations = {} - decorations[:encoding] = 'base64' if type == :binary - decorations[:type] = (type == :string) ? nil : type - decorations[:nil] = true if value.nil? - decorations - end - - protected - - def compute_type - return if value.nil? - type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] - type ||= :string if value.respond_to?(:to_str) - type ||= :yaml - type - end - end - - class MethodAttribute < Attribute #:nodoc: - end - - attr_reader :options - - def initialize(serializable, options = nil) - @serializable = serializable - @options = options ? options.dup : {} - end - - def serializable_hash - @serializable.serializable_hash(@options.except(:include)) - end - - def serializable_collection - methods = Array.wrap(options[:methods]).map(&:to_s) - serializable_hash.map do |name, value| - name = name.to_s - if methods.include?(name) - self.class::MethodAttribute.new(name, @serializable, value) - else - self.class::Attribute.new(name, @serializable, value) - end - end - end - - def serialize - require 'builder' unless defined? ::Builder - - options[:indent] ||= 2 - options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent]) - - @builder = options[:builder] - @builder.instruct! unless options[:skip_instruct] + include ActiveModel::Serializable::XML - root = (options[:root] || @serializable.class.model_name.element).to_s - root = ActiveSupport::XmlMini.rename_key(root, options) - - args = [root] - args << {:xmlns => options[:namespace]} if options[:namespace] - args << {:type => options[:type]} if options[:type] && !options[:skip_types] - - @builder.tag!(*args) do - add_attributes_and_methods - add_includes - add_extra_behavior - add_procs - yield @builder if block_given? - end - end - - private - - def add_extra_behavior - end - - def add_attributes_and_methods - serializable_collection.each do |attribute| - key = ActiveSupport::XmlMini.rename_key(attribute.name, options) - ActiveSupport::XmlMini.to_tag(key, attribute.value, - options.merge(attribute.decorations)) - end - end - - def add_includes - @serializable.send(:serializable_add_includes, options) do |association, records, opts| - add_associations(association, records, opts) - end - end - - # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well. - def add_associations(association, records, opts) - merged_options = opts.merge(options.slice(:builder, :indent)) - merged_options[:skip_instruct] = true - - if records.is_a?(Enumerable) - tag = ActiveSupport::XmlMini.rename_key(association.to_s, options) - type = options[:skip_types] ? { } : {:type => "array"} - association_name = association.to_s.singularize - merged_options[:root] = association_name - - if records.empty? - @builder.tag!(tag, type) - else - @builder.tag!(tag, type) do - records.each do |record| - if options[:skip_types] - record_type = {} - else - record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name - record_type = {:type => record_class} - end - - record.to_xml merged_options.merge(record_type) - end - end - end - else - merged_options[:root] = association.to_s - records.to_xml(merged_options) - end - end - - def add_procs - if procs = options.delete(:procs) - Array.wrap(procs).each do |proc| - if proc.arity == 1 - proc.call(options) - else - proc.call(options, @serializable) - end - end - end - end - end - - # Returns XML representing the model. Configuration can be - # passed through +options+. - # - # Without any +options+, the returned XML string will include all the model's - # attributes. For example: - # - # user = User.find(1) - # user.to_xml - # - # - # - # 1 - # David - # 16 - # 2011-01-30T22:29:23Z - # - # - # The :only and :except options can be used to limit the attributes - # included, and work similar to the +attributes+ method. - # - # To include the result of some method calls on the model use :methods. - # - # To include associations use :include. - # - # For further documentation see activerecord/lib/active_record/serializers/xml_serializer.xml. - def to_xml(options = {}, &block) - Serializer.new(self, options).serialize(&block) - end + Serializer = ActiveModel::Serializable::XML::Serializer - def from_xml(xml) - self.attributes = Hash.from_xml(xml).values.first - self + included do + ActiveSupport::Deprecation.warn "ActiveModel::Serializers::Xml is deprecated in favor of ActiveModel::Serializable::XML" end end end -end +end \ No newline at end of file diff --git a/activemodel/test/cases/serializable/json_test.rb b/activemodel/test/cases/serializable/json_test.rb new file mode 100644 index 0000000000..ad5b04091e --- /dev/null +++ b/activemodel/test/cases/serializable/json_test.rb @@ -0,0 +1,219 @@ +require 'cases/helper' +require 'models/contact' +require 'models/automobile' +require 'active_support/core_ext/object/instance_variables' + +class Contact + extend ActiveModel::Naming + include ActiveModel::Serializable::JSON + include ActiveModel::Validations + + def attributes=(hash) + hash.each do |k, v| + instance_variable_set("@#{k}", v) + end + end + + def attributes + instance_values + end unless method_defined?(:attributes) +end + +class JsonSerializationTest < ActiveModel::TestCase + def setup + @contact = Contact.new + @contact.name = 'Konata Izumi' + @contact.age = 16 + @contact.created_at = Time.utc(2006, 8, 1) + @contact.awesome = true + @contact.preferences = { 'shows' => 'anime' } + end + + test "should include root in json" do + json = @contact.to_json + + assert_match %r{^\{"contact":\{}, json + assert_match %r{"name":"Konata Izumi"}, json + assert_match %r{"age":16}, json + assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_match %r{"awesome":true}, json + assert_match %r{"preferences":\{"shows":"anime"\}}, json + end + + test "should not include root in json (class method)" do + begin + Contact.include_root_in_json = false + json = @contact.to_json + + assert_no_match %r{^\{"contact":\{}, json + assert_match %r{"name":"Konata Izumi"}, json + assert_match %r{"age":16}, json + assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_match %r{"awesome":true}, json + assert_match %r{"preferences":\{"shows":"anime"\}}, json + ensure + Contact.include_root_in_json = true + end + end + + test "should include root in json (option) even if the default is set to false" do + begin + Contact.include_root_in_json = false + json = @contact.to_json(:root => true) + assert_match %r{^\{"contact":\{}, json + ensure + Contact.include_root_in_json = true + end + end + + test "should not include root in json (option)" do + + json = @contact.to_json(:root => false) + + assert_no_match %r{^\{"contact":\{}, json + end + + test "should include custom root in json" do + json = @contact.to_json(:root => 'json_contact') + + assert_match %r{^\{"json_contact":\{}, json + assert_match %r{"name":"Konata Izumi"}, json + assert_match %r{"age":16}, json + assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_match %r{"awesome":true}, json + assert_match %r{"preferences":\{"shows":"anime"\}}, json + end + + test "should encode all encodable attributes" do + json = @contact.to_json + + assert_match %r{"name":"Konata Izumi"}, json + assert_match %r{"age":16}, json + assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_match %r{"awesome":true}, json + assert_match %r{"preferences":\{"shows":"anime"\}}, json + end + + test "should allow attribute filtering with only" do + json = @contact.to_json(:only => [:name, :age]) + + assert_match %r{"name":"Konata Izumi"}, json + assert_match %r{"age":16}, json + assert_no_match %r{"awesome":true}, json + assert !json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_no_match %r{"preferences":\{"shows":"anime"\}}, json + end + + test "should allow attribute filtering with except" do + json = @contact.to_json(:except => [:name, :age]) + + assert_no_match %r{"name":"Konata Izumi"}, json + assert_no_match %r{"age":16}, json + assert_match %r{"awesome":true}, json + assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_match %r{"preferences":\{"shows":"anime"\}}, json + end + + test "methods are called on object" do + # Define methods on fixture. + def @contact.label; "Has cheezburger"; end + def @contact.favorite_quote; "Constraints are liberating"; end + + # Single method. + assert_match %r{"label":"Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label) + + # Both methods. + methods_json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote]) + assert_match %r{"label":"Has cheezburger"}, methods_json + assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json + end + + test "should return OrderedHash for errors" do + contact = Contact.new + contact.errors.add :name, "can't be blank" + contact.errors.add :name, "is too short (minimum is 2 characters)" + contact.errors.add :age, "must be 16 or over" + + hash = ActiveSupport::OrderedHash.new + hash[:name] = ["can't be blank", "is too short (minimum is 2 characters)"] + hash[:age] = ["must be 16 or over"] + assert_equal hash.to_json, contact.errors.to_json + end + + test "serializable_hash should not modify options passed in argument" do + options = { :except => :name } + @contact.serializable_hash(options) + + assert_nil options[:only] + assert_equal :name, options[:except] + end + + test "as_json should return a hash" do + json = @contact.as_json + + assert_kind_of Hash, json + assert_kind_of Hash, json['contact'] + %w(name age created_at awesome preferences).each do |field| + assert_equal @contact.send(field), json['contact'][field] + end + end + + test "from_json should set the object's attributes" do + json = @contact.to_json + result = Contact.new.from_json(json) + + assert_equal result.name, @contact.name + assert_equal result.age, @contact.age + assert_equal Time.parse(result.created_at), @contact.created_at + assert_equal result.awesome, @contact.awesome + assert_equal result.preferences, @contact.preferences + end + + test "from_json should work without a root (method parameter)" do + json = @contact.to_json(:root => false) + result = Contact.new.from_json(json, false) + + assert_equal result.name, @contact.name + assert_equal result.age, @contact.age + assert_equal Time.parse(result.created_at), @contact.created_at + assert_equal result.awesome, @contact.awesome + assert_equal result.preferences, @contact.preferences + end + + test "from_json should work without a root (class attribute)" do + begin + Contact.include_root_in_json = false + json = @contact.to_json + result = Contact.new.from_json(json) + + assert_equal result.name, @contact.name + assert_equal result.age, @contact.age + assert_equal Time.parse(result.created_at), @contact.created_at + assert_equal result.awesome, @contact.awesome + assert_equal result.preferences, @contact.preferences + ensure + Contact.include_root_in_json = true + end + end + + test "custom as_json should be honored when generating json" do + def @contact.as_json(options); { :name => name, :created_at => created_at }; end + json = @contact.to_json + + assert_match %r{"name":"Konata Izumi"}, json + assert_match %r{"created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}}, json + assert_no_match %r{"awesome":}, json + assert_no_match %r{"preferences":}, json + end + + test "custom as_json options should be extendible" do + def @contact.as_json(options = {}); super(options.merge(:only => [:name])); end + json = @contact.to_json + + assert_match %r{"name":"Konata Izumi"}, json + assert_no_match %r{"created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}}, json + assert_no_match %r{"awesome":}, json + assert_no_match %r{"preferences":}, json + end + +end diff --git a/activemodel/test/cases/serializable/xml_test.rb b/activemodel/test/cases/serializable/xml_test.rb new file mode 100644 index 0000000000..817ca1e736 --- /dev/null +++ b/activemodel/test/cases/serializable/xml_test.rb @@ -0,0 +1,206 @@ +require 'cases/helper' +require 'models/contact' +require 'active_support/core_ext/object/instance_variables' +require 'ostruct' + +class Contact + extend ActiveModel::Naming + include ActiveModel::Serializable::XML + + attr_accessor :address, :friends + + def attributes + instance_values.except("address", "friends") + end +end + +module Admin + class Contact < ::Contact + end +end + +class Customer < Struct.new(:name) +end + +class Address + extend ActiveModel::Naming + include ActiveModel::Serializable::XML + + attr_accessor :street, :city, :state, :zip + + def attributes + instance_values + end +end + +class SerializableContact < Contact + def serializable_hash(options={}) + super(options.merge(:only => [:name, :age])) + end +end + +class XmlSerializationTest < ActiveModel::TestCase + def setup + @contact = Contact.new + @contact.name = 'aaron stack' + @contact.age = 25 + @contact.created_at = Time.utc(2006, 8, 1) + @contact.awesome = false + customer = Customer.new + customer.name = "John" + @contact.preferences = customer + @contact.address = Address.new + @contact.address.street = "123 Lane" + @contact.address.city = "Springfield" + @contact.address.state = "CA" + @contact.address.zip = 11111 + @contact.friends = [Contact.new, Contact.new] + end + + test "should serialize default root" do + @xml = @contact.to_xml + assert_match %r{^}, @xml + assert_match %r{$}, @xml + end + + test "should serialize namespaced root" do + @xml = Admin::Contact.new(@contact.attributes).to_xml + assert_match %r{^}, @xml + assert_match %r{$}, @xml + end + + test "should serialize default root with namespace" do + @xml = @contact.to_xml :namespace => "http://xml.rubyonrails.org/contact" + assert_match %r{^}, @xml + assert_match %r{$}, @xml + end + + test "should serialize custom root" do + @xml = @contact.to_xml :root => 'xml_contact' + assert_match %r{^}, @xml + assert_match %r{$}, @xml + end + + test "should allow undasherized tags" do + @xml = @contact.to_xml :root => 'xml_contact', :dasherize => false + assert_match %r{^}, @xml + assert_match %r{$}, @xml + assert_match %r{ 'xml_contact', :camelize => true + assert_match %r{^}, @xml + assert_match %r{$}, @xml + assert_match %r{ 'xml_contact', :camelize => :lower + assert_match %r{^}, @xml + assert_match %r{$}, @xml + assert_match %r{aaron stack}, @xml + assert_match %r{25}, @xml + assert_no_match %r{}, @xml + end + + test "should allow skipped types" do + @xml = @contact.to_xml :skip_types => true + assert_match %r{25}, @xml + end + + test "should include yielded additions" do + @xml = @contact.to_xml do |xml| + xml.creator "David" + end + assert_match %r{David}, @xml + end + + test "should serialize string" do + assert_match %r{aaron stack}, @contact.to_xml + end + + test "should serialize nil" do + assert_match %r{}, @contact.to_xml(:methods => :pseudonyms) + end + + test "should serialize integer" do + assert_match %r{25}, @contact.to_xml + end + + test "should serialize datetime" do + assert_match %r{2006-08-01T00:00:00Z}, @contact.to_xml + end + + test "should serialize boolean" do + assert_match %r{false}, @contact.to_xml + end + + test "should serialize array" do + assert_match %r{\s*twitter\s*github\s*}, @contact.to_xml(:methods => :social) + end + + test "should serialize hash" do + assert_match %r{\s*github\s*}, @contact.to_xml(:methods => :network) + end + + test "should serialize yaml" do + assert_match %r{--- !ruby/struct:Customer(\s*)\nname: John\n}, @contact.to_xml + end + + test "should call proc on object" do + proc = Proc.new { |options| options[:builder].tag!('nationality', 'unknown') } + xml = @contact.to_xml(:procs => [ proc ]) + assert_match %r{unknown}, xml + end + + test 'should supply serializable to second proc argument' do + proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) } + xml = @contact.to_xml(:procs => [ proc ]) + assert_match %r{kcats noraa}, xml + end + + test "should serialize string correctly when type passed" do + xml = @contact.to_xml :type => 'Contact' + assert_match %r{}, xml + assert_match %r{aaron stack}, xml + end + + test "include option with singular association" do + xml = @contact.to_xml :include => :address, :indent => 0 + assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true)) + end + + test "include option with plural association" do + xml = @contact.to_xml :include => :friends, :indent => 0 + assert_match %r{}, xml + assert_match %r{}, xml + end + + test "multiple includes" do + xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => [ :address, :friends ] + assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true)) + assert_match %r{}, xml + assert_match %r{}, xml + end + + test "include with options" do + xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => { :address => { :only => :city } } + assert xml.include?(%(>
Springfield
)) + end + + test "propagates skip_types option to included associations" do + xml = @contact.to_xml :include => :friends, :indent => 0, :skip_types => true + assert_match %r{}, xml + assert_match %r{}, xml + end +end diff --git a/activemodel/test/cases/serializable_test.rb b/activemodel/test/cases/serializable_test.rb new file mode 100644 index 0000000000..46ee372c6f --- /dev/null +++ b/activemodel/test/cases/serializable_test.rb @@ -0,0 +1,151 @@ +require "cases/helper" +require 'active_support/core_ext/object/instance_variables' + +class SerializationTest < ActiveModel::TestCase + class User + include ActiveModel::Serializable + + attr_accessor :name, :email, :gender, :address, :friends + + def initialize(name, email, gender) + @name, @email, @gender = name, email, gender + @friends = [] + end + + def attributes + instance_values.except("address", "friends") + end + + def foo + 'i_am_foo' + end + end + + class Address + include ActiveModel::Serializable + + attr_accessor :street, :city, :state, :zip + + def attributes + instance_values + end + end + + setup do + @user = User.new('David', 'david@example.com', 'male') + @user.address = Address.new + @user.address.street = "123 Lane" + @user.address.city = "Springfield" + @user.address.state = "CA" + @user.address.zip = 11111 + @user.friends = [User.new('Joe', 'joe@example.com', 'male'), + User.new('Sue', 'sue@example.com', 'female')] + end + + def test_method_serializable_hash_should_work + expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} + assert_equal expected , @user.serializable_hash + end + + def test_method_serializable_hash_should_work_with_only_option + expected = {"name"=>"David"} + assert_equal expected , @user.serializable_hash(:only => [:name]) + end + + def test_method_serializable_hash_should_work_with_except_option + expected = {"gender"=>"male", "email"=>"david@example.com"} + assert_equal expected , @user.serializable_hash(:except => [:name]) + end + + def test_method_serializable_hash_should_work_with_methods_option + expected = {"name"=>"David", "gender"=>"male", :foo=>"i_am_foo", "email"=>"david@example.com"} + assert_equal expected , @user.serializable_hash(:methods => [:foo]) + end + + def test_method_serializable_hash_should_work_with_only_and_methods + expected = {:foo=>"i_am_foo"} + assert_equal expected , @user.serializable_hash(:only => [], :methods => [:foo]) + end + + def test_method_serializable_hash_should_work_with_except_and_methods + expected = {"gender"=>"male", :foo=>"i_am_foo"} + assert_equal expected , @user.serializable_hash(:except => [:name, :email], :methods => [:foo]) + end + + def test_should_not_call_methods_that_dont_respond + expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} + assert_equal expected , @user.serializable_hash(:methods => [:bar]) + end + + def test_should_use_read_attribute_for_serialization + def @user.read_attribute_for_serialization(n) + "Jon" + end + + expected = { "name" => "Jon" } + assert_equal expected, @user.serializable_hash(:only => :name) + end + + def test_include_option_with_singular_association + expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", + :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} + assert_equal expected , @user.serializable_hash(:include => :address) + end + + def test_include_option_with_plural_association + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + assert_equal expected , @user.serializable_hash(:include => :friends) + end + + def test_include_option_with_empty_association + @user.friends = [] + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]} + assert_equal expected , @user.serializable_hash(:include => :friends) + end + + def test_multiple_includes + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, + :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + assert_equal expected , @user.serializable_hash(:include => [:address, :friends]) + end + + def test_include_with_options + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :address=>{"street"=>"123 Lane"}} + assert_equal expected , @user.serializable_hash(:include => {:address => {:only => "street"}}) + end + + def test_nested_include + @user.friends.first.friends = [@user] + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', + :friends => [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', :friends => []}]} + assert_equal expected , @user.serializable_hash(:include => {:friends => {:include => :friends}}) + end + + def test_only_include + expected = {"name"=>"David", :friends => [{"name" => "Joe"}, {"name" => "Sue"}]} + assert_equal expected , @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}}) + end + + def test_except_include + expected = {"name"=>"David", "email"=>"david@example.com", + :friends => [{"name" => 'Joe', "email" => 'joe@example.com'}, + {"name" => "Sue", "email" => 'sue@example.com'}]} + assert_equal expected , @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}}) + end + + def test_multiple_includes_with_options + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :address=>{"street"=>"123 Lane"}, + :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + assert_equal expected , @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends]) + end + +end diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb deleted file mode 100644 index b8dad9d51f..0000000000 --- a/activemodel/test/cases/serialization_test.rb +++ /dev/null @@ -1,151 +0,0 @@ -require "cases/helper" -require 'active_support/core_ext/object/instance_variables' - -class SerializationTest < ActiveModel::TestCase - class User - include ActiveModel::Serialization - - attr_accessor :name, :email, :gender, :address, :friends - - def initialize(name, email, gender) - @name, @email, @gender = name, email, gender - @friends = [] - end - - def attributes - instance_values.except("address", "friends") - end - - def foo - 'i_am_foo' - end - end - - class Address - include ActiveModel::Serialization - - attr_accessor :street, :city, :state, :zip - - def attributes - instance_values - end - end - - setup do - @user = User.new('David', 'david@example.com', 'male') - @user.address = Address.new - @user.address.street = "123 Lane" - @user.address.city = "Springfield" - @user.address.state = "CA" - @user.address.zip = 11111 - @user.friends = [User.new('Joe', 'joe@example.com', 'male'), - User.new('Sue', 'sue@example.com', 'female')] - end - - def test_method_serializable_hash_should_work - expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash - end - - def test_method_serializable_hash_should_work_with_only_option - expected = {"name"=>"David"} - assert_equal expected , @user.serializable_hash(:only => [:name]) - end - - def test_method_serializable_hash_should_work_with_except_option - expected = {"gender"=>"male", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash(:except => [:name]) - end - - def test_method_serializable_hash_should_work_with_methods_option - expected = {"name"=>"David", "gender"=>"male", :foo=>"i_am_foo", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash(:methods => [:foo]) - end - - def test_method_serializable_hash_should_work_with_only_and_methods - expected = {:foo=>"i_am_foo"} - assert_equal expected , @user.serializable_hash(:only => [], :methods => [:foo]) - end - - def test_method_serializable_hash_should_work_with_except_and_methods - expected = {"gender"=>"male", :foo=>"i_am_foo"} - assert_equal expected , @user.serializable_hash(:except => [:name, :email], :methods => [:foo]) - end - - def test_should_not_call_methods_that_dont_respond - expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash(:methods => [:bar]) - end - - def test_should_use_read_attribute_for_serialization - def @user.read_attribute_for_serialization(n) - "Jon" - end - - expected = { "name" => "Jon" } - assert_equal expected, @user.serializable_hash(:only => :name) - end - - def test_include_option_with_singular_association - expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", - :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} - assert_equal expected , @user.serializable_hash(:include => :address) - end - - def test_include_option_with_plural_association - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} - assert_equal expected , @user.serializable_hash(:include => :friends) - end - - def test_include_option_with_empty_association - @user.friends = [] - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]} - assert_equal expected , @user.serializable_hash(:include => :friends) - end - - def test_multiple_includes - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} - assert_equal expected , @user.serializable_hash(:include => [:address, :friends]) - end - - def test_include_with_options - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane"}} - assert_equal expected , @user.serializable_hash(:include => {:address => {:only => "street"}}) - end - - def test_nested_include - @user.friends.first.friends = [@user] - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', - :friends => [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', :friends => []}]} - assert_equal expected , @user.serializable_hash(:include => {:friends => {:include => :friends}}) - end - - def test_only_include - expected = {"name"=>"David", :friends => [{"name" => "Joe"}, {"name" => "Sue"}]} - assert_equal expected , @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}}) - end - - def test_except_include - expected = {"name"=>"David", "email"=>"david@example.com", - :friends => [{"name" => 'Joe', "email" => 'joe@example.com'}, - {"name" => "Sue", "email" => 'sue@example.com'}]} - assert_equal expected , @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}}) - end - - def test_multiple_includes_with_options - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane"}, - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} - assert_equal expected , @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends]) - end - -end diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index 00d519dc1a..e99b3692ec 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -9,18 +9,34 @@ class SerializerTest < ActiveModel::TestCase def read_attribute_for_serialization(name) @attributes[name] end + + def as_json(*) + { :model => "Model" } + end end - class User < Model + class User + include ActiveModel::Serializable + attr_accessor :superuser + attr_writer :model_serializer def initialize(hash={}) - super hash.merge(:first_name => "Jose", :last_name => "Valim", :password => "oh noes yugive my password") + @model_serializer = nil + @attributes = hash.merge(:first_name => "Jose", :last_name => "Valim", :password => "oh noes yugive my password") + end + + def read_attribute_for_serialization(name) + @attributes[name] end def super_user? @superuser end + + def model_serializer + @model_serializer || super + end end class Post < Model @@ -403,4 +419,47 @@ class SerializerTest < ActiveModel::TestCase } }, serializer.as_json) end -end + + def test_array_serializer + model = Model.new + user = User.new + post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") + comments = Comment.new(:title => "Comment1", :id => 1) + post.comments = [] + + array = [model, post, comments] + serializer = ActiveModel::Serializer::Finder.find(array, user).new(array, user) + assert_equal([ + { :model => "Model" }, + { :post => { :body => "Body of new post", :comments => [], :title => "New Post" } }, + { :comment => { :title => "Comment1" } } + ], serializer.as_json) + end + + def test_array_serializer_respects_model_serializer + user = User.new(:first_name => "Jose", :last_name => "Valim") + user.model_serializer = User2Serializer + + array = [user] + serializer = ActiveModel::Serializer::Finder.find(array, user).new(array, {}) + assert_equal([ + { :user2 => { :last_name => "Valim", :first_name => "Jose", :ok => true } }, + ], serializer.as_json) + end + + def test_finder_respects_model_serializer + user = User.new(:first_name => "Jose", :last_name => "Valim") + assert_equal UserSerializer, user.model_serializer + + serializer = ActiveModel::Serializer::Finder.find(user, self).new(user, {}) + assert_equal({ + :user => { :last_name => "Valim", :first_name => "Jose"}, + }, serializer.as_json) + + user.model_serializer = User2Serializer + serializer = ActiveModel::Serializer::Finder.find(user, self).new(user, {}) + assert_equal({ + :user2 => { :last_name => "Valim", :first_name => "Jose", :ok => true }, + }, serializer.as_json) + end +end \ No newline at end of file diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb deleted file mode 100644 index a754d610b9..0000000000 --- a/activemodel/test/cases/serializers/json_serialization_test.rb +++ /dev/null @@ -1,219 +0,0 @@ -require 'cases/helper' -require 'models/contact' -require 'models/automobile' -require 'active_support/core_ext/object/instance_variables' - -class Contact - extend ActiveModel::Naming - include ActiveModel::Serializers::JSON - include ActiveModel::Validations - - def attributes=(hash) - hash.each do |k, v| - instance_variable_set("@#{k}", v) - end - end - - def attributes - instance_values - end unless method_defined?(:attributes) -end - -class JsonSerializationTest < ActiveModel::TestCase - def setup - @contact = Contact.new - @contact.name = 'Konata Izumi' - @contact.age = 16 - @contact.created_at = Time.utc(2006, 8, 1) - @contact.awesome = true - @contact.preferences = { 'shows' => 'anime' } - end - - test "should include root in json" do - json = @contact.to_json - - assert_match %r{^\{"contact":\{}, json - assert_match %r{"name":"Konata Izumi"}, json - assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) - assert_match %r{"awesome":true}, json - assert_match %r{"preferences":\{"shows":"anime"\}}, json - end - - test "should not include root in json (class method)" do - begin - Contact.include_root_in_json = false - json = @contact.to_json - - assert_no_match %r{^\{"contact":\{}, json - assert_match %r{"name":"Konata Izumi"}, json - assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) - assert_match %r{"awesome":true}, json - assert_match %r{"preferences":\{"shows":"anime"\}}, json - ensure - Contact.include_root_in_json = true - end - end - - test "should include root in json (option) even if the default is set to false" do - begin - Contact.include_root_in_json = false - json = @contact.to_json(:root => true) - assert_match %r{^\{"contact":\{}, json - ensure - Contact.include_root_in_json = true - end - end - - test "should not include root in json (option)" do - - json = @contact.to_json(:root => false) - - assert_no_match %r{^\{"contact":\{}, json - end - - test "should include custom root in json" do - json = @contact.to_json(:root => 'json_contact') - - assert_match %r{^\{"json_contact":\{}, json - assert_match %r{"name":"Konata Izumi"}, json - assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) - assert_match %r{"awesome":true}, json - assert_match %r{"preferences":\{"shows":"anime"\}}, json - end - - test "should encode all encodable attributes" do - json = @contact.to_json - - assert_match %r{"name":"Konata Izumi"}, json - assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) - assert_match %r{"awesome":true}, json - assert_match %r{"preferences":\{"shows":"anime"\}}, json - end - - test "should allow attribute filtering with only" do - json = @contact.to_json(:only => [:name, :age]) - - assert_match %r{"name":"Konata Izumi"}, json - assert_match %r{"age":16}, json - assert_no_match %r{"awesome":true}, json - assert !json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) - assert_no_match %r{"preferences":\{"shows":"anime"\}}, json - end - - test "should allow attribute filtering with except" do - json = @contact.to_json(:except => [:name, :age]) - - assert_no_match %r{"name":"Konata Izumi"}, json - assert_no_match %r{"age":16}, json - assert_match %r{"awesome":true}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) - assert_match %r{"preferences":\{"shows":"anime"\}}, json - end - - test "methods are called on object" do - # Define methods on fixture. - def @contact.label; "Has cheezburger"; end - def @contact.favorite_quote; "Constraints are liberating"; end - - # Single method. - assert_match %r{"label":"Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label) - - # Both methods. - methods_json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote]) - assert_match %r{"label":"Has cheezburger"}, methods_json - assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json - end - - test "should return OrderedHash for errors" do - contact = Contact.new - contact.errors.add :name, "can't be blank" - contact.errors.add :name, "is too short (minimum is 2 characters)" - contact.errors.add :age, "must be 16 or over" - - hash = ActiveSupport::OrderedHash.new - hash[:name] = ["can't be blank", "is too short (minimum is 2 characters)"] - hash[:age] = ["must be 16 or over"] - assert_equal hash.to_json, contact.errors.to_json - end - - test "serializable_hash should not modify options passed in argument" do - options = { :except => :name } - @contact.serializable_hash(options) - - assert_nil options[:only] - assert_equal :name, options[:except] - end - - test "as_json should return a hash" do - json = @contact.as_json - - assert_kind_of Hash, json - assert_kind_of Hash, json['contact'] - %w(name age created_at awesome preferences).each do |field| - assert_equal @contact.send(field), json['contact'][field] - end - end - - test "from_json should set the object's attributes" do - json = @contact.to_json - result = Contact.new.from_json(json) - - assert_equal result.name, @contact.name - assert_equal result.age, @contact.age - assert_equal Time.parse(result.created_at), @contact.created_at - assert_equal result.awesome, @contact.awesome - assert_equal result.preferences, @contact.preferences - end - - test "from_json should work without a root (method parameter)" do - json = @contact.to_json(:root => false) - result = Contact.new.from_json(json, false) - - assert_equal result.name, @contact.name - assert_equal result.age, @contact.age - assert_equal Time.parse(result.created_at), @contact.created_at - assert_equal result.awesome, @contact.awesome - assert_equal result.preferences, @contact.preferences - end - - test "from_json should work without a root (class attribute)" do - begin - Contact.include_root_in_json = false - json = @contact.to_json - result = Contact.new.from_json(json) - - assert_equal result.name, @contact.name - assert_equal result.age, @contact.age - assert_equal Time.parse(result.created_at), @contact.created_at - assert_equal result.awesome, @contact.awesome - assert_equal result.preferences, @contact.preferences - ensure - Contact.include_root_in_json = true - end - end - - test "custom as_json should be honored when generating json" do - def @contact.as_json(options); { :name => name, :created_at => created_at }; end - json = @contact.to_json - - assert_match %r{"name":"Konata Izumi"}, json - assert_match %r{"created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}}, json - assert_no_match %r{"awesome":}, json - assert_no_match %r{"preferences":}, json - end - - test "custom as_json options should be extendible" do - def @contact.as_json(options = {}); super(options.merge(:only => [:name])); end - json = @contact.to_json - - assert_match %r{"name":"Konata Izumi"}, json - assert_no_match %r{"created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}}, json - assert_no_match %r{"awesome":}, json - assert_no_match %r{"preferences":}, json - end - -end diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb deleted file mode 100644 index fc73d9dcd8..0000000000 --- a/activemodel/test/cases/serializers/xml_serialization_test.rb +++ /dev/null @@ -1,206 +0,0 @@ -require 'cases/helper' -require 'models/contact' -require 'active_support/core_ext/object/instance_variables' -require 'ostruct' - -class Contact - extend ActiveModel::Naming - include ActiveModel::Serializers::Xml - - attr_accessor :address, :friends - - def attributes - instance_values.except("address", "friends") - end -end - -module Admin - class Contact < ::Contact - end -end - -class Customer < Struct.new(:name) -end - -class Address - extend ActiveModel::Naming - include ActiveModel::Serializers::Xml - - attr_accessor :street, :city, :state, :zip - - def attributes - instance_values - end -end - -class SerializableContact < Contact - def serializable_hash(options={}) - super(options.merge(:only => [:name, :age])) - end -end - -class XmlSerializationTest < ActiveModel::TestCase - def setup - @contact = Contact.new - @contact.name = 'aaron stack' - @contact.age = 25 - @contact.created_at = Time.utc(2006, 8, 1) - @contact.awesome = false - customer = Customer.new - customer.name = "John" - @contact.preferences = customer - @contact.address = Address.new - @contact.address.street = "123 Lane" - @contact.address.city = "Springfield" - @contact.address.state = "CA" - @contact.address.zip = 11111 - @contact.friends = [Contact.new, Contact.new] - end - - test "should serialize default root" do - @xml = @contact.to_xml - assert_match %r{^}, @xml - assert_match %r{$}, @xml - end - - test "should serialize namespaced root" do - @xml = Admin::Contact.new(@contact.attributes).to_xml - assert_match %r{^}, @xml - assert_match %r{$}, @xml - end - - test "should serialize default root with namespace" do - @xml = @contact.to_xml :namespace => "http://xml.rubyonrails.org/contact" - assert_match %r{^}, @xml - assert_match %r{$}, @xml - end - - test "should serialize custom root" do - @xml = @contact.to_xml :root => 'xml_contact' - assert_match %r{^}, @xml - assert_match %r{$}, @xml - end - - test "should allow undasherized tags" do - @xml = @contact.to_xml :root => 'xml_contact', :dasherize => false - assert_match %r{^}, @xml - assert_match %r{$}, @xml - assert_match %r{ 'xml_contact', :camelize => true - assert_match %r{^}, @xml - assert_match %r{$}, @xml - assert_match %r{ 'xml_contact', :camelize => :lower - assert_match %r{^}, @xml - assert_match %r{$}, @xml - assert_match %r{aaron stack}, @xml - assert_match %r{25}, @xml - assert_no_match %r{}, @xml - end - - test "should allow skipped types" do - @xml = @contact.to_xml :skip_types => true - assert_match %r{25}, @xml - end - - test "should include yielded additions" do - @xml = @contact.to_xml do |xml| - xml.creator "David" - end - assert_match %r{David}, @xml - end - - test "should serialize string" do - assert_match %r{aaron stack}, @contact.to_xml - end - - test "should serialize nil" do - assert_match %r{}, @contact.to_xml(:methods => :pseudonyms) - end - - test "should serialize integer" do - assert_match %r{25}, @contact.to_xml - end - - test "should serialize datetime" do - assert_match %r{2006-08-01T00:00:00Z}, @contact.to_xml - end - - test "should serialize boolean" do - assert_match %r{false}, @contact.to_xml - end - - test "should serialize array" do - assert_match %r{\s*twitter\s*github\s*}, @contact.to_xml(:methods => :social) - end - - test "should serialize hash" do - assert_match %r{\s*github\s*}, @contact.to_xml(:methods => :network) - end - - test "should serialize yaml" do - assert_match %r{--- !ruby/struct:Customer(\s*)\nname: John\n}, @contact.to_xml - end - - test "should call proc on object" do - proc = Proc.new { |options| options[:builder].tag!('nationality', 'unknown') } - xml = @contact.to_xml(:procs => [ proc ]) - assert_match %r{unknown}, xml - end - - test 'should supply serializable to second proc argument' do - proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) } - xml = @contact.to_xml(:procs => [ proc ]) - assert_match %r{kcats noraa}, xml - end - - test "should serialize string correctly when type passed" do - xml = @contact.to_xml :type => 'Contact' - assert_match %r{}, xml - assert_match %r{aaron stack}, xml - end - - test "include option with singular association" do - xml = @contact.to_xml :include => :address, :indent => 0 - assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true)) - end - - test "include option with plural association" do - xml = @contact.to_xml :include => :friends, :indent => 0 - assert_match %r{}, xml - assert_match %r{}, xml - end - - test "multiple includes" do - xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => [ :address, :friends ] - assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true)) - assert_match %r{}, xml - assert_match %r{}, xml - end - - test "include with options" do - xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => { :address => { :only => :city } } - assert xml.include?(%(>
Springfield
)) - end - - test "propagates skip_types option to included associations" do - xml = @contact.to_xml :include => :friends, :indent => 0, :skip_types => true - assert_match %r{}, xml - assert_match %r{}, xml - end -end -- cgit v1.2.3 From 7fcc8c0a1f38c77b12cb6ffe81fb2887e6c60b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 23 Nov 2011 23:45:27 +0000 Subject: Rely solely on active_model_serializer and remove the fancy constant lookup. --- activemodel/lib/active_model/serializable.rb | 12 ++++---- activemodel/lib/active_model/serializer.rb | 45 +++++----------------------- activemodel/test/cases/serializer_test.rb | 37 ++--------------------- 3 files changed, 17 insertions(+), 77 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/serializable.rb b/activemodel/lib/active_model/serializable.rb index 769e934dbe..70e27c5683 100644 --- a/activemodel/lib/active_model/serializable.rb +++ b/activemodel/lib/active_model/serializable.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/string/inflections' module ActiveModel # == Active Model Serializable @@ -72,11 +73,10 @@ module ActiveModel autoload :JSON, "active_model/serializable/json" autoload :XML, "active_model/serializable/xml" - include ActiveModel::Serializer::Scope - module ClassMethods #:nodoc: - def _model_serializer - @_model_serializer ||= ActiveModel::Serializer::Finder.find(self, self) + def active_model_serializer + return @active_model_serializer if defined?(@active_model_serializer) + @active_model_serializer = "#{self.name}Serializer".safe_constantize end end @@ -108,8 +108,8 @@ module ActiveModel end # Returns a model serializer for this object considering its namespace. - def model_serializer - self.class._model_serializer + def active_model_serializer + self.class.active_model_serializer end private diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb index a541a1053d..5478da15c8 100644 --- a/activemodel/lib/active_model/serializer.rb +++ b/activemodel/lib/active_model/serializer.rb @@ -1,7 +1,6 @@ require "active_support/core_ext/class/attribute" require "active_support/core_ext/string/inflections" require "active_support/core_ext/module/anonymous" -require "active_support/core_ext/module/introspection" require "set" module ActiveModel @@ -15,7 +14,7 @@ module ActiveModel def serializable_array @object.map do |item| - if serializer = Serializer::Finder.find(item, scope) + if item.respond_to?(:active_model_serializer) && (serializer = item.active_model_serializer) serializer.new(item, scope) else item @@ -30,41 +29,6 @@ module ActiveModel # Active Model Serializer class Serializer - module Finder - mattr_accessor :constantizer - @@constantizer = ActiveSupport::Inflector - - # Finds a serializer for the given object in the given scope. - # If the object implements a +model_serializer+ method, it does - # not do a scope lookup but uses the model_serializer method instead. - def self.find(object, scope) - if object.respond_to?(:model_serializer) - object.model_serializer - else - scope = scope.class unless scope.respond_to?(:const_defined?) - object = object.class unless object.respond_to?(:name) - serializer = "#{object.name.demodulize}Serializer" - - begin - scope.const_get serializer - rescue NameError => e - raise unless e.message =~ /uninitialized constant ([\w_]+::)*#{serializer}$/ - scope.parents.each do |parent| - return parent.const_get(serializer) if parent.const_defined?(serializer) - end - nil - end - end - end - end - - # Defines the serialization scope. Core extension serializers - # are defined in this module so a scoped lookup is able to find - # core extension serializers. - module Scope - ArraySerializer = ::ActiveModel::ArraySerializer - end - module Associations class Config < Struct.new(:name, :options) def serializer @@ -216,3 +180,10 @@ module ActiveModel end end end + +class Array + # Array uses ActiveModel::ArraySerializer. + def active_model_serializer + ActiveModel::ArraySerializer + end +end \ No newline at end of file diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index e99b3692ec..f6c4282d39 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -19,10 +19,8 @@ class SerializerTest < ActiveModel::TestCase include ActiveModel::Serializable attr_accessor :superuser - attr_writer :model_serializer def initialize(hash={}) - @model_serializer = nil @attributes = hash.merge(:first_name => "Jose", :last_name => "Valim", :password => "oh noes yugive my password") end @@ -33,17 +31,15 @@ class SerializerTest < ActiveModel::TestCase def super_user? @superuser end - - def model_serializer - @model_serializer || super - end end class Post < Model attr_accessor :comments + def active_model_serializer; PostSerializer; end end class Comment < Model + def active_model_serializer; CommentSerializer; end end class UserSerializer < ActiveModel::Serializer @@ -428,38 +424,11 @@ class SerializerTest < ActiveModel::TestCase post.comments = [] array = [model, post, comments] - serializer = ActiveModel::Serializer::Finder.find(array, user).new(array, user) + serializer = array.active_model_serializer.new(array, user) assert_equal([ { :model => "Model" }, { :post => { :body => "Body of new post", :comments => [], :title => "New Post" } }, { :comment => { :title => "Comment1" } } ], serializer.as_json) end - - def test_array_serializer_respects_model_serializer - user = User.new(:first_name => "Jose", :last_name => "Valim") - user.model_serializer = User2Serializer - - array = [user] - serializer = ActiveModel::Serializer::Finder.find(array, user).new(array, {}) - assert_equal([ - { :user2 => { :last_name => "Valim", :first_name => "Jose", :ok => true } }, - ], serializer.as_json) - end - - def test_finder_respects_model_serializer - user = User.new(:first_name => "Jose", :last_name => "Valim") - assert_equal UserSerializer, user.model_serializer - - serializer = ActiveModel::Serializer::Finder.find(user, self).new(user, {}) - assert_equal({ - :user => { :last_name => "Valim", :first_name => "Jose"}, - }, serializer.as_json) - - user.model_serializer = User2Serializer - serializer = ActiveModel::Serializer::Finder.find(user, self).new(user, {}) - assert_equal({ - :user2 => { :last_name => "Valim", :first_name => "Jose", :ok => true }, - }, serializer.as_json) - end end \ No newline at end of file -- cgit v1.2.3 From 28bcda4098985ae5eb2d18c5214d95cf04f3ea93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 23 Nov 2011 23:53:20 +0000 Subject: Rename UserSerializer to DefaultUserSerializer in tests. --- activemodel/test/cases/serializer_test.rb | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'activemodel') diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb index f6c4282d39..2b2e21540f 100644 --- a/activemodel/test/cases/serializer_test.rb +++ b/activemodel/test/cases/serializer_test.rb @@ -44,16 +44,16 @@ class SerializerTest < ActiveModel::TestCase class UserSerializer < ActiveModel::Serializer attributes :first_name, :last_name - end - - class User2Serializer < ActiveModel::Serializer - attributes :first_name, :last_name def serializable_hash attributes.merge(:ok => true).merge(scope) end end + class DefaultUserSerializer < ActiveModel::Serializer + attributes :first_name, :last_name + end + class MyUserSerializer < ActiveModel::Serializer attributes :first_name, :last_name @@ -85,34 +85,34 @@ class SerializerTest < ActiveModel::TestCase def test_attributes user = User.new - user_serializer = UserSerializer.new(user, nil) + user_serializer = DefaultUserSerializer.new(user, {}) hash = user_serializer.as_json assert_equal({ - :user => { :first_name => "Jose", :last_name => "Valim" } + :default_user => { :first_name => "Jose", :last_name => "Valim" } }, hash) end def test_attributes_method user = User.new - user_serializer = User2Serializer.new(user, {}) + user_serializer = UserSerializer.new(user, {}) hash = user_serializer.as_json assert_equal({ - :user2 => { :first_name => "Jose", :last_name => "Valim", :ok => true } + :user => { :first_name => "Jose", :last_name => "Valim", :ok => true } }, hash) end def test_serializer_receives_scope user = User.new - user_serializer = User2Serializer.new(user, {:scope => true}) + user_serializer = UserSerializer.new(user, {:scope => true}) hash = user_serializer.as_json assert_equal({ - :user2 => { + :user => { :first_name => "Jose", :last_name => "Valim", :ok => true, @@ -419,15 +419,13 @@ class SerializerTest < ActiveModel::TestCase def test_array_serializer model = Model.new user = User.new - post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") comments = Comment.new(:title => "Comment1", :id => 1) - post.comments = [] - array = [model, post, comments] - serializer = array.active_model_serializer.new(array, user) + array = [model, user, comments] + serializer = array.active_model_serializer.new(array, {:scope => true}) assert_equal([ { :model => "Model" }, - { :post => { :body => "Body of new post", :comments => [], :title => "New Post" } }, + { :user => { :last_name=>"Valim", :ok=>true, :first_name=>"Jose", :scope => true } }, { :comment => { :title => "Comment1" } } ], serializer.as_json) end -- cgit v1.2.3 From dc39af0a9a998938a969b214554db624dcdd9c85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ku=C5=BAma?= Date: Thu, 24 Nov 2011 15:50:21 +0100 Subject: make ActiveModel::Name fail gracefully with anonymous classes --- activemodel/lib/active_model/naming.rb | 3 +++ activemodel/test/cases/naming_test.rb | 13 +++++++++++++ 2 files changed, 16 insertions(+) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 2566920d63..953d24a3b2 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -12,6 +12,9 @@ module ActiveModel def initialize(klass, namespace = nil, name = nil) name ||= klass.name + + raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if name.blank? + super(name) @unnamespaced = self.sub(/^#{namespace.name}::/, '') if namespace diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index a5ee2d6090..6f9004da7f 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -247,3 +247,16 @@ class NamingHelpersTest < Test::Unit::TestCase ActiveModel::Naming.send(method, *args) end end + +class NameWithAnonymousClassTest < Test::Unit::TestCase + def test_anonymous_class_without_name_argument + assert_raises(ArgumentError) do + model_name = ActiveModel::Name.new(Class.new) + end + end + + def test_anonymous_class_with_name_argument + model_name = ActiveModel::Name.new(Class.new, nil, "Anonymous") + assert_equal "Anonymous", model_name + end +end -- cgit v1.2.3 From 696d01f7f4a8ed787924a41cce6df836cd73c46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 25 Nov 2011 09:49:54 +0000 Subject: Add docs to serializers. Update CHANGELOGs. --- activemodel/CHANGELOG.md | 14 +++++- activemodel/lib/active_model/serializer.rb | 78 +++++++++++++++++++++++++++--- 2 files changed, 84 insertions(+), 8 deletions(-) (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 71a737caff..53934f08e7 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,4 +1,16 @@ -* Added ActiveModel::Errors#added? to check if a specific error has been added *Martin Svalin* +## Rails 3.2.0 (unreleased) ## + +* Add ActiveModel::Serializer that encapsulates an ActiveModel object serialization *José Valim* + +* Renamed (with a deprecation the following constants): + + ActiveModel::Serialization => ActiveModel::Serializable + ActiveModel::Serializers::JSON => ActiveModel::Serializable::JSON + ActiveModel::Serializers::Xml => ActiveModel::Serializable::XML + + *José Valim* + +* Add ActiveModel::Errors#added? to check if a specific error has been added *Martin Svalin* * Add ability to define strict validation(with :strict => true option) that always raises exception when fails *Bogdan Gusiev* diff --git a/activemodel/lib/active_model/serializer.rb b/activemodel/lib/active_model/serializer.rb index 5478da15c8..0e23df2f2b 100644 --- a/activemodel/lib/active_model/serializer.rb +++ b/activemodel/lib/active_model/serializer.rb @@ -5,6 +5,9 @@ require "set" module ActiveModel # Active Model Array Serializer + # + # It serializes an array checking if each element that implements + # the +active_model_serializer+ method passing down the current scope. class ArraySerializer attr_reader :object, :scope @@ -28,15 +31,46 @@ module ActiveModel end # Active Model Serializer + # + # 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 a scope. For example, + # one may do in a controller: + # + # PostSerializer.new(@post, current_user).to_json + # + # The object to be serialized is the +@post+ and the scope is +current_user+. + # + # 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 + # author of the post: + # + # class PostSerializer < ActiveModel::Serializer + # attributes :title, :body + # has_many :comments + # + # private + # + # def attributes + # hash = super + # hash.merge!(:email => post.email) if author? + # hash + # end + # + # def author? + # post.author == scope + # end + # end + # class Serializer - module Associations - class Config < Struct.new(:name, :options) + module Associations #:nodoc: + class Config < Struct.new(:name, :options) #:nodoc: def serializer options[:serializer] end end - class HasMany < Config + class HasMany < Config #:nodoc: def serialize(collection, scope) collection.map do |item| serializer.new(item, scope).serializable_hash @@ -45,7 +79,7 @@ module ActiveModel def serialize_ids(collection, scope) # use named scopes if they are present - #return collection.ids if collection.respond_to?(:ids) + # return collection.ids if collection.respond_to?(:ids) collection.map do |item| item.read_attribute_for_serialization(:id) @@ -53,7 +87,7 @@ module ActiveModel end end - class HasOne < Config + class HasOne < Config #:nodoc: def serialize(object, scope) object && serializer.new(object, scope).serializable_hash end @@ -76,11 +110,12 @@ module ActiveModel class_attribute :_root_embed class << self + # Define attributes to be used in the serialization. def attributes(*attrs) self._attributes += attrs end - def associate(klass, attrs) + def associate(klass, attrs) #:nodoc: options = attrs.extract_options! self._associations += attrs.map do |attr| unless method_defined?(attr) @@ -92,24 +127,43 @@ module ActiveModel end end + # Defines an association in the object should be rendered. + # + # The serializer object should implement the association name + # as a method which should return an array when invoked. If a method + # with the association name does not exist, the association name is + # dispatched to the serialized object. def has_many(*attrs) associate(Associations::HasMany, attrs) end + # Defines an association in the object should be rendered. + # + # The serializer object should implement the association name + # as a method which should return an object when invoked. If a method + # with the association name does not exist, the association name is + # dispatched to the serialized object. def has_one(*attrs) associate(Associations::HasOne, attrs) end + # Define how associations should be embedded. + # + # embed :objects # Embed associations as full objects + # embed :ids # Embed only the association ids + # embed :ids, :include => true # Embed the association ids and include objects in the root + # def embed(type, options={}) self._embed = type self._root_embed = true if options[:include] end + # Defines the root used on serialization. If false, disables the root. def root(name) self._root = name end - def inherited(klass) + def inherited(klass) #:nodoc: return if klass.anonymous? name = klass.name.demodulize.underscore.sub(/_serializer$/, '') @@ -127,6 +181,8 @@ module ActiveModel @object, @scope = object, scope end + # Returns a json representation of the serializable + # object including the root. def as_json(*) if _root hash = { _root => serializable_hash } @@ -137,6 +193,8 @@ module ActiveModel end end + # Returns a hash representation of the serializable + # object without the root. def serializable_hash if _embed == :ids attributes.merge(association_ids) @@ -147,6 +205,8 @@ module ActiveModel end end + # Returns a hash representation of the serializable + # object associations. def associations hash = {} @@ -158,6 +218,8 @@ module ActiveModel hash end + # Returns a hash representation of the serializable + # object associations ids. def association_ids hash = {} @@ -169,6 +231,8 @@ module ActiveModel hash end + # Returns a hash representation of the serializable + # object attributes. def attributes hash = {} -- cgit v1.2.3 From 0a4035b12a6c59253cb60f9e3456513c6a6a9d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Fri, 25 Nov 2011 19:29:39 +0000 Subject: Revert the serializers API as other alternatives are now also under discussion --- activemodel/CHANGELOG.md | 2 - activemodel/lib/active_model.rb | 2 - activemodel/lib/active_model/serializable.rb | 12 - activemodel/test/cases/serializer_test.rb | 432 --------------------------- 4 files changed, 448 deletions(-) delete mode 100644 activemodel/test/cases/serializer_test.rb (limited to 'activemodel') diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 53934f08e7..caea0b86bd 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,7 +1,5 @@ ## Rails 3.2.0 (unreleased) ## -* Add ActiveModel::Serializer that encapsulates an ActiveModel object serialization *José Valim* - * Renamed (with a deprecation the following constants): ActiveModel::Serialization => ActiveModel::Serializable diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 6c4fb44b0f..7ea04344f0 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -29,7 +29,6 @@ require 'active_model/version' module ActiveModel extend ActiveSupport::Autoload - autoload :ArraySerializer, 'active_model/serializer' autoload :AttributeMethods autoload :BlockValidator, 'active_model/validator' autoload :Callbacks @@ -46,7 +45,6 @@ module ActiveModel autoload :SecurePassword autoload :Serializable autoload :Serialization - autoload :Serializer autoload :TestCase autoload :Translation autoload :Validations diff --git a/activemodel/lib/active_model/serializable.rb b/activemodel/lib/active_model/serializable.rb index 70e27c5683..86770a25e4 100644 --- a/activemodel/lib/active_model/serializable.rb +++ b/activemodel/lib/active_model/serializable.rb @@ -73,13 +73,6 @@ module ActiveModel autoload :JSON, "active_model/serializable/json" autoload :XML, "active_model/serializable/xml" - module ClassMethods #:nodoc: - def active_model_serializer - return @active_model_serializer if defined?(@active_model_serializer) - @active_model_serializer = "#{self.name}Serializer".safe_constantize - end - end - def serializable_hash(options = nil) options ||= {} @@ -107,11 +100,6 @@ module ActiveModel hash end - # Returns a model serializer for this object considering its namespace. - def active_model_serializer - self.class.active_model_serializer - end - private # Hook method defining how an attribute value should be retrieved for diff --git a/activemodel/test/cases/serializer_test.rb b/activemodel/test/cases/serializer_test.rb deleted file mode 100644 index 2b2e21540f..0000000000 --- a/activemodel/test/cases/serializer_test.rb +++ /dev/null @@ -1,432 +0,0 @@ -require "cases/helper" - -class SerializerTest < ActiveModel::TestCase - class Model - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - - def as_json(*) - { :model => "Model" } - end - end - - class User - include ActiveModel::Serializable - - attr_accessor :superuser - - def initialize(hash={}) - @attributes = hash.merge(:first_name => "Jose", :last_name => "Valim", :password => "oh noes yugive my password") - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - - def super_user? - @superuser - end - end - - class Post < Model - attr_accessor :comments - def active_model_serializer; PostSerializer; end - end - - class Comment < Model - def active_model_serializer; CommentSerializer; end - end - - class UserSerializer < ActiveModel::Serializer - attributes :first_name, :last_name - - def serializable_hash - attributes.merge(:ok => true).merge(scope) - end - end - - class DefaultUserSerializer < ActiveModel::Serializer - attributes :first_name, :last_name - end - - class MyUserSerializer < ActiveModel::Serializer - attributes :first_name, :last_name - - def serializable_hash - hash = attributes - hash = hash.merge(:super_user => true) if my_user.super_user? - hash - end - end - - class CommentSerializer - def initialize(comment, scope) - @comment, @scope = comment, scope - end - - def serializable_hash - { :title => @comment.read_attribute_for_serialization(:title) } - end - - def as_json - { :comment => serializable_hash } - end - end - - class PostSerializer < ActiveModel::Serializer - attributes :title, :body - has_many :comments, :serializer => CommentSerializer - end - - def test_attributes - user = User.new - user_serializer = DefaultUserSerializer.new(user, {}) - - hash = user_serializer.as_json - - assert_equal({ - :default_user => { :first_name => "Jose", :last_name => "Valim" } - }, hash) - end - - def test_attributes_method - user = User.new - user_serializer = UserSerializer.new(user, {}) - - hash = user_serializer.as_json - - assert_equal({ - :user => { :first_name => "Jose", :last_name => "Valim", :ok => true } - }, hash) - end - - def test_serializer_receives_scope - user = User.new - user_serializer = UserSerializer.new(user, {:scope => true}) - - hash = user_serializer.as_json - - assert_equal({ - :user => { - :first_name => "Jose", - :last_name => "Valim", - :ok => true, - :scope => true - } - }, hash) - end - - def test_pretty_accessors - user = User.new - user.superuser = true - user_serializer = MyUserSerializer.new(user, nil) - - hash = user_serializer.as_json - - assert_equal({ - :my_user => { - :first_name => "Jose", :last_name => "Valim", :super_user => true - } - }, hash) - end - - def test_has_many - user = User.new - - post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") - comments = [Comment.new(:title => "Comment1"), Comment.new(:title => "Comment2")] - post.comments = comments - - post_serializer = PostSerializer.new(post, user) - - assert_equal({ - :post => { - :title => "New Post", - :body => "Body of new post", - :comments => [ - { :title => "Comment1" }, - { :title => "Comment2" } - ] - } - }, post_serializer.as_json) - end - - class Blog < Model - attr_accessor :author - end - - class AuthorSerializer < ActiveModel::Serializer - attributes :first_name, :last_name - end - - class BlogSerializer < ActiveModel::Serializer - has_one :author, :serializer => AuthorSerializer - end - - def test_has_one - user = User.new - blog = Blog.new - blog.author = user - - json = BlogSerializer.new(blog, user).as_json - assert_equal({ - :blog => { - :author => { - :first_name => "Jose", - :last_name => "Valim" - } - } - }, json) - end - - def test_implicit_serializer - author_serializer = Class.new(ActiveModel::Serializer) do - attributes :first_name - end - - blog_serializer = Class.new(ActiveModel::Serializer) do - const_set(:AuthorSerializer, author_serializer) - has_one :author - end - - user = User.new - blog = Blog.new - blog.author = user - - json = blog_serializer.new(blog, user).as_json - assert_equal({ - :author => { - :first_name => "Jose" - } - }, json) - end - - def test_overridden_associations - author_serializer = Class.new(ActiveModel::Serializer) do - attributes :first_name - end - - blog_serializer = Class.new(ActiveModel::Serializer) do - const_set(:PersonSerializer, author_serializer) - - def person - object.author - end - - has_one :person - end - - user = User.new - blog = Blog.new - blog.author = user - - json = blog_serializer.new(blog, user).as_json - assert_equal({ - :person => { - :first_name => "Jose" - } - }, json) - end - - def post_serializer(type) - Class.new(ActiveModel::Serializer) do - attributes :title, :body - has_many :comments, :serializer => CommentSerializer - - if type != :super - define_method :serializable_hash do - post_hash = attributes - post_hash.merge!(send(type)) - post_hash - end - end - end - end - - def test_associations - post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") - comments = [Comment.new(:title => "Comment1"), Comment.new(:title => "Comment2")] - post.comments = comments - - serializer = post_serializer(:associations).new(post, nil) - - assert_equal({ - :title => "New Post", - :body => "Body of new post", - :comments => [ - { :title => "Comment1" }, - { :title => "Comment2" } - ] - }, serializer.as_json) - end - - def test_association_ids - serializer = post_serializer(:association_ids) - - serializer.class_eval do - def as_json(*) - { :post => serializable_hash }.merge(associations) - end - end - - post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") - comments = [Comment.new(:title => "Comment1", :id => 1), Comment.new(:title => "Comment2", :id => 2)] - post.comments = comments - - serializer = serializer.new(post, nil) - - assert_equal({ - :post => { - :title => "New Post", - :body => "Body of new post", - :comments => [1, 2] - }, - :comments => [ - { :title => "Comment1" }, - { :title => "Comment2" } - ] - }, serializer.as_json) - end - - def test_associations_with_nil_association - user = User.new - blog = Blog.new - - json = BlogSerializer.new(blog, user).as_json - assert_equal({ - :blog => { :author => nil } - }, json) - - serializer = Class.new(BlogSerializer) do - root :blog - - def serializable_hash - attributes.merge(association_ids) - end - end - - json = serializer.new(blog, user).as_json - assert_equal({ :blog => { :author => nil } }, json) - end - - def test_custom_root - user = User.new - blog = Blog.new - - serializer = Class.new(BlogSerializer) do - root :my_blog - end - - assert_equal({ :my_blog => { :author => nil } }, serializer.new(blog, user).as_json) - end - - def test_false_root - user = User.new - blog = Blog.new - - serializer = Class.new(BlogSerializer) do - root false - end - - assert_equal({ :author => nil }, serializer.new(blog, user).as_json) - - # test inherited false root - serializer = Class.new(serializer) - assert_equal({ :author => nil }, serializer.new(blog, user).as_json) - end - - def test_embed_ids - serializer = post_serializer(:super) - - serializer.class_eval do - root :post - embed :ids - end - - post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") - comments = [Comment.new(:title => "Comment1", :id => 1), Comment.new(:title => "Comment2", :id => 2)] - post.comments = comments - - serializer = serializer.new(post, nil) - - assert_equal({ - :post => { - :title => "New Post", - :body => "Body of new post", - :comments => [1, 2] - } - }, serializer.as_json) - end - - def test_embed_ids_include_true - serializer = post_serializer(:super) - - serializer.class_eval do - root :post - embed :ids, :include => true - end - - post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") - comments = [Comment.new(:title => "Comment1", :id => 1), Comment.new(:title => "Comment2", :id => 2)] - post.comments = comments - - serializer = serializer.new(post, nil) - - assert_equal({ - :post => { - :title => "New Post", - :body => "Body of new post", - :comments => [1, 2] - }, - :comments => [ - { :title => "Comment1" }, - { :title => "Comment2" } - ] - }, serializer.as_json) - end - - def test_embed_objects - serializer = post_serializer(:super) - - serializer.class_eval do - root :post - embed :objects - end - - post = Post.new(:title => "New Post", :body => "Body of new post", :email => "tenderlove@tenderlove.com") - comments = [Comment.new(:title => "Comment1", :id => 1), Comment.new(:title => "Comment2", :id => 2)] - post.comments = comments - - serializer = serializer.new(post, nil) - - assert_equal({ - :post => { - :title => "New Post", - :body => "Body of new post", - :comments => [ - { :title => "Comment1" }, - { :title => "Comment2" } - ] - } - }, serializer.as_json) - end - - def test_array_serializer - model = Model.new - user = User.new - comments = Comment.new(:title => "Comment1", :id => 1) - - array = [model, user, comments] - serializer = array.active_model_serializer.new(array, {:scope => true}) - assert_equal([ - { :model => "Model" }, - { :user => { :last_name=>"Valim", :ok=>true, :first_name=>"Jose", :scope => true } }, - { :comment => { :title => "Comment1" } } - ], serializer.as_json) - end -end \ No newline at end of file -- cgit v1.2.3 From 448df2d100feccf1d5df9db2b02b732a775d8406 Mon Sep 17 00:00:00 2001 From: Alexey Vakhov Date: Sun, 27 Nov 2011 10:23:40 +0400 Subject: Cosmetic fixes in AM validatations docs --- activemodel/lib/active_model/validations/presence.rb | 6 +++--- activemodel/lib/active_model/validations/with.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'activemodel') diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index 35af7152db..9a643a6f5c 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -25,14 +25,14 @@ module ActiveModel # This is due to the way Object#blank? handles boolean values: false.blank? # => true. # # Configuration options: - # * message - A custom error message (default is: "can't be blank"). + # * :message - A custom error message (default is: "can't be blank"). # * :on - Specifies when this validation is active. Runs in all # validation contexts by default (+nil+), other options are :create # and :update. - # * if - Specifies a method, proc or string to call to determine if the validation should + # * :if - Specifies a method, proc or string to call to determine if the validation should # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). # The method, proc or string should return or evaluate to a true or false value. - # * unless - Specifies a method, proc or string to call to determine if the validation should + # * :unless - Specifies a method, proc or string to call to determine if the validation should # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). # The method, proc or string should return or evaluate to a true or false value. # * :strict - Specifies whether validation should be strict. diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 93a340eb39..72b8562b93 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -56,7 +56,7 @@ module ActiveModel # if the validation should occur (e.g. :if => :allow_validation, # or :if => Proc.new { |user| user.signup_step > 2 }). # The method, proc or string should return or evaluate to a true or false value. - # * unless - Specifies a method, proc or string to call to + # * :unless - Specifies a method, proc or string to call to # determine if the validation should not occur # (e.g. :unless => :skip_validation, or # :unless => Proc.new { |user| user.signup_step <= 2 }). -- cgit v1.2.3