From 5a230e8e66da51dc7c8541eb441b918be72add84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 1 Dec 2011 08:19:14 +0100 Subject: [PATCH] Added tests. --- Rakefile | 9 + lib/active_model_serializers.rb | 8 +- .../rails => generators}/serializer/USAGE | 0 .../serializer/serializer_generator.rb | 6 +- .../serializer/templates/serializer.rb | 0 .../serializer/serializer_generator.rb | 13 - .../serializer/templates/unit_test.rb | 9 - test/generators_test.rb | 67 +++ test/serialization_test.rb | 172 +++++++ test/serializer_test.rb | 432 ++++++++++++++++++ test/test_helper.rb | 24 + 11 files changed, 715 insertions(+), 25 deletions(-) rename lib/{rails/generators/rails => generators}/serializer/USAGE (100%) rename lib/{rails/generators/rails => generators}/serializer/serializer_generator.rb (82%) rename lib/{rails/generators/rails => generators}/serializer/templates/serializer.rb (100%) delete mode 100644 lib/rails/generators/test_unit/serializer/serializer_generator.rb delete mode 100644 lib/rails/generators/test_unit/serializer/templates/unit_test.rb create mode 100644 test/generators_test.rb create mode 100644 test/serialization_test.rb create mode 100644 test/serializer_test.rb create mode 100644 test/test_helper.rb diff --git a/Rakefile b/Rakefile index f57ae68a..8dc5b3aa 100644 --- a/Rakefile +++ b/Rakefile @@ -1,2 +1,11 @@ #!/usr/bin/env rake require "bundler/gem_tasks" +require "rake/testtask" + +desc 'Run tests' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index a6c2cf99..0f1e14b9 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -7,7 +7,13 @@ ActiveModel::Serialization.class_eval do module ClassMethods #:nodoc: def active_model_serializer return @active_model_serializer if defined?(@active_model_serializer) - @active_model_serializer = "#{self.name}Serializer".safe_constantize + + # Use safe constantize when Rails 3.2 is out + begin + @active_model_serializer = "#{self.name}Serializer".constantize + rescue NameError => e + raise unless e.message =~ /uninitialized constant$/ && e.name.to_s == "#{self.name}Serializer" + end end end diff --git a/lib/rails/generators/rails/serializer/USAGE b/lib/generators/serializer/USAGE similarity index 100% rename from lib/rails/generators/rails/serializer/USAGE rename to lib/generators/serializer/USAGE diff --git a/lib/rails/generators/rails/serializer/serializer_generator.rb b/lib/generators/serializer/serializer_generator.rb similarity index 82% rename from lib/rails/generators/rails/serializer/serializer_generator.rb rename to lib/generators/serializer/serializer_generator.rb index 21189062..d1d418ea 100644 --- a/lib/rails/generators/rails/serializer/serializer_generator.rb +++ b/lib/generators/serializer/serializer_generator.rb @@ -1,6 +1,7 @@ module Rails module Generators class SerializerGenerator < NamedBase + source_root File.expand_path("../templates", __FILE__) check_class_collision :suffix => "Serializer" argument :attributes, :type => :array, :default => [], :banner => "field:type field:type" @@ -26,8 +27,9 @@ module Rails def parent_class_name if options[:parent] options[:parent] - elsif (n = Rails::Generators.namespace) && n.const_defined?(:ApplicationSerializer) - "ApplicationSerializer" + # Only works on 3.2 + # elsif (n = Rails::Generators.namespace) && n.const_defined?(:ApplicationSerializer) + # "ApplicationSerializer" elsif Object.const_defined?(:ApplicationSerializer) "ApplicationSerializer" else diff --git a/lib/rails/generators/rails/serializer/templates/serializer.rb b/lib/generators/serializer/templates/serializer.rb similarity index 100% rename from lib/rails/generators/rails/serializer/templates/serializer.rb rename to lib/generators/serializer/templates/serializer.rb diff --git a/lib/rails/generators/test_unit/serializer/serializer_generator.rb b/lib/rails/generators/test_unit/serializer/serializer_generator.rb deleted file mode 100644 index 533c032c..00000000 --- a/lib/rails/generators/test_unit/serializer/serializer_generator.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'rails/generators/test_unit' - -module TestUnit - module Generators - class SerializerGenerator < Base - check_class_collision :suffix => "SerializerTest" - - def create_test_files - template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_serializer_test.rb") - end - end - end -end diff --git a/lib/rails/generators/test_unit/serializer/templates/unit_test.rb b/lib/rails/generators/test_unit/serializer/templates/unit_test.rb deleted file mode 100644 index 0b1bbdca..00000000 --- a/lib/rails/generators/test_unit/serializer/templates/unit_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'test_helper' - -<% module_namespacing do -%> -class <%= class_name %>SerializerTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end -<% end -%> diff --git a/test/generators_test.rb b/test/generators_test.rb new file mode 100644 index 00000000..0e388633 --- /dev/null +++ b/test/generators_test.rb @@ -0,0 +1,67 @@ +require 'rails' + +class Foo < Rails::Application +end + +Rails.application.load_generators + +require 'generators/serializer/serializer_generator' + +class SerializerGeneratorTest < Rails::Generators::TestCase + destination File.expand_path("../tmp", __FILE__) + setup :prepare_destination + + tests Rails::Generators::SerializerGenerator + arguments %w(account name:string description:text business:references) + + def test_generates_a_serializer + run_generator + assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < ActiveModel::Serializer/ + end + + def test_generates_a_namespaced_serializer + run_generator ["admin/account"] + assert_file "app/serializers/admin/account_serializer.rb", /class Admin::AccountSerializer < ActiveModel::Serializer/ + end + + def test_uses_application_serializer_if_one_exists + Object.const_set(:ApplicationSerializer, Class.new) + run_generator + assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < ApplicationSerializer/ + ensure + Object.send :remove_const, :ApplicationSerializer + end + + # def test_uses_namespace_application_serializer_if_one_exists + # Object.const_set(:SerializerNamespace, Module.new) + # SerializerNamespace.const_set(:ApplicationSerializer, Class.new) + # Rails::Generators.namespace = SerializerNamespace + # run_generator + # assert_file "app/serializers/serializer_namespace/account_serializer.rb", + # /module SerializerNamespace\n class AccountSerializer < ApplicationSerializer/ + # ensure + # Object.send :remove_const, :SerializerNamespace + # Rails::Generators.namespace = nil + # end + + def test_uses_given_parent + Object.const_set(:ApplicationSerializer, Class.new) + run_generator ["Account", "--parent=MySerializer"] + assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < MySerializer/ + ensure + Object.send :remove_const, :ApplicationSerializer + end + + def test_generates_attributes_and_associations + run_generator + assert_file "app/serializers/account_serializer.rb" do |serializer| + assert_match(/^ attributes :name, :description$/, serializer) + assert_match(/^ has_one :business$/, serializer) + end + end + + def test_with_no_attributes_does_not_add_extra_space + run_generator ["account"] + assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < ActiveModel::Serializer\nend/ + end +end diff --git a/test/serialization_test.rb b/test/serialization_test.rb new file mode 100644 index 00000000..627baa8c --- /dev/null +++ b/test/serialization_test.rb @@ -0,0 +1,172 @@ +require 'test_helper' +require 'pathname' + +class RenderJsonTest < ActionController::TestCase + class JsonRenderable + def as_json(options={}) + hash = { :a => :b, :c => :d, :e => :f } + hash.except!(*options[:except]) if options[:except] + hash + end + + def to_json(options = {}) + super :except => [:c, :e] + end + end + + class JsonSerializer + def initialize(object, scope) + @object, @scope = object, scope + end + + def as_json(*) + { :object => @object.as_json, :scope => @scope.as_json } + end + end + + class JsonSerializable + def initialize(skip=false) + @skip = skip + end + + def active_model_serializer + JsonSerializer unless @skip + end + + def as_json(*) + { :serializable_object => true } + end + end + + class TestController < ActionController::Base + protect_from_forgery + + serialization_scope :current_user + attr_reader :current_user + + def self.controller_path + 'test' + end + + def render_json_nil + render :json => nil + end + + def render_json_render_to_string + render :text => render_to_string(:json => '[]') + end + + def render_json_hello_world + render :json => ActiveSupport::JSON.encode(:hello => 'world') + end + + def render_json_hello_world_with_status + render :json => ActiveSupport::JSON.encode(:hello => 'world'), :status => 401 + end + + def render_json_hello_world_with_callback + render :json => ActiveSupport::JSON.encode(:hello => 'world'), :callback => 'alert' + end + + def render_json_with_custom_content_type + render :json => ActiveSupport::JSON.encode(:hello => 'world'), :content_type => 'text/javascript' + end + + def render_symbol_json + render :json => ActiveSupport::JSON.encode(:hello => 'world') + end + + def render_json_with_extra_options + render :json => JsonRenderable.new, :except => [:c, :e] + end + + def render_json_without_options + render :json => JsonRenderable.new + end + + def render_json_with_serializer + @current_user = Struct.new(:as_json).new(:current_user => true) + render :json => JsonSerializable.new + end + + def render_json_with_serializer_api_but_without_serializer + @current_user = Struct.new(:as_json).new(:current_user => true) + render :json => JsonSerializable.new(true) + end + end + + tests TestController + + def setup + # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get + # a more accurate simulation of what happens in "real life". + super + @controller.logger = Logger.new(nil) + + @request.host = "www.nextangle.com" + end + + def test_render_json_nil + get :render_json_nil + assert_equal 'null', @response.body + assert_equal 'application/json', @response.content_type + end + + def test_render_json_render_to_string + get :render_json_render_to_string + assert_equal '[]', @response.body + end + + + def test_render_json + get :render_json_hello_world + assert_equal '{"hello":"world"}', @response.body + assert_equal 'application/json', @response.content_type + end + + def test_render_json_with_status + get :render_json_hello_world_with_status + assert_equal '{"hello":"world"}', @response.body + assert_equal 401, @response.status + end + + def test_render_json_with_callback + get :render_json_hello_world_with_callback + assert_equal 'alert({"hello":"world"})', @response.body + assert_equal 'application/json', @response.content_type + end + + def test_render_json_with_custom_content_type + get :render_json_with_custom_content_type + assert_equal '{"hello":"world"}', @response.body + assert_equal 'text/javascript', @response.content_type + end + + def test_render_symbol_json + get :render_symbol_json + assert_equal '{"hello":"world"}', @response.body + assert_equal 'application/json', @response.content_type + end + + def test_render_json_forwards_extra_options + get :render_json_with_extra_options + assert_equal '{"a":"b"}', @response.body + assert_equal 'application/json', @response.content_type + end + + def test_render_json_calls_to_json_from_object + get :render_json_without_options + assert_equal '{"a":"b"}', @response.body + end + + def test_render_json_with_serializer + get :render_json_with_serializer + assert_match '"scope":{"current_user":true}', @response.body + assert_match '"object":{"serializable_object":true}', @response.body + end + + def test_render_json_with_serializer_api_but_without_serializer + get :render_json_with_serializer_api_but_without_serializer + assert_match '{"serializable_object":true}', @response.body + end +end \ No newline at end of file diff --git a/test/serializer_test.rb b/test/serializer_test.rb new file mode 100644 index 00000000..2c390fe3 --- /dev/null +++ b/test/serializer_test.rb @@ -0,0 +1,432 @@ +require "test_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::Serialization + + 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 diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 00000000..009aac0b --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,24 @@ +require "rubygems" +require "bundler" + +Bundler.setup + +require "active_model_serializers" +require "active_support/json" +require "test/unit" + +module TestHelper + Routes = ActionDispatch::Routing::RouteSet.new + Routes.draw do + match ':controller(/:action(/:id))' + match ':controller(/:action)' + end + + ActionController::Base.send :include, Routes.url_helpers +end + +ActiveSupport::TestCase.class_eval do + setup do + @routes = ::TestHelper::Routes + end +end \ No newline at end of file