diff --git a/.gitignore b/.gitignore index f61f58b8..9fab38c6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ .bundle .config .yardoc -Gemfile.lock +*.lock InstalledFiles _yardoc coverage diff --git a/.travis.yml b/.travis.yml index a001825b..a044659d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,10 +6,8 @@ rvm: - rbx-19mode gemfile: - Gemfile + - Gemfile.rails3 - Gemfile.edge -matrix: - allow_failures: - - gemfile: Gemfile.edge notifications: email: false campfire: diff --git a/CHANGELOG.md b/CHANGELOG.md index b4c2913d..ab66c950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,46 +1,20 @@ -# UNRELEASED +# VERSION 0.9.0.pre -* ActiveModel::Serializable was created it has the shared code between - AM::Serializer and AM::ArraySerializer. Basically enable objects to be - serializable by implementing an options method to handle the options - of the serialization and a serialize method that returns an object to - be converted to json by the module. This also removes duplicate code. - https://github.com/rails-api/active_model_serializers/commit/6c6bc8872d3b0f040a200854fa5530a775824dbf +* The following methods were removed + - Model#active\_model\_serializer + - Serializer#include! + - Serializer#include? + - Serializer#attr\_disabled= + - Serializer#cache + - Serializer#perform\_caching + - Serializer#schema (needs more discussion) + - Serializer#attribute + - Serializer#include\_#{name}? (filter method added) + - Serializer#attributes (took a hash) -* ActiveModel::Serializer::Caching module was created it enables - Serializers to be able to cache to\_json and serialize calls. This - also helps removing duplicate code. - https://github.com/rails-api/active_model_serializers/commit/3e27110df78696ac48cafd1568f72216f348a188 - -* We got rid of the Association.refine method which generated - subclasses. - https://github.com/rails-api/active_model_serializers/commit/24923722d4f215c7cfcdf553fd16582e28e3801b - -* Associations doesn't know anymore about the source serializer. - That didn't make any sense. - https://github.com/rails-api/active_model_serializers/commit/2252e8fe6dbf45660c6a35f35e2423792f2c3abf - https://github.com/rails-api/active_model_serializers/commit/87eadd09b9a988bc1d9b30d9a501ef7e3fc6bb87 - https://github.com/rails-api/active_model_serializers/commit/79a6e13e8f7fae2eb4f48e83a9633e74beb6739e - -* Passing options[:hash] is not public API of include!. That was - removed. - https://github.com/rails-api/active_model_serializers/commit/5cbf9317051002a32c90c3f995b8b2f126f70d0c - -* ActiveModel::Serializer::Associations::Config is now - ActiveModel::Serializer::Association but it's an internal - thing so shouldn't bother. - ActiveModel::Serializer::Associations::Has\* are now - ActiveModel::Serializer::Association::Has\* and inherit from - ActiveModel::Serializer::Association - https://github.com/rails-api/active_model_serializers/commit/f5de334ddf1f3b9764d914a717311532021785d2 - https://github.com/rails-api/active_model_serializers/commit/3dd422d99e8c57f113880da34f6abe583c4dadf9 - -* serialize\_ids call methods on the corresponding serializer if they - are defined, instead of talking directly with the serialized object. - Serializers are decorators so we shouldn't talk directly with - serialized objects. - -* Array items are not wrapped anymore in root element. +* The following things were added + - Serializer#filter method + - SETTINGS object * Remove support for ruby 1.8 versions. @@ -107,7 +81,7 @@ * Allow serialization_scope to be disabled with serialization_scope nil * Array serializer should support pure ruby objects besides serializers -# VERSION 0.5.0 (May 16, 2012) +# VERSION 0.5.0 * First tagged version * Changes generators to always generate an ApplicationSerializer diff --git a/Gemfile b/Gemfile index 79ab2b93..00cd24d9 100644 --- a/Gemfile +++ b/Gemfile @@ -3,4 +3,8 @@ source 'https://rubygems.org' # Specify gem dependencies in active_model_serializers.gemspec gemspec -gem "coveralls", require: false +gem 'sqlite3' +gem 'coveralls', :require => false +gem 'simplecov', :require => false + +gem 'rails', "~> 4.0.0" diff --git a/Gemfile.edge b/Gemfile.edge index d4e1c028..281edceb 100644 --- a/Gemfile.edge +++ b/Gemfile.edge @@ -1,9 +1,13 @@ -source 'http://rubygems.org' +source 'https://rubygems.org' gemspec +gem 'sqlite3' +gem 'coveralls', :require => false +gem 'simplecov', :require => false + +# Use Rails master gem 'rails', github: 'rails/rails' # Current dependencies of edge rails -gem 'journey', github: 'rails/journey' -gem 'activerecord-deprecated_finders' , github: 'rails/activerecord-deprecated_finders' +gem 'arel', github: 'rails/arel' diff --git a/Gemfile.rails3 b/Gemfile.rails3 new file mode 100644 index 00000000..5f6012a4 --- /dev/null +++ b/Gemfile.rails3 @@ -0,0 +1,11 @@ +source 'https://rubygems.org' + +# Specify gem dependencies in active_model_serializers.gemspec +gemspec + +gem 'sqlite3' +gem 'coveralls', :require => false +gem 'simplecov', :require => false + +gem 'minitest', '~> 4.0' +gem 'rails', '~> 3.2' diff --git a/MIT-LICENSE.txt b/MIT-LICENSE similarity index 100% rename from MIT-LICENSE.txt rename to MIT-LICENSE diff --git a/README.md b/README.md index bb7d8871..8e748a3f 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,9 @@ # Purpose The purpose of `ActiveModel::Serializers` is to provide an object to -encapsulate serialization of `ActiveModel` objects, including `ActiveRecord` -objects. +encapsulate serialization of objects which respond to +read\_attribute\_for\_serialization like ActiveModel ones and including +`ActiveRecord` objects. Serializers know about both a model and the `current_user`, so you can customize serialization based upon whether a user is authorized to see the @@ -55,17 +56,11 @@ the serializer generator: $ rails g serializer post ``` -### Support for POROs and other ORMs. +### Support for POROs -Currently `ActiveModel::Serializers` adds serialization support to all models -that descend from `ActiveRecord` or include `Mongoid::Document`. If you are -using another ORM, or if you are using objects that are `ActiveModel` -compliant but do not descend from `ActiveRecord` or include -`Mongoid::Document`, you must add an include statement for -`ActiveModel::SerializerSupport` to make models serializable. If you -also want to make collections serializable, you should include -`ActiveModel::ArraySerializerSupport` into your ORM's -relation/criteria class. +Currently `ActiveModel::Serializers` expects objects to implement +read\_attribute\_for\_serialization. That's all you need to do to have +your POROs supported. # ActiveModel::Serializer @@ -92,19 +87,8 @@ This also works with `respond_with`, which uses `to_json` under the hood. Also note that any options passed to `render :json` will be passed to your serializer and available as `@options` inside. -To specify a custom serializer for an object, there are 2 options: - -#### 1. Specify the serializer in your model: - -```ruby -class Post < ActiveRecord::Base - def active_model_serializer - FancyPostSerializer - end -end -``` - -#### 2. Specify the serializer when you render the object: +To specify a custom serializer for an object, you can specify the +serializer when you render the object: ```ruby render json: @post, serializer: FancyPostSerializer @@ -267,49 +251,44 @@ class VersionSerializer < ActiveModel::Serializer end ``` -You can also access the `current_user` method, which provides an +You can also access the `scope` method, which provides an authorization context to your serializer. By default, the context is the current user of your application, but this [can be customized](#customizing-scope). -Serializers will check for the presence of a method named -`include_[ATTRIBUTE]?` to determine whether a particular attribute should be -included in the output. This is typically used to customize output -based on `current_user`. For example: +Serializers provides a method named `filter` used to determine what +attributes and associations should be included in the output. This is +typically used to customize output based on `current_user`. For example: ```ruby class PostSerializer < ActiveModel::Serializer attributes :id, :title, :body, :author - def include_author? - current_user.admin? + def filter(keys) + if scope.admin? + keys + else + keys - [:author] + end end end ``` -The type of a computed attribute (like :full_name above) is not easily -calculated without some sophisticated static code analysis. To specify the -type of a computed attribute: - -```ruby -class PersonSerializer < ActiveModel::Serializer - attributes :first_name, :last_name, {full_name: :string} - - def full_name - "#{object.first_name} #{object.last_name}" - end -end -``` +And it's also safe to mutate keys argument by doing keys.delete(:author) +in case you want to avoid creating two extra arrays. If you would like the key in the outputted JSON to be different from its name -in ActiveRecord, you can use the `:key` option to customize it: +in ActiveRecord, you can declare the attribute with the different name +and redefine that method: ```ruby class PostSerializer < ActiveModel::Serializer - attributes :id, :body + # look up subject on the model, but use title in the JSON + def title + object.subject + end - # look up :subject on the model, but use +title+ in the JSON - attribute :subject, key: :title + attributes :id, :body, :title has_many :comments end ``` @@ -360,7 +339,7 @@ class PersonSerializer < ActiveModel::Serializer def attributes hash = super - if current_user.admin? + if scope.admin? hash["ssn"] = object.ssn hash["secret"] = object.mothers_maiden_name end @@ -405,23 +384,23 @@ class PostSerializer < ActiveModel::Serializer end ``` -Also, as with attributes, serializers will check for the presence -of a method named `include_[ASSOCIATION]?` to determine whether a particular association -should be included in the output. For example: +Also, as with attributes, serializers will execute a filter method to +determine which associations should be included in the output. For +example: ```ruby class PostSerializer < ActiveModel::Serializer attributes :id, :title, :body has_many :comments - def include_comments? - !object.comments_disabled? + def filter(keys) + keys.delete :comments if object.comments_disabled? + keys end end ``` -If you would like lower-level control of association serialization, you can -override `include_associations!` to specify which associations should be included: +Or ... ```ruby class PostSerializer < ActiveModel::Serializer @@ -429,9 +408,10 @@ class PostSerializer < ActiveModel::Serializer has_one :author has_many :comments - def include_associations! - include! :author if current_user.admin? - include! :comments unless object.comments_disabled? + def filter(keys) + keys.delete :author unless current_user.admin? + keys.delete :comments if object.comments_disabled? + keys end end ``` @@ -445,6 +425,9 @@ You may also use the `:serializer` option to specify a custom serializer class a Serializers are only concerned with multiplicity, and not ownership. `belongs_to` ActiveRecord associations can be included using `has_one` in your serializer. +NOTE: polymorphic was removed because was only supported for has\_one +associations and is in the TODO list of the project. + ## Embedding Associations By default, associations will be embedded inside the serialized object. So if @@ -635,7 +618,7 @@ class ApplicationController < ActionController::Base end ``` -The above example will also change the scope name from `current_user` to +The above example will also change the scope from `current_user` to `current_admin`. Please note that, until now, `serialization_scope` doesn't accept a second @@ -678,6 +661,10 @@ that query, only the `show` action will. ## Caching +NOTE: This functionality was removed from AMS and it's in the TODO list. +We need to re-think and re-design the caching strategy for the next +version of AMS. + To cache a serializer, call `cached` and define a `cache_key` method: ```ruby diff --git a/Rakefile b/Rakefile index 8c5bd75a..0a1d1f00 100644 --- a/Rakefile +++ b/Rakefile @@ -10,9 +10,4 @@ Rake::TestTask.new(:test) do |t| t.verbose = true end -desc 'Benchmark' -task :bench do - load 'bench/perf.rb' -end - -task default: :test +task :default => :test diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index 9b551fad..4592f6a7 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -4,15 +4,15 @@ $:.unshift File.expand_path("../lib", __FILE__) require "active_model/serializer/version" Gem::Specification.new do |gem| - gem.authors = ["José Valim", "Yehuda Katz"] - gem.email = ["jose.valim@gmail.com", "wycats@gmail.com"] + gem.authors = ["José Valim", "Yehuda Katz", "Santiago Pastorino"] + gem.email = ["jose.valim@gmail.com", "wycats@gmail.com", "santiago@wyeworks.com"] gem.description = %q{Making it easy to serialize models for client-side use} gem.summary = %q{Bringing consistency and object orientation to model serialization. Works great for client-side MVC frameworks!} gem.homepage = "https://github.com/rails-api/active_model_serializers" - gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } - gem.files = `git ls-files`.split("\n") - gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + gem.files = Dir['README.md', 'CHANGELOG.md', 'CONTRIBUTING.md', 'DESIGN.textile', 'MIT-LICENSE', 'lib/**/*', 'test/**/*'] + gem.test_files = Dir['test/**/*'] + gem.name = "active_model_serializers" gem.require_paths = ["lib"] gem.version = ActiveModel::Serializer::VERSION @@ -20,9 +20,5 @@ Gem::Specification.new do |gem| gem.required_ruby_version = ">= 1.9.3" gem.add_dependency "activemodel", ">= 3.2" - gem.add_development_dependency "rails", ">= 3.2" - gem.add_development_dependency "pry" - gem.add_development_dependency "simplecov" - gem.add_development_dependency "coveralls" end diff --git a/bench/perf.rb b/bench/perf.rb deleted file mode 100644 index ea668d56..00000000 --- a/bench/perf.rb +++ /dev/null @@ -1,43 +0,0 @@ -require "rubygems" -require "bundler/setup" -require "active_model_serializers" -require "active_support/json" -require "benchmark" - -class User < Struct.new(:id,:name,:age,:about) - include ActiveModel::SerializerSupport - - def fast_hash - h = { - id: read_attribute_for_serialization(:id), - name: read_attribute_for_serialization(:name), - about: read_attribute_for_serialization(:about) - } - h[:age] = read_attribute_for_serialization(:age) if age > 18 - h - end -end - -class UserSerializer < ActiveModel::Serializer - attributes :id, :name, :age, :about - - def include_age? - object.age > 18 - end -end - - - -u = User.new(1, "sam", 10, "about") -s = UserSerializer.new(u) - -n = 100000 - -Benchmark.bmbm {|x| - x.report("init") { n.times { UserSerializer.new(u) } } - x.report("fast_hash") { n.times { u.fast_hash } } - x.report("attributes") { n.times { UserSerializer.new(u).attributes } } - x.report("serializable_hash") { n.times { UserSerializer.new(u).serializable_hash } } -} - - diff --git a/cruft.md b/cruft.md deleted file mode 100644 index 22cbf7d3..00000000 --- a/cruft.md +++ /dev/null @@ -1,19 +0,0 @@ -As of Ruby 1.9.3, it is impossible to dynamically generate a Symbol -through interpolation without generating garbage. Theoretically, Ruby -should be able to take care of this by building up the String in C and -interning the C String. - -Because of this, we avoid generating dynamic Symbols at runtime. For -example, instead of generating the instrumentation event dynamically, we -have a constant with a Hash of events: - -```ruby -INSTRUMENT = { - serialize: :"serialize.serializer", - associations: :"associations.serializer" -} -``` - -If Ruby ever fixes this issue and avoids generating garbage with dynamic -symbols, this code can be removed. - diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index 6a67275c..b2c605f5 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/class/attribute' + module ActionController # Action Controller Serialization # @@ -32,27 +34,46 @@ module ActionController self._serialization_scope = :current_user end - def serialization_scope - send(_serialization_scope) if _serialization_scope && respond_to?(_serialization_scope, true) - end - - def default_serializer_options - end - - def _render_option_json(resource, options) - json = ActiveModel::Serializer.build_json(self, resource, options) - - if json - super(json, options) - else - super - end - end - module ClassMethods def serialization_scope(scope) self._serialization_scope = scope end end + + def _render_option_json(resource, options) + serializer = build_json_serializer(resource, options) + + if serializer + super(serializer, options) + else + super + end + end + + private + + def default_serializer_options + {} + end + + def serialization_scope + _serialization_scope = self.class._serialization_scope + send(_serialization_scope) if _serialization_scope && respond_to?(_serialization_scope, true) + end + + def build_json_serializer(resource, options) + options = default_serializer_options.merge(options || {}) + + serializer = + options.delete(:serializer) || + ActiveModel::Serializer.serializer_for(resource) + + return unless serializer + + options[:scope] = serialization_scope unless options.has_key?(:scope) + options[:resource_name] = self.controller_name if resource.respond_to?(:to_ary) + + serializer.new(resource, options) + end end end diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index e752c812..95bd449f 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -1,65 +1,35 @@ +require 'active_model/default_serializer' require 'active_model/serializable' -require 'active_model/serializer/caching' -require "active_support/core_ext/class/attribute" -require 'active_support/dependencies' -require 'active_support/descendants_tracker' +require 'active_model/serializer' module ActiveModel - # Active Model Array Serializer - # - # Serializes an Array, checking if each element implements - # the +active_model_serializer+ method. - # - # To disable serialization of root elements: - # - # ActiveModel::ArraySerializer.root = false - # class ArraySerializer - extend ActiveSupport::DescendantsTracker - - include ActiveModel::Serializable - include ActiveModel::Serializer::Caching - - attr_reader :object, :options - - class_attribute :root - - class_attribute :cache - class_attribute :perform_caching + include Serializable class << self - # set perform caching like root - def cached(value = true) - self.perform_caching = value - end + attr_accessor :_root + alias root _root= + alias root= _root= end def initialize(object, options={}) - @object = object - @options = options - end - - def serialize_object - serializable_array + @object = object + @root = options[:root] + @root = self.class._root if @root.nil? + @root = options[:resource_name] if @root.nil? + @meta_key = options[:meta_key] || :meta + @meta = options[@meta_key] + @each_serializer = options[:each_serializer] + @options = options.merge(root: nil) end + attr_accessor :object, :root, :meta_key, :meta def serializable_array - object.map do |item| - if options.has_key? :each_serializer - serializer = options[:each_serializer] - elsif item.respond_to?(:active_model_serializer) - serializer = item.active_model_serializer - end - serializer ||= DefaultSerializer - - serializable = serializer.new(item, options.merge(root: nil)) - - if serializable.respond_to?(:serializable_hash) - serializable.serializable_hash - else - serializable.as_json - end + @object.map do |item| + serializer = @each_serializer || Serializer.serializer_for(item) || DefaultSerializer + serializer.new(item, @options).serializable_object end end + alias serializable_object serializable_array end end diff --git a/lib/active_model/default_serializer.rb b/lib/active_model/default_serializer.rb new file mode 100644 index 00000000..7b5e52ce --- /dev/null +++ b/lib/active_model/default_serializer.rb @@ -0,0 +1,17 @@ +module ActiveModel + # DefaultSerializer + # + # Provides a constant interface for all items + class DefaultSerializer + attr_reader :object + + def initialize(object, options=nil) + @object = object + end + + def serializable_hash(*) + @object.as_json + end + alias serializable_object serializable_hash + end +end diff --git a/lib/active_model/serializable.rb b/lib/active_model/serializable.rb index 7122ae20..fa4b7d83 100644 --- a/lib/active_model/serializable.rb +++ b/lib/active_model/serializable.rb @@ -1,49 +1,21 @@ -require 'active_support/core_ext/object/to_json' - module ActiveModel - # Enable classes to Classes including this module to serialize themselves by implementing a serialize method and an options method. - # - # Example: - # - # require 'active_model_serializers' - # - # class MySerializer - # include ActiveModel::Serializable - # - # def initialize - # @options = {} - # end - # - # attr_reader :options - # - # def serialize - # { a: 1 } - # end - # end - # - # puts MySerializer.new.to_json module Serializable - def as_json(args={}) - if root = args[:root] || options[:root] - options[:hash] = hash = {} - options[:unique_values] = {} - - hash.merge!(root => serialize) - include_meta hash + def as_json(options={}) + if root = options[:root] || self.root + hash = { root => serializable_object } + hash.merge!(serializable_data) hash else - serialize + serializable_object end end - private - - def include_meta(hash) - hash[meta_key] = options[:meta] if options.has_key?(:meta) - end - - def meta_key - options[:meta_key].try(:to_sym) || :meta + def serializable_data + if respond_to?(:meta) && meta + { meta_key => meta } + else + {} + end end end end diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index e2a6228b..e7f74ced 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -1,124 +1,81 @@ +require 'active_model/array_serializer' require 'active_model/serializable' -require 'active_model/serializer/caching' -require "active_support/core_ext/class/attribute" -require "active_support/core_ext/module/anonymous" -require 'active_support/dependencies' -require 'active_support/descendants_tracker' +require 'active_model/serializer/associations' +require 'active_model/serializer/settings' module ActiveModel - # 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 two objects as arguments, a resource and options. For example, - # one may do in a controller: - # - # PostSerializer.new(@post, scope: current_user).to_json - # - # The object to be serialized is the +@post+ and the current user is passed - # in for authorization purposes. - # - # We use the scope to check if a given attribute should be serialized or not. - # For example, some attributes may 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 - extend ActiveSupport::DescendantsTracker - - include ActiveModel::Serializable - include ActiveModel::Serializer::Caching - - INCLUDE_METHODS = {} - INSTRUMENT = { serialize: :"serialize.serializer", associations: :"associations.serializer" } - - class IncludeError < StandardError - attr_reader :source, :association - - def initialize(source, association) - @source, @association = source, association - end - - def to_s - "Cannot serialize #{association} when #{source} does not have a root!" - end - end - - class_attribute :_attributes - self._attributes = {} - - class_attribute :_associations - self._associations = {} - - class_attribute :_root - class_attribute :_embed - self._embed = :objects - class_attribute :_root_embed - - class_attribute :cache - class_attribute :perform_caching + include Serializable class << self - def cached(value = true) - self.perform_caching = value + def inherited(base) + base._attributes = [] + base._associations = {} end - # Define attributes to be used in the serialization. - def attributes(*attrs) + def setup + yield SETTINGS + end - self._attributes = _attributes.dup + def embed(type, options={}) + SETTINGS[:embed] = type + SETTINGS[:include] = true if options[:include] + end + + if RUBY_VERSION >= '2.0' + def serializer_for(resource) + if resource.respond_to?(:to_ary) + ArraySerializer + else + begin + Object.const_get "#{resource.class.name}Serializer" + rescue NameError + nil + end + end + end + else + def serializer_for(resource) + if resource.respond_to?(:to_ary) + ArraySerializer + else + "#{resource.class.name}Serializer".safe_constantize + end + end + end + + attr_accessor :_root, :_attributes, :_associations + alias root _root= + alias root= _root= + + def root_name + name.demodulize.underscore.sub(/_serializer$/, '') if name + end + + def attributes(*attrs) + @_attributes.concat attrs attrs.each do |attr| - if Hash === attr - attr.each {|attr_real, key| attribute(attr_real, key: key) } - else - attribute attr + unless method_defined?(attr) + define_method attr do + object.read_attribute_for_serialization(attr) + end end end end - def attribute(attr, options={}) - self._attributes = _attributes.merge(attr.is_a?(Hash) ? attr : {attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym}) - - attr = attr.keys[0] if attr.is_a? Hash - - unless method_defined?(attr) - define_method attr do - object.read_attribute_for_serialization(attr.to_sym) - end - end - - define_include_method attr - - # protect inheritance chains and open classes - # if a serializer inherits from another OR - # attributes are added later in a classes lifecycle - # poison the cache - define_method :_fast_attributes do - raise NameError - end - + def has_one(*attrs) + associate(Association::HasOne, *attrs) end - def associate(klass, attrs) #:nodoc: + def has_many(*attrs) + associate(Association::HasMany, *attrs) + end + + private + + def associate(klass, *attrs) options = attrs.extract_options! - self._associations = _associations.dup attrs.each do |attr| unless method_defined?(attr) @@ -127,349 +84,91 @@ module ActiveModel end end - define_include_method attr - - self._associations[attr] = [klass, options] + @_associations[attr] = klass.new(attr, options) end end - - def define_include_method(name) - method = "include_#{name}?".to_sym - - INCLUDE_METHODS[name] = method - - unless method_defined?(method) - define_method method do - true - end - 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(Association::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(Association::HasOne, attrs) - end - - # Return a schema hash for the current serializer. This information - # can be used to generate clients for the serialized output. - # - # The schema hash has two keys: +attributes+ and +associations+. - # - # The +attributes+ hash looks like this: - # - # { name: :string, age: :integer } - # - # The +associations+ hash looks like this: - # { posts: { has_many: :posts } } - # - # If :key is used: - # - # class PostsSerializer < ActiveModel::Serializer - # has_many :posts, key: :my_posts - # end - # - # the hash looks like this: - # - # { my_posts: { has_many: :posts } - # - # This information is extracted from the serializer's model class, - # which is provided by +SerializerClass.model_class+. - # - # The schema method uses the +columns_hash+ and +reflect_on_association+ - # methods, provided by default by ActiveRecord. You can implement these - # methods on your custom models if you want the serializer's schema method - # to work. - # - # TODO: This is currently coupled to Active Record. We need to - # figure out a way to decouple those two. - def schema - klass = model_class - columns = klass.columns_hash - - attrs = {} - _attributes.each do |name, key| - if column = columns[name.to_s] - attrs[key] = column.type - else - # Computed attribute (method on serializer or model). We cannot - # infer the type, so we put nil, unless specified in the attribute declaration - if name != key - attrs[name] = key - else - attrs[key] = nil - end - end - end - - associations = {} - _associations.each do |attr, (association_class, options)| - association = association_class.new(attr, options) - - if model_association = klass.reflect_on_association(association.name) - # Real association. - associations[association.key] = { model_association.macro => model_association.name } - else - # Computed association. We could infer has_many vs. has_one from - # the association class, but that would make it different from - # real associations, which read has_one vs. belongs_to from the - # model. - associations[association.key] = nil - end - end - - { attributes: attrs, associations: associations } - end - - # The model class associated with this serializer. - def model_class - name.sub(/Serializer$/, '').constantize - 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 - alias_method :root=, :root - - # Used internally to create a new serializer object based on controller - # settings and options for a given resource. These settings are typically - # set during the request lifecycle or by the controller class, and should - # not be manually defined for this method. - def build_json(controller, resource, options) - default_options = controller.send(:default_serializer_options) || {} - options = default_options.merge(options || {}) - - serializer = options.delete(:serializer) || - (resource.respond_to?(:active_model_serializer) && - resource.active_model_serializer) - - return serializer unless serializer - - if resource.respond_to?(:to_ary) - unless serializer <= ActiveModel::ArraySerializer - raise ArgumentError.new("#{serializer.name} is not an ArraySerializer. " + - "You may want to use the :each_serializer option instead.") - end - - if options[:root] != false && serializer.root != false - # the serializer for an Array is ActiveModel::ArraySerializer - options[:root] ||= serializer.root || controller.controller_name - end - end - - options[:scope] = controller.serialization_scope unless options.has_key?(:scope) - options[:scope_name] = controller._serialization_scope unless options.has_key?(:scope_name) - options[:url_options] = controller.url_options - - serializer.new(resource, options) - end end - attr_reader :object, :options - def initialize(object, options={}) - @object, @options = object, options + @object = object + @scope = options[:scope] + self.root = options[:root] + @meta_key = options[:meta_key] || :meta + @meta = options[@meta_key] + end + attr_accessor :object, :scope, :meta_key, :meta + attr_reader :root - scope_name = @options[:scope_name] - if scope_name && !respond_to?(scope_name) - self.class.class_eval do - define_method scope_name, lambda { scope } - end - end + def root=(root) + @root = root + @root = self.class._root if @root.nil? + @root = self.class.root_name if @root == true || @root.nil? end - def root_name - return false if self._root == false - - class_name = self.class.name.demodulize.underscore.sub(/_serializer$/, '').to_sym unless self.class.name.blank? - - if self._root == true - class_name - else - self._root || class_name - end - end - - def url_options - @options[:url_options] || {} - end - - # Returns a json representation of the serializable - # object including the root. - def as_json(args={}) - super(root: args.fetch(:root, options.fetch(:root, root_name))) - end - - def serialize_object - serializable_hash - end - - # Returns a hash representation of the serializable - # object without the root. - def serializable_hash - return nil if @object.nil? - @node = attributes - include_associations! if _embed - @node - end - - def include_associations! - _associations.each_key do |name| - include!(name) if include?(name) - end - end - - def include?(name) - return false if @options.key?(:only) && !Array(@options[:only]).include?(name) - return false if @options.key?(:except) && Array(@options[:except]).include?(name) - send INCLUDE_METHODS[name] - end - - def include!(name, options={}) - hash = @options[:hash] - unique_values = @options[:unique_values] ||= {} - - node = options[:node] ||= @node - value = options[:value] - - if options[:include] == nil - if @options.key?(:include) - options[:include] = @options[:include].include?(name) - elsif @options.include?(:exclude) - options[:include] = !@options[:exclude].include?(name) - end - end - - klass, klass_options = _associations[name] - association_class = - if klass - options = klass_options.merge options - klass - elsif value.respond_to?(:to_ary) - Association::HasMany - else - Association::HasOne - end - - options = default_embed_options.merge!(options) - options[:value] ||= send(name) - association = association_class.new(name, options, self.options) - - if association.embed_ids? - node[association.key] = association.serialize_ids - - if association.embed_in_root? && hash.nil? - raise IncludeError.new(self.class, association.name) - elsif association.embed_in_root? && association.embeddable? - merge_association hash, association.root, association.serializables, unique_values - end - elsif association.embed_objects? - node[association.key] = association.serialize - end - end - - # In some cases, an Array of associations is built by merging the associated - # content for all of the children. For instance, if a Post has_many comments, - # which has_many tags, the top-level :tags key will contain the merged list - # of all tags for all comments of the post. - # - # In order to make this efficient, we store a :unique_values hash containing - # a unique list of all of the objects that are already in the Array. This - # avoids the need to scan through the Array looking for entries every time - # we want to merge a new list of values. - def merge_association(hash, key, serializables, unique_values) - already_serialized = (unique_values[key] ||= {}) - serializable_hashes = (hash[key] ||= []) - - serializables.each do |serializable| - unless already_serialized.include? serializable.object - already_serialized[serializable.object] = true - serializable_hashes << serializable.serializable_hash - end - end - end - - # Returns a hash representation of the serializable - # object attributes. def attributes - _fast_attributes - rescue NameError - method = "def _fast_attributes\n" + filter(self.class._attributes.dup).each_with_object({}) do |name, hash| + hash[name] = send(name) + end + end - method << " h = {}\n" - - _attributes.each do |name,key| - method << " h[:\"#{key}\"] = read_attribute_for_serialization(:\"#{name}\") if include?(:\"#{name}\")\n" + def associations + associations = self.class._associations + included_associations = filter(associations.keys) + associations.each_with_object({}) do |(name, association), hash| + if included_associations.include? name + if association.embed_ids? + hash[association.key] = serialize_ids association + elsif association.embed_objects? + hash[association.embedded_key] = serialize association + end end - method << " h\nend" - - self.class.class_eval method - _fast_attributes + end end - # Returns options[:scope] - def scope - @options[:scope] + def filter(keys) + keys end - alias :read_attribute_for_serialization :send - - # Use ActiveSupport::Notifications to send events to external systems. - # The event name is: name.class_name.serializer - def instrument(name, payload = {}, &block) - event_name = INSTRUMENT[name] - ActiveSupport::Notifications.instrument(event_name, payload, &block) + def serializable_data + embedded_in_root_associations.merge!(super) end - private - - def default_embed_options - { - embed: _embed, - include: _root_embed - } - end - end - - # DefaultSerializer - # - # Provides a constant interface for all items, particularly - # for ArraySerializer. - class DefaultSerializer - attr_reader :object, :options - - def initialize(object, options={}) - @object, @options = object, options + def embedded_in_root_associations + associations = self.class._associations + included_associations = filter(associations.keys) + associations.each_with_object({}) do |(name, association), hash| + if included_associations.include? name + if association.embed_in_root? + hash[association.embedded_key] = serialize association + end + end + end end - def serializable_hash - @object.as_json(@options) + def serialize(association) + associated_data = send(association.name) + if associated_data.respond_to?(:to_ary) + associated_data.map { |elem| association.build_serializer(elem).serializable_hash } + else + result = association.build_serializer(associated_data).serializable_hash + association.is_a?(Association::HasMany) ? [result] : result + end end + + def serialize_ids(association) + associated_data = send(association.name) + if associated_data.respond_to?(:to_ary) + associated_data.map { |elem| elem.read_attribute_for_serialization(association.embed_key) } + else + associated_data.read_attribute_for_serialization(association.embed_key) if associated_data + end + end + + def serializable_hash(options={}) + return nil if object.nil? + hash = attributes + hash.merge! associations + end + alias serializable_object serializable_hash end end diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index 1f2b0b53..863edc54 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -1,183 +1,53 @@ +require 'active_model/default_serializer' +require 'active_model/serializer' + module ActiveModel class Serializer - class Association #:nodoc: - # name: The name of the association. - # - # options: A hash. These keys are accepted: - # - # value: The object we're associating with. - # - # serializer: The class used to serialize the association. - # - # embed: Define how associations should be embedded. - # - :objects # Embed associations as full objects. - # - :ids # Embed only the association ids. - # - :ids, include: true # Embed the association ids and include objects in the root. - # - # include: Used in conjunction with embed :ids. Includes the objects in the root. - # - # root: Used in conjunction with include: true. Defines the key used to embed the objects. - # - # key: Key name used to store the ids in. - # - # embed_key: Method used to fetch ids. Defaults to :id. - # - # polymorphic: Is the association is polymorphic?. Values: true or false. - def initialize(name, options={}, serializer_options={}) - @name = name - @object = options[:value] + class Association + def initialize(name, options={}) + @name = name.to_s + @options = options - embed = options[:embed] - @embed_ids = embed == :id || embed == :ids - @embed_objects = embed == :object || embed == :objects + self.embed = options[:embed] || SETTINGS[:embed] || :objects + @embed_in_root = @embed_ids && (options[:include] || SETTINGS[:include]) @embed_key = options[:embed_key] || :id - @embed_in_root = options[:include] + @key = options[:key] + @embedded_key = options[:root] || name - serializer = options[:serializer] - @serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer - - @options = options - @serializer_options = serializer_options + self.serializer_class = @options[:serializer] end - attr_reader :object, :root, :name, :embed_ids, :embed_objects, :embed_in_root - alias embeddable? object - alias embed_objects? embed_objects + attr_reader :name, :embed_ids, :embed_objects, :serializer_class + attr_accessor :embed_in_root, :embed_key, :key, :embedded_key, :options alias embed_ids? embed_ids - alias use_id_key? embed_ids? + alias embed_objects? embed_objects alias embed_in_root? embed_in_root - def key - if key = options[:key] - key - elsif use_id_key? - id_key - else - name - end + def serializer_class=(serializer) + @serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer end - private - - attr_reader :embed_key, :serializer_class, :options, :serializer_options - - def find_serializable(object) - if serializer_class - serializer_class.new(object, serializer_options) - elsif object.respond_to?(:active_model_serializer) && (ams = object.active_model_serializer) - ams.new(object, serializer_options) - else - object - end + def embed=(embed) + @embed_ids = embed == :id || embed == :ids + @embed_objects = embed == :object || embed == :objects end - class HasMany < Association #:nodoc: - def root - options[:root] || name - end - - def id_key - "#{name.to_s.singularize}_ids".to_sym - end - - def serializables - object.map do |item| - find_serializable(item) - end - end - - def serialize - object.map do |item| - find_serializable(item).serializable_hash - end - end - - def serialize_ids - object.map do |item| - serializer = find_serializable(item) - if serializer.respond_to?(embed_key) - serializer.send(embed_key) - else - item.read_attribute_for_serialization(embed_key) - end - end - end + def build_serializer(object) + @serializer_class ||= Serializer.serializer_for(object) || DefaultSerializer + @serializer_class.new(object, @options) end - class HasOne < Association #:nodoc: - def initialize(name, options={}, serializer_options={}) + class HasOne < Association + def initialize(*args) super - @polymorphic = options[:polymorphic] + @key ||= "#{name}_id" end + end - def root - if root = options[:root] - root - elsif polymorphic? - object.class.to_s.pluralize.demodulize.underscore.to_sym - else - name.to_s.pluralize.to_sym - end - end - - def id_key - "#{name}_id".to_sym - end - - def embeddable? - super || !polymorphic? - end - - def serializables - value = object && find_serializable(object) - value ? [value] : [] - end - - def serialize - if object - if polymorphic? - { - :type => polymorphic_key, - polymorphic_key => find_serializable(object).serializable_hash - } - else - find_serializable(object).serializable_hash - end - end - end - - def serialize_ids - if object - serializer = find_serializable(object) - id = - if serializer.respond_to?(embed_key) - serializer.send(embed_key) - else - object.read_attribute_for_serialization(embed_key) - end - - if polymorphic? - { - type: polymorphic_key, - id: id - } - else - id - end - end - end - - private - - attr_reader :polymorphic - alias polymorphic? polymorphic - - def use_id_key? - embed_ids? && !polymorphic? - end - - def polymorphic_key - object.class.to_s.demodulize.underscore.to_sym + class HasMany < Association + def initialize(*args) + super + @key ||= "#{name.singularize}_ids" end end end diff --git a/lib/active_model/serializer/caching.rb b/lib/active_model/serializer/caching.rb deleted file mode 100644 index 50fcf7b5..00000000 --- a/lib/active_model/serializer/caching.rb +++ /dev/null @@ -1,37 +0,0 @@ -module ActiveModel - class Serializer - module Caching - def to_json(*args) - if caching_enabled? - key = expand_cache_key([self.class.to_s.underscore, cache_key, 'to-json']) - cache.fetch key do - super - end - else - super - end - end - - def serialize(*args) - if caching_enabled? - key = expand_cache_key([self.class.to_s.underscore, cache_key, 'serialize']) - cache.fetch key do - serialize_object - end - else - serialize_object - end - end - - private - - def caching_enabled? - perform_caching && cache && respond_to?(:cache_key) - end - - def expand_cache_key(*args) - ActiveSupport::Cache.expand_cache_key(args) - end - end - end -end diff --git a/lib/active_model/serializer/generators/resource_override.rb b/lib/active_model/serializer/generators/resource_override.rb new file mode 100644 index 00000000..9fad8dc1 --- /dev/null +++ b/lib/active_model/serializer/generators/resource_override.rb @@ -0,0 +1,13 @@ +require 'rails/generators' +require 'rails/generators/rails/resource/resource_generator' + +module Rails + module Generators + class ResourceGenerator + def add_serializer + invoke 'serializer' + end + end + end +end + diff --git a/lib/generators/serializer/USAGE b/lib/active_model/serializer/generators/serializer/USAGE similarity index 100% rename from lib/generators/serializer/USAGE rename to lib/active_model/serializer/generators/serializer/USAGE diff --git a/lib/generators/serializer/serializer_generator.rb b/lib/active_model/serializer/generators/serializer/serializer_generator.rb similarity index 60% rename from lib/generators/serializer/serializer_generator.rb rename to lib/active_model/serializer/generators/serializer/serializer_generator.rb index 8212d62c..7c4c036d 100644 --- a/lib/generators/serializer/serializer_generator.rb +++ b/lib/active_model/serializer/generators/serializer/serializer_generator.rb @@ -1,12 +1,12 @@ module Rails module Generators class SerializerGenerator < NamedBase - source_root File.expand_path("../templates", __FILE__) - check_class_collision suffix: "Serializer" + source_root File.expand_path('../templates', __FILE__) + check_class_collision suffix: 'Serializer' - argument :attributes, type: :array, default: [], banner: "field:type field:type" + argument :attributes, type: :array, default: [], banner: 'field:type field:type' - class_option :parent, type: :string, desc: "The parent class for the generated serializer" + class_option :parent, type: :string, desc: 'The parent class for the generated serializer' def create_serializer_file template 'serializer.rb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb") @@ -25,10 +25,11 @@ module Rails def parent_class_name if options[:parent] options[:parent] - elsif defined?(::ApplicationSerializer) - "ApplicationSerializer" + elsif (ns = Rails::Generators.namespace) && ns.const_defined?(:ApplicationSerializer) || + defined?(::ApplicationSerializer) + 'ApplicationSerializer' else - "ActiveModel::Serializer" + 'ActiveModel::Serializer' end end end diff --git a/lib/generators/serializer/templates/serializer.rb b/lib/active_model/serializer/generators/serializer/templates/serializer.rb similarity index 100% rename from lib/generators/serializer/templates/serializer.rb rename to lib/active_model/serializer/generators/serializer/templates/serializer.rb diff --git a/lib/active_model/serializer/railtie.rb b/lib/active_model/serializer/railtie.rb new file mode 100644 index 00000000..080fc53a --- /dev/null +++ b/lib/active_model/serializer/railtie.rb @@ -0,0 +1,10 @@ +module ActiveModel + class Railtie < Rails::Railtie + initializer 'generators' do |app| + require 'rails/generators' + require 'active_model/serializer/generators/serializer/serializer_generator' + Rails::Generators.configure!(app.config.generators) + require 'active_model/serializer/generators/resource_override' + end + end +end diff --git a/lib/active_model/serializer/settings.rb b/lib/active_model/serializer/settings.rb new file mode 100644 index 00000000..4a3e7c9d --- /dev/null +++ b/lib/active_model/serializer/settings.rb @@ -0,0 +1,27 @@ +module ActiveModel + class Serializer + class Settings + def initialize + @data = {} + end + + def [](key) + @data[key.to_s] + end + + def []=(key, value) + @data[key.to_s] = value + end + + def each(&block) + @data.each(&block) + end + + def clear + @data.clear + end + end + + SETTINGS = Settings.new + end +end diff --git a/lib/active_model/serializer/version.rb b/lib/active_model/serializer/version.rb index 10e62668..fa2ed76d 100644 --- a/lib/active_model/serializer/version.rb +++ b/lib/active_model/serializer/version.rb @@ -1,5 +1,5 @@ module ActiveModel class Serializer - VERSION = "0.8.1" + VERSION = "0.9.0.pre" end end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 4ae2d743..102ea3c0 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -1,85 +1,7 @@ -require "active_support" -require "active_support/core_ext/string/inflections" -require "active_support/notifications" -require "active_model" -require "active_model/array_serializer" -require "active_model/serializer" -require "active_model/serializer/associations" -require "set" - -if defined?(Rails) - module ActiveModel - class Railtie < Rails::Railtie - generators do |app| - Rails::Generators.configure!(app.config.generators) - Rails::Generators.hidden_namespaces.uniq! - require_relative "generators/resource_override" - end - - initializer "include_routes.active_model_serializer" do |app| - ActiveSupport.on_load(:active_model_serializers) do - include AbstractController::UrlFor - extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) - include app.routes.mounted_helpers - end - end - - initializer "caching.active_model_serializer" do |app| - ActiveModel::Serializer.perform_caching = app.config.action_controller.perform_caching - ActiveModel::ArraySerializer.perform_caching = app.config.action_controller.perform_caching - - ActiveModel::Serializer.cache = Rails.cache - ActiveModel::ArraySerializer.cache = Rails.cache - end - end - end -end - -module ActiveModel::SerializerSupport - extend ActiveSupport::Concern - - module ClassMethods #:nodoc: - if "".respond_to?(:safe_constantize) - def active_model_serializer - "#{self.name}Serializer".safe_constantize - end - else - def active_model_serializer - begin - "#{self.name}Serializer".constantize - rescue NameError => e - raise unless e.message =~ /uninitialized constant/ - end - end - end - end - - # Returns a model serializer for this object considering its namespace. - def active_model_serializer - self.class.active_model_serializer - end - - alias :read_attribute_for_serialization :send -end - -module ActiveModel::ArraySerializerSupport - def active_model_serializer - ActiveModel::ArraySerializer - end -end - -Array.send(:include, ActiveModel::ArraySerializerSupport) -Set.send(:include, ActiveModel::ArraySerializerSupport) - -{ - active_record: 'ActiveRecord::Relation', - mongoid: 'Mongoid::Criteria' -}.each do |orm, rel_class| - ActiveSupport.on_load(orm) do - include ActiveModel::SerializerSupport - rel_class.constantize.send(:include, ActiveModel::ArraySerializerSupport) - end -end +require 'active_model' +require 'active_model/serializer' +require 'active_model/serializer/version' +require 'active_model/serializer/railtie' if defined?(Rails) begin require 'action_controller' @@ -88,8 +10,6 @@ begin ActiveSupport.on_load(:action_controller) do include ::ActionController::Serialization end -rescue LoadError => ex - # rails on installed, continuing +rescue LoadError + # rails not installed, continuing end - -ActiveSupport.run_load_hooks(:active_model_serializers, ActiveModel::Serializer) diff --git a/lib/active_record/serializer_override.rb b/lib/active_record/serializer_override.rb deleted file mode 100644 index b6149b83..00000000 --- a/lib/active_record/serializer_override.rb +++ /dev/null @@ -1,16 +0,0 @@ -# We do not recommend that you use AM::S in this way, but if you must, here -# is a mixin that overrides ActiveRecord::Base#to_json and #as_json. - -module ActiveRecord - module SerializerOverride - def to_json options = {} - active_model_serializer.new(self).to_json options - end - - def as_json options={} - active_model_serializer.new(self).as_json options - end - end - - Base.send(:include, SerializerOverride) -end diff --git a/lib/generators/resource_override.rb b/lib/generators/resource_override.rb deleted file mode 100644 index 1b48a12e..00000000 --- a/lib/generators/resource_override.rb +++ /dev/null @@ -1,13 +0,0 @@ -require "rails/generators" -require "rails/generators/rails/resource/resource_generator" - -module Rails - module Generators - ResourceGenerator.class_eval do - def add_serializer - invoke "serializer" - end - end - end -end - diff --git a/test/array_serializer_test.rb b/test/array_serializer_test.rb deleted file mode 100644 index d48e8027..00000000 --- a/test/array_serializer_test.rb +++ /dev/null @@ -1,85 +0,0 @@ -require "test_helper" -require "test_fakes" - -class ArraySerializerTest < ActiveModel::TestCase - # serialize different typed objects - 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: { scope: true }) - assert_equal([ - { model: "Model" }, - { last_name: "Valim", ok: true, first_name: "Jose", scope: true }, - { title: "Comment1" } - ], serializer.as_json) - end - - def test_array_serializer_with_root - comment1 = Comment.new(title: "Comment1", id: 1) - comment2 = Comment.new(title: "Comment2", id: 2) - - array = [ comment1, comment2 ] - - serializer = array.active_model_serializer.new(array, root: :comments) - - assert_equal({ comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ]}, serializer.as_json) - end - - def test_active_model_with_root - comment1 = ModelWithActiveModelSerializer.new(title: "Comment1") - comment2 = ModelWithActiveModelSerializer.new(title: "Comment2") - - array = [ comment1, comment2 ] - - serializer = array.active_model_serializer.new(array, root: :comments) - - assert_equal({ comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ]}, serializer.as_json) - end - - def test_array_serializer_with_hash - hash = { value: "something" } - array = [hash] - serializer = array.active_model_serializer.new(array, root: :items) - assert_equal({ items: [hash.as_json] }, serializer.as_json) - end - - def test_array_serializer_with_specified_serializer - post1 = Post.new(title: "Post1", author: "Author1", id: 1) - post2 = Post.new(title: "Post2", author: "Author2", id: 2) - - array = [ post1, post2 ] - - serializer = array.active_model_serializer.new array, each_serializer: CustomPostSerializer - - assert_equal([ - { title: "Post1" }, - { title: "Post2" } - ], serializer.as_json) - end - - def test_array_serializer_using_default_serializer - hash = { "value" => "something" } - class << hash - def active_model_serializer - nil - end - end - - array = [hash] - - serializer = array.active_model_serializer.new array - - assert_equal([ - { "value" => "something" } - ], serializer.as_json) - end -end diff --git a/test/association_test.rb b/test/association_test.rb deleted file mode 100644 index 3ae225a0..00000000 --- a/test/association_test.rb +++ /dev/null @@ -1,592 +0,0 @@ -require "test_helper" - -class AssociationTest < ActiveModel::TestCase - def def_serializer(&block) - Class.new(ActiveModel::Serializer, &block) - end - - class Model - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - - def as_json(*) - { model: "Model" } - end - - def method_missing(meth, *args) - if meth.to_s =~ /^(.*)=$/ - @attributes[$1.to_sym] = args[0] - elsif @attributes.key?(meth) - @attributes[meth] - else - super - end - end - end - - def setup - @hash = {} - @root_hash = {} - - @post = Model.new(title: "New Post", body: "Body") - @comment = Model.new(id: 1, external_id: "COMM001", body: "ZOMG A COMMENT") - @post.comments = [ @comment ] - @post.comment = @comment - - @comment_serializer_class = def_serializer do - attributes :id, :external_id, :body - end - - @post_serializer_class = def_serializer do - attributes :title, :body - end - - @post_serializer = @post_serializer_class.new(@post, hash: @root_hash) - end - - def include!(key, options={}) - @post_serializer.include! key, { - embed: :ids, - include: true, - node: @hash, - serializer: @comment_serializer_class - }.merge(options) - end - - def include_bare!(key, options={}) - @post_serializer.include! key, { - node: @hash, - serializer: @comment_serializer_class - }.merge(options) - end - - class NoDefaults < AssociationTest - def test_include_bang_has_many_associations - include! :comments, value: @post.comments - - assert_equal({ - comment_ids: [ 1 ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_include_bang_with_embed_false - include! :comments, value: @post.comments, embed: false - - assert_equal({}, @hash) - assert_equal({}, @root_hash) - end - - def test_include_bang_with_embed_ids_include_false - include! :comments, value: @post.comments, embed: :ids, include: false - - assert_equal({ - comment_ids: [ 1 ] - }, @hash) - - assert_equal({}, @root_hash) - end - - def test_include_bang_has_one_associations - include! :comment, value: @post.comment - - assert_equal({ - comment_id: 1 - }, @hash) - - assert_equal({ - comments: [{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }] - }, @root_hash) - end - end - - class DefaultsTest < AssociationTest - def test_with_default_has_many - @post_serializer_class.class_eval do - has_many :comments - end - - include! :comments - - assert_equal({ - comment_ids: [ 1 ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_one - @post_serializer_class.class_eval do - has_one :comment - end - - include! :comment - - assert_equal({ - comment_id: 1 - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_many_with_custom_key - @post_serializer_class.class_eval do - has_many :comments, key: :custom_comments - end - - include! :comments - - assert_equal({ - custom_comments: [ 1 ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_one_with_custom_key - @post_serializer_class.class_eval do - has_one :comment, key: :custom_comment_id - end - - include! :comment - - assert_equal({ - custom_comment_id: 1 - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_many_with_custom_embed_key - @post_serializer_class.class_eval do - has_many :comments, embed_key: :external_id - end - - include! :comments - - assert_equal({ - comment_ids: [ "COMM001" ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_one_with_custom_embed_key - @post_serializer_class.class_eval do - has_one :comment, embed_key: :external_id - end - - include! :comment - - assert_equal({ - comment_id: "COMM001" - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_many_with_custom_key_and_custom_embed_key - @post_serializer_class.class_eval do - has_many :comments, key: :custom_comments, embed_key: :external_id - end - - include! :comments - - assert_equal({ - custom_comments: [ "COMM001" ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_one_with_custom_key_and_custom_embed_key - @post_serializer_class.class_eval do - has_one :comment, key: :custom_comment, embed_key: :external_id - end - - include! :comment - - assert_equal({ - custom_comment: "COMM001" - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_embed_objects_for_has_many_associations - @post_serializer_class.class_eval do - has_many :comments, embed: :objects - end - - include_bare! :comments - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @hash) - - assert_equal({}, @root_hash) - end - - def test_embed_ids_for_has_many_associations - @post_serializer_class.class_eval do - has_many :comments, embed: :ids - end - - include_bare! :comments - - assert_equal({ - comment_ids: [ 1 ] - }, @hash) - - assert_equal({}, @root_hash) - end - - def test_embed_false_for_has_many_associations - @post_serializer_class.class_eval do - has_many :comments, embed: false - end - - include_bare! :comments - - assert_equal({}, @hash) - assert_equal({}, @root_hash) - end - - def test_embed_ids_include_true_for_has_many_associations - @post_serializer_class.class_eval do - has_many :comments, embed: :ids, include: true - end - - include_bare! :comments - - assert_equal({ - comment_ids: [ 1 ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_embed_ids_for_has_one_associations - @post_serializer_class.class_eval do - has_one :comment, embed: :ids - end - - include_bare! :comment - - assert_equal({ - comment_id: 1 - }, @hash) - - assert_equal({}, @root_hash) - end - - def test_embed_false_for_has_one_associations - @post_serializer_class.class_eval do - has_one :comment, embed: false - end - - include_bare! :comment - - assert_equal({}, @hash) - assert_equal({}, @root_hash) - end - - def test_embed_ids_include_true_for_has_one_associations - @post_serializer_class.class_eval do - has_one :comment, embed: :ids, include: true - end - - include_bare! :comment - - assert_equal({ - comment_id: 1 - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_embed_ids_include_true_does_not_serialize_multiple_times - @post.recent_comment = @comment - - @post_serializer_class.class_eval do - has_one :comment, embed: :ids, include: true - has_one :recent_comment, embed: :ids, include: true, root: :comments - end - - # Count how often the @comment record is serialized. - serialized_times = 0 - @comment.class_eval do - define_method :read_attribute_for_serialization, lambda { |name| - serialized_times += 1 if name == :body - super(name) - } - end - - include_bare! :comment - include_bare! :recent_comment - - assert_equal 1, serialized_times - end - - def test_include_with_read_association_id_for_serialization_hook - @post_serializer_class.class_eval do - has_one :comment, embed: :ids, include: true - end - - association_name = nil - @post.class_eval do - define_method :read_attribute_for_serialization, lambda { |name| - association_name = name - send(name) - } - define_method :comment_id, lambda { - @attributes[:comment].id - } - end - - include_bare! :comment - - assert_equal({ - comment_id: 1 - }, @hash) - end - - def test_include_with_read_association_ids_for_serialization_hook - @post_serializer_class.class_eval do - has_many :comments, embed: :ids, include: false - end - - association_name = nil - @post.class_eval do - define_method :read_attribute_for_serialization, lambda { |name| - association_name = name - send(name) - } - define_method :comment_ids, lambda { - @attributes[:comments].map(&:id) - } - end - - include_bare! :comments - - assert_equal({ - comment_ids: [1] - }, @hash) - end - end - - class RecursiveTest < AssociationTest - class BarSerializer < ActiveModel::Serializer; end - - class FooSerializer < ActiveModel::Serializer - root :foos - attributes :id - has_many :bars, serializer: BarSerializer, root: :bars, embed: :ids, include: true - end - - class BarSerializer < ActiveModel::Serializer - root :bars - attributes :id - has_many :foos, serializer: FooSerializer, root: :foos, embed: :ids, include: true - end - - class Foo < Model - def active_model_serializer; FooSerializer; end - end - - class Bar < Model - def active_model_serializer; BarSerializer; end - end - - def setup - super - - foo = Foo.new(id: 1) - bar = Bar.new(id: 2) - - foo.bars = [ bar ] - bar.foos = [ foo ] - - collection = [ foo ] - - @serializer = collection.active_model_serializer.new(collection, root: :foos) - end - - def test_mutual_relation_result - assert_equal({ - foos: [{ - bar_ids: [ 2 ], - id: 1 - }], - bars: [{ - foo_ids: [ 1 ], - id: 2 - }] - }, @serializer.as_json) - end - - def test_mutual_relation_does_not_raise_error - assert_nothing_raised SystemStackError, 'stack level too deep' do - @serializer.as_json - end - end - end - - class InclusionTest < AssociationTest - def setup - super - - comment_serializer_class = @comment_serializer_class - - @post_serializer_class.class_eval do - root :post - embed :ids, include: true - has_many :comments, serializer: comment_serializer_class - end - end - - def test_when_it_is_included - post_serializer = @post_serializer_class.new( - @post, include: [:comments] - ) - - json = post_serializer.as_json - - assert_equal({ - post: { - title: "New Post", - body: "Body", - comment_ids: [ 1 ] - }, - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, json) - end - - def test_when_it_is_not_included - post_serializer = @post_serializer_class.new( - @post, include: [] - ) - - json = post_serializer.as_json - - assert_equal({ - post: { - title: "New Post", - body: "Body", - comment_ids: [ 1 ] - } - }, json) - end - - def test_when_it_is_excluded - post_serializer = @post_serializer_class.new( - @post, exclude: [:comments] - ) - - json = post_serializer.as_json - - assert_equal({ - post: { - title: "New Post", - body: "Body", - comment_ids: [ 1 ] - } - }, json) - end - - def test_when_it_is_not_excluded - post_serializer = @post_serializer_class.new( - @post, exclude: [] - ) - - json = post_serializer.as_json - - assert_equal({ - post: { - title: "New Post", - body: "Body", - comment_ids: [ 1 ] - }, - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, json) - end - end - - class StringSerializerOption < AssociationTest - class StringSerializer < ActiveModel::Serializer - attributes :id, :body - end - - def test_specifying_serializer_class_as_string - @post_serializer_class.class_eval do - has_many :comments, embed: :objects - end - - include_bare! :comments, serializer: "AssociationTest::StringSerializerOption::StringSerializer" - - assert_equal({ - comments: [ - { id: 1, body: "ZOMG A COMMENT" } - ] - }, @hash) - - assert_equal({}, @root_hash) - end - end -end diff --git a/test/caching_test.rb b/test/caching_test.rb deleted file mode 100644 index ee1dd263..00000000 --- a/test/caching_test.rb +++ /dev/null @@ -1,96 +0,0 @@ -require "test_helper" - -class CachingTest < ActiveModel::TestCase - class NullStore - def fetch(key) - return store[key] if store[key] - - store[key] = yield - end - - def clear - store.clear - end - - def store - @store ||= {} - end - - def read(key) - store[key] - end - end - - class Programmer - def name - 'Adam' - end - - def skills - %w(ruby) - end - - def read_attribute_for_serialization(name) - send name - end - end - - def test_serializers_have_a_cache_store - ActiveModel::Serializer.cache = NullStore.new - - assert_kind_of NullStore, ActiveModel::Serializer.cache - end - - def test_serializers_can_enable_caching - serializer = Class.new(ActiveModel::Serializer) do - cached true - end - - assert serializer.perform_caching - end - - def test_serializers_use_cache - serializer = Class.new(ActiveModel::Serializer) do - cached true - attributes :name, :skills - - def self.to_s - 'serializer' - end - - def cache_key - object.name - end - end - - serializer.cache = NullStore.new - instance = serializer.new Programmer.new - - instance.to_json - - assert_equal(instance.serializable_hash, serializer.cache.read('serializer/Adam/serialize')) - assert_equal(instance.to_json, serializer.cache.read('serializer/Adam/to-json')) - end - - def test_array_serializer_uses_cache - serializer = Class.new(ActiveModel::ArraySerializer) do - cached true - - def self.to_s - 'array_serializer' - end - - def cache_key - 'cache-key' - end - end - - serializer.cache = NullStore.new - instance = serializer.new [Programmer.new] - - instance.to_json - - assert_equal instance.serializable_array, serializer.cache.read('array_serializer/cache-key/serialize') - assert_equal instance.to_json, serializer.cache.read('array_serializer/cache-key/to-json') - end -end diff --git a/test/coverage_setup.rb b/test/coverage_setup.rb new file mode 100644 index 00000000..7fe12c67 --- /dev/null +++ b/test/coverage_setup.rb @@ -0,0 +1,12 @@ +require 'simplecov' +require 'coveralls' + +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter +] + +SimpleCov.start do + add_group "lib", "lib" + add_group "test", "test" +end diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb new file mode 100644 index 00000000..6ae75f43 --- /dev/null +++ b/test/fixtures/active_record.rb @@ -0,0 +1,92 @@ +require 'active_record' + +ActiveRecord::Base.establish_connection( + :adapter => 'sqlite3', + :database => ':memory:' +) + +ActiveRecord::Schema.define do + create_table :ar_posts, force: true do |t| + t.string :title + t.text :body + t.belongs_to :ar_section, index: true + t.timestamps + end + + create_table :ar_comments, force: true do |t| + t.text :body + t.belongs_to :ar_post, index: true + t.timestamps + end + + create_table :ar_tags, force: true do |t| + t.string :name + end + + create_table :ar_sections, force: true do |t| + t.string :name + end + + create_table :ar_posts_tags, force: true do |t| + t.references :ar_post, :ar_tag, index: true + end + + create_table :ar_comments_tags, force: true do |t| + t.references :ar_comment, :ar_tag, index: true + end +end + +class ARPost < ActiveRecord::Base + has_many :ar_comments, class_name: 'ARComment' + has_and_belongs_to_many :ar_tags, class_name: 'ARTag', join_table: :ar_posts_tags + belongs_to :ar_section, class_name: 'ARSection' +end + +class ARComment < ActiveRecord::Base + belongs_to :ar_post, class_name: 'ARPost' + has_and_belongs_to_many :ar_tags, class_name: 'ARTag', join_table: :ar_comments_tags +end + +class ARTag < ActiveRecord::Base +end + +class ARSection < ActiveRecord::Base +end + +class ARPostSerializer < ActiveModel::Serializer + attributes :title, :body + + has_many :ar_comments, :ar_tags + has_one :ar_section +end + +class ARCommentSerializer < ActiveModel::Serializer + attributes :body + + has_many :ar_tags +end + +class ARTagSerializer < ActiveModel::Serializer + attributes :name +end + +class ARSectionSerializer < ActiveModel::Serializer + attributes :name +end + +ARPost.create(title: 'New post', + body: 'A body!!!', + ar_section: ARSection.create(name: 'ruby')).tap do |post| + + short_tag = post.ar_tags.create(name: 'short') + whiny_tag = post.ar_tags.create(name: 'whiny') + happy_tag = post.ar_tags.create(name: 'happy') + + post.ar_comments.create(body: 'what a dumb post').tap do |comment| + comment.ar_tags.concat short_tag, whiny_tag + end + + post.ar_comments.create(body: 'i liked it').tap do |comment| + comment.ar_tags.concat short_tag, happy_tag + end +end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb new file mode 100644 index 00000000..a43954b5 --- /dev/null +++ b/test/fixtures/poro.rb @@ -0,0 +1,64 @@ +class Model + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + if name == :id || name == 'id' + object_id + else + @attributes[name] + end + end +end + + +### +## Models +### +class User < Model + def profile + @profile ||= Profile.new(name: 'N1', description: 'D1') + end +end + +class Profile < Model +end + +class Post < Model + def comments + @comments ||= [Comment.new(content: 'C1'), + Comment.new(content: 'C2')] + end +end + +class Comment < Model +end + +### +## Serializers +### +class UserSerializer < ActiveModel::Serializer + attributes :name, :email + + has_one :profile +end + +class ProfileSerializer < ActiveModel::Serializer + def description + description = object.read_attribute_for_serialization(:description) + scope ? "#{description} - #{scope}" : description + end + + attributes :name, :description +end + +class PostSerializer < ActiveModel::Serializer + attributes :title, :body + + has_many :comments +end + +class CommentSerializer < ActiveModel::Serializer + attributes :content +end diff --git a/test/generators_test.rb b/test/generators_test.rb deleted file mode 100644 index b1a05b3a..00000000 --- a/test/generators_test.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'test_helper' - -class Foo < Rails::Application - if Rails.version.to_s.start_with? '4' - config.eager_load = false - config.secret_key_base = 'abc123' - end -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 :id, :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" do |content| - assert_no_match /\n\nend/, content - end - end -end diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb new file mode 100644 index 00000000..024dc0ab --- /dev/null +++ b/test/integration/action_controller/serialization_test.rb @@ -0,0 +1,181 @@ +require 'test_helper' + +module ActionController + module Serialization + class ImplicitSerializerTest < ActionController::TestCase + class MyController < ActionController::Base + def render_using_implicit_serializer + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + end + end + + tests MyController + + def test_render_using_implicit_serializer + get :render_using_implicit_serializer + assert_equal 'application/json', @response.content_type + assert_equal '{"profile":{"name":"Name 1","description":"Description 1"}}', @response.body + end + end + + class ImplicitSerializerScopeTest < ActionController::TestCase + class MyController < ActionController::Base + def render_using_implicit_serializer_and_scope + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + end + + private + + def current_user + 'current_user' + end + end + + tests MyController + + def test_render_using_implicit_serializer_and_scope + get :render_using_implicit_serializer_and_scope + assert_equal 'application/json', @response.content_type + assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_user"}}', @response.body + end + end + + class DefaultOptionsForSerializerScopeTest < ActionController::TestCase + class MyController < ActionController::Base + def default_serializer_options + { scope: current_admin } + end + + def render_using_scope_set_in_default_serializer_options + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + end + + private + + def current_user + 'current_user' + end + + def current_admin + 'current_admin' + end + end + + tests MyController + + def test_render_using_scope_set_in_default_serializer_options + get :render_using_scope_set_in_default_serializer_options + assert_equal 'application/json', @response.content_type + assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_admin"}}', @response.body + end + end + + class ExplicitSerializerScopeTest < ActionController::TestCase + class MyController < ActionController::Base + def render_using_implicit_serializer_and_explicit_scope + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), scope: current_admin + end + + private + + def current_user + 'current_user' + end + + def current_admin + 'current_admin' + end + end + + tests MyController + + def test_render_using_implicit_serializer_and_explicit_scope + get :render_using_implicit_serializer_and_explicit_scope + assert_equal 'application/json', @response.content_type + assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_admin"}}', @response.body + end + end + + class OverridingSerializationScopeTest < ActionController::TestCase + class MyController < ActionController::Base + def render_overriding_serialization_scope + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + end + + private + + def current_user + 'current_user' + end + + def serialization_scope + 'current_admin' + end + end + + tests MyController + + def test_render_overriding_serialization_scope + get :render_overriding_serialization_scope + assert_equal 'application/json', @response.content_type + assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_admin"}}', @response.body + end + end + + class CallingSerializationScopeTest < ActionController::TestCase + class MyController < ActionController::Base + def render_calling_serialization_scope + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + end + + private + + def current_user + 'current_user' + end + + serialization_scope :current_user + end + + tests MyController + + def test_render_calling_serialization_scope + get :render_calling_serialization_scope + assert_equal 'application/json', @response.content_type + assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_user"}}', @response.body + end + end + + class RailsSerializerTest < ActionController::TestCase + class MyController < ActionController::Base + def render_using_rails_behavior + render json: JSON.dump(hello: 'world') + end + end + + tests MyController + + def test_render_using_rails_behavior + get :render_using_rails_behavior + assert_equal 'application/json', @response.content_type + assert_equal '{"hello":"world"}', @response.body + end + end + + class ArraySerializerTest < ActionController::TestCase + class MyController < ActionController::Base + def render_array + render json: [Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })] + end + end + + tests MyController + + def test_render_array + get :render_array + assert_equal 'application/json', @response.content_type + assert_equal '{"my":[{"name":"Name 1","description":"Description 1"}]}', @response.body + end + end + end +end diff --git a/test/integration/active_record/active_record_test.rb b/test/integration/active_record/active_record_test.rb new file mode 100644 index 00000000..cf914b90 --- /dev/null +++ b/test/integration/active_record/active_record_test.rb @@ -0,0 +1,75 @@ +require 'test_helper' +require 'fixtures/active_record' + +module ActiveModel + class Serializer + class ActiveRecordTest < ActiveModel::TestCase + def setup + @post = ARPost.first + end + + def test_serialization_embedding_objects + post_serializer = ARPostSerializer.new(@post) + + assert_equal({ + 'ar_post' => { + title: 'New post', body: 'A body!!!', + ar_comments: [{ body: 'what a dumb post', ar_tags: [{ name: 'short' }, { name: 'whiny' }] }, + { body: 'i liked it', ar_tags: [{:name=>"short"}, {:name=>"happy"}] }], + ar_tags: [{ name: 'short' }, { name: 'whiny' }, { name: 'happy' }], + ar_section: { name: 'ruby' } + } + }, post_serializer.as_json) + end + + def test_serialization_embedding_ids + post_serializer = ARPostSerializer.new(@post) + + embed(ARPostSerializer, embed: :ids) do + assert_equal({ + 'ar_post' => { + title: 'New post', body: 'A body!!!', + 'ar_comment_ids' => [1, 2], + 'ar_tag_ids' => [1, 2, 3], + 'ar_section_id' => 1 + } + }, post_serializer.as_json) + end + end + + def test_serialization_embedding_ids_including_in_root + post_serializer = ARPostSerializer.new(@post) + + embed(ARPostSerializer, embed: :ids, include: true) do + assert_equal({ + 'ar_post' => { + title: 'New post', body: 'A body!!!', + 'ar_comment_ids' => [1, 2], + 'ar_tag_ids' => [1, 2, 3], + 'ar_section_id' => 1 + }, + ar_comments: [{ body: 'what a dumb post', ar_tags: [{ name: 'short' }, { name: 'whiny' }] }, + { body: 'i liked it', ar_tags: [{:name=>"short"}, {:name=>"happy"}] }], + ar_tags: [{ name: 'short' }, { name: 'whiny' }, { name: 'happy' }], + ar_section: { name: 'ruby' } + }, post_serializer.as_json) + end + end + + private + + def embed(klass, options = {}) + old_assocs = Hash[ARPostSerializer._associations.to_a.map { |(name, association)| [name, association.dup] }] + + ARPostSerializer._associations.each_value do |association| + association.embed = options[:embed] + association.embed_in_root = options[:include] + end + + yield + ensure + ARPostSerializer._associations = old_assocs + end + end + end +end diff --git a/test/integration/generators/resource_generator_test.rb b/test/integration/generators/resource_generator_test.rb new file mode 100644 index 00000000..aa9eed13 --- /dev/null +++ b/test/integration/generators/resource_generator_test.rb @@ -0,0 +1,27 @@ +require 'test_helper' +require 'rails' +require 'test_app' +require 'rails/generators/rails/resource/resource_generator' +require 'active_model/serializer/generators/resource_override' + +class ResourceGeneratorTest < Rails::Generators::TestCase + destination File.expand_path('../../../tmp', __FILE__) + setup :prepare_destination, :copy_routes + + tests Rails::Generators::ResourceGenerator + arguments %w(account) + + def test_serializer_file_is_generated + run_generator + + assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < ActiveModel::Serializer/ + end + + private + + def copy_routes + config_dir = File.join(destination_root, 'config') + FileUtils.mkdir_p(config_dir) + File.write(File.join(config_dir, 'routes.rb'), 'Rails.application.routes.draw { }') + end +end diff --git a/test/integration/generators/serializer_generator_test.rb b/test/integration/generators/serializer_generator_test.rb new file mode 100644 index 00000000..ff75e772 --- /dev/null +++ b/test/integration/generators/serializer_generator_test.rb @@ -0,0 +1,41 @@ +require 'test_helper' +require 'rails' +require 'test_app' +require 'active_model/serializer/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_with_attributes_and_associations + run_generator + assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < ActiveModel::Serializer/ do |serializer| + assert_match(/attributes :id, :name, :description/, serializer) + assert_match(/has_one :business/, serializer) + end + 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_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 +end diff --git a/test/no_serialization_scope_test.rb b/test/no_serialization_scope_test.rb deleted file mode 100644 index 31ba475f..00000000 --- a/test/no_serialization_scope_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require "test_helper" - -class NoSerializationScopeTest < ActionController::TestCase - class ScopeSerializer - def initialize(object, options) - @object, @options = object, options - end - - def as_json(*) - { scope: @options[:scope].as_json } - end - end - - class ScopeSerializable - def active_model_serializer - ScopeSerializer - end - end - - class NoSerializationScopeController < ActionController::Base - serialization_scope nil - - def index - render json: ScopeSerializable.new - end - end - - tests NoSerializationScopeController - - def test_disabled_serialization_scope - get :index, format: :json - assert_equal '{"scope":null}', @response.body - end -end diff --git a/test/serialization_scope_name_test.rb b/test/serialization_scope_name_test.rb deleted file mode 100644 index a5e164c4..00000000 --- a/test/serialization_scope_name_test.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'test_helper' -require 'pathname' - -class DefaultScopeNameTest < ActionController::TestCase - TestUser = Struct.new(:name, :admin) - - class UserSerializer < ActiveModel::Serializer - attributes :admin? - def admin? - current_user.admin - end - end - - class UserTestController < ActionController::Base - protect_from_forgery - - before_filter { request.format = :json } - - def current_user - TestUser.new('Pete', false) - end - - def render_new_user - render json: TestUser.new('pete', false), serializer: UserSerializer - end - end - - tests UserTestController - - def test_default_scope_name - get :render_new_user - assert_equal '{"user":{"admin":false}}', @response.body - end -end - -class SerializationScopeNameTest < ActionController::TestCase - TestUser = Struct.new(:name, :admin) - - class AdminUserSerializer < ActiveModel::Serializer - attributes :admin? - def admin? - current_admin.admin - end - end - - class AdminUserTestController < ActionController::Base - protect_from_forgery - - serialization_scope :current_admin - before_filter { request.format = :json } - - def current_admin - TestUser.new('Bob', true) - end - - def render_new_user - render json: TestUser.new('pete', false), serializer: AdminUserSerializer - end - end - - tests AdminUserTestController - - def test_override_scope_name_with_controller - get :render_new_user - assert_equal '{"admin_user":{"admin":true}}', @response.body - end -end - -class SerializationActionScopeOverrideTest < ActionController::TestCase - TestUser = Struct.new(:name, :admin) - - class AdminUserSerializer < ActiveModel::Serializer - attributes :admin? - def admin? - current_admin.admin - end - end - - class AdminUserTestController < ActionController::Base - protect_from_forgery - before_filter { request.format = :json } - - def current_admin - TestUser.new('Bob', true) - end - - def render_new_user - render json: TestUser.new('pete', false), serializer: AdminUserSerializer, scope: current_admin, scope_name: :current_admin - end - end - - tests AdminUserTestController - - def test_override_scope_name_with_controller - get :render_new_user - assert_equal '{"admin_user":{"admin":true}}', @response.body - end - -end diff --git a/test/serialization_test.rb b/test/serialization_test.rb deleted file mode 100644 index 6fe5075c..00000000 --- a/test/serialization_test.rb +++ /dev/null @@ -1,394 +0,0 @@ -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, options={}) - @object, @options = object, options - end - - def as_json(*) - hash = { object: serializable_hash, scope: @options[:scope].as_json } - hash.merge!(options: true) if @options[:options] - hash.merge!(check_defaults: true) if @options[:check_defaults] - hash - end - - def serializable_hash - @object.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 CustomSerializer - def initialize(*) - end - - def as_json(*) - { hello: true } - end - end - - class AnotherCustomSerializer - def initialize(*) - end - - def as_json(*) - { rails: 'rocks' } - end - end - - class DummyCustomSerializer < ActiveModel::Serializer - attributes :id - end - - class HypermediaSerializable - def active_model_serializer - HypermediaSerializer - end - end - - class HypermediaSerializer < ActiveModel::Serializer - def as_json(*) - { link: hypermedia_url } - end - end - - class CustomArraySerializer < ActiveModel::ArraySerializer - self.root = "items" - 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_nil_with_custom_serializer - render json: nil, serializer: DummyCustomSerializer - 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_and_implicit_root - @current_user = Struct.new(:as_json).new(current_user: true) - render json: [JsonSerializable.new] - end - - def render_json_with_serializer_and_options - @current_user = Struct.new(:as_json).new(current_user: true) - render json: JsonSerializable.new, options: true - end - - def render_json_with_serializer_and_scope_option - @current_user = Struct.new(:as_json).new(current_user: true) - scope = Struct.new(:as_json).new(current_user: false) - render json: JsonSerializable.new, scope: scope - end - - def render_json_with_serializer_api_but_without_serializer - @current_user = Struct.new(:as_json).new(current_user: true) - render json: JsonSerializable.new(true) - end - - # To specify a custom serializer for an object, use :serializer. - def render_json_with_custom_serializer - render json: Object.new, serializer: CustomSerializer - end - - # To specify a custom serializer for each item in the Array, use :each_serializer. - def render_json_array_with_custom_serializer - render json: [Object.new], each_serializer: CustomSerializer - end - - def render_json_array_with_wrong_option - render json: [Object.new], serializer: CustomSerializer - end - - def render_json_with_links - render json: HypermediaSerializable.new - end - - def render_json_array_with_no_root - render json: [], root: false - end - - def render_json_empty_array - render json: [] - end - - def render_json_array_with_custom_array_serializer - render json: [], serializer: CustomArraySerializer - end - - - private - def default_serializer_options - defaults = {} - defaults.merge!(check_defaults: true) if params[:check_defaults] - defaults.merge!(root: :awesome) if params[:check_default_root] - defaults.merge!(scope: :current_admin) if params[:check_default_scope] - defaults.merge!(serializer: AnotherCustomSerializer) if params[:check_default_serializer] - defaults.merge!(each_serializer: AnotherCustomSerializer) if params[:check_default_each_serializer] - defaults - 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_nil_with_custom_serializer - get :render_json_nil_with_custom_serializer - assert_equal "{\"dummy_custom\":null}", @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 - # For JSONP, Rails 3 uses application/json, but Rails 4 uses text/javascript - assert_match %r(application/json|text/javascript), @response.content_type.to_s - 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_checking_defaults - get :render_json_with_serializer, check_defaults: true - assert_match '"scope":{"current_user":true}', @response.body - assert_match '"object":{"serializable_object":true}', @response.body - assert_match '"check_defaults":true', @response.body - end - - def test_render_json_with_serializer_checking_default_serailizer - get :render_json_with_serializer, check_default_serializer: true - assert_match '{"rails":"rocks"}', @response.body - end - - def test_render_json_with_serializer_checking_default_scope - get :render_json_with_serializer, check_default_scope: true - assert_match '"scope":"current_admin"', @response.body - end - - def test_render_json_with_serializer_and_implicit_root - get :render_json_with_serializer_and_implicit_root - assert_match '"test":[{"serializable_object":true}]', @response.body - end - - def test_render_json_with_serializer_and_implicit_root_checking_default_each_serailizer - get :render_json_with_serializer_and_implicit_root, check_default_each_serializer: true - assert_match '"test":[{"rails":"rocks"}]', @response.body - end - - def test_render_json_with_serializer_and_options - get :render_json_with_serializer_and_options - assert_match '"scope":{"current_user":true}', @response.body - assert_match '"object":{"serializable_object":true}', @response.body - assert_match '"options":true', @response.body - end - - def test_render_json_with_serializer_and_scope_option - get :render_json_with_serializer_and_scope_option - assert_match '"scope":{"current_user":false}', @response.body - end - - def test_render_json_with_serializer_and_scope_option_checking_default_scope - get :render_json_with_serializer_and_scope_option, check_default_scope: true - assert_match '"scope":{"current_user":false}', @response.body - end - - def test_render_json_with_serializer_api_but_without_serializer - get :render_json_with_serializer_api_but_without_serializer - assert_match '{"serializable_object":true}', @response.body - end - - def test_render_json_with_custom_serializer - get :render_json_with_custom_serializer - assert_match '{"hello":true}', @response.body - end - - def test_render_json_with_custom_serializer_checking_default_serailizer - get :render_json_with_custom_serializer, check_default_serializer: true - assert_match '{"hello":true}', @response.body - end - - def test_render_json_array_with_custom_serializer - get :render_json_array_with_custom_serializer - assert_match '{"test":[{"hello":true}]}', @response.body - end - - def test_render_json_array_with_wrong_option - assert_raise ArgumentError do - get :render_json_array_with_wrong_option - end - end - - def test_render_json_array_with_custom_serializer_checking_default_each_serailizer - get :render_json_array_with_custom_serializer, check_default_each_serializer: true - assert_match '{"test":[{"hello":true}]}', @response.body - end - - def test_render_json_with_links - get :render_json_with_links - assert_match '{"link":"http://www.nextangle.com/hypermedia"}', @response.body - end - - def test_render_json_array_with_no_root - get :render_json_array_with_no_root - assert_equal '[]', @response.body - end - - def test_render_json_array_with_no_root_checking_default_root - get :render_json_array_with_no_root, check_default_root: true - assert_equal '[]', @response.body - end - - def test_render_json_empty_array - get :render_json_empty_array - assert_equal '{"test":[]}', @response.body - end - - def test_render_json_empty_array_checking_default_root - get :render_json_empty_array, check_default_root: true - assert_equal '{"awesome":[]}', @response.body - end - - def test_render_json_empty_array_with_array_serializer_root_false - ActiveModel::ArraySerializer.root = false - get :render_json_empty_array - assert_equal '[]', @response.body - ensure # teardown - ActiveModel::ArraySerializer.root = nil - end - - def test_render_json_array_with_custom_array_serializer - get :render_json_array_with_custom_array_serializer - assert_equal '{"items":[]}', @response.body - end - -end diff --git a/test/serializer_support_test.rb b/test/serializer_support_test.rb deleted file mode 100644 index 03bd130f..00000000 --- a/test/serializer_support_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require "test_helper" - -class RandomModel - include ActiveModel::SerializerSupport -end - -class OtherRandomModel - include ActiveModel::SerializerSupport -end - -class OtherRandomModelSerializer -end - -class RandomModelCollection - include ActiveModel::ArraySerializerSupport -end - -module ActiveRecord - class Relation - end -end - -module Mongoid - class Criteria - end -end - -class SerializerSupportTest < ActiveModel::TestCase - test "it returns nil if no serializer exists" do - assert_equal nil, RandomModel.new.active_model_serializer - end - - test "it returns a deducted serializer if it exists exists" do - assert_equal OtherRandomModelSerializer, OtherRandomModel.new.active_model_serializer - end - - test "it returns ArraySerializer for a collection" do - assert_equal ActiveModel::ArraySerializer, RandomModelCollection.new.active_model_serializer - end - - test "it automatically includes array_serializer in active_record/relation" do - ActiveSupport.run_load_hooks(:active_record) - assert_equal ActiveModel::ArraySerializer, ActiveRecord::Relation.new.active_model_serializer - end - - test "it automatically includes array_serializer in mongoid/criteria" do - ActiveSupport.run_load_hooks(:mongoid) - assert_equal ActiveModel::ArraySerializer, Mongoid::Criteria.new.active_model_serializer - end -end - diff --git a/test/serializer_test.rb b/test/serializer_test.rb deleted file mode 100644 index 5da28d8a..00000000 --- a/test/serializer_test.rb +++ /dev/null @@ -1,1521 +0,0 @@ -require "test_helper" -require "test_fakes" - -class SerializerTest < ActiveModel::TestCase - def test_scope_works_correct - serializer = ActiveModel::Serializer.new :foo, scope: :bar - assert_equal serializer.scope, :bar - 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, scope: {}) - - hash = user_serializer.as_json - - assert_equal({ - user: { first_name: "Jose", last_name: "Valim", ok: true } - }, hash) - end - - def test_attributes_method_specifying_keys - user = User.new - user_serializer = UserAttributesWithKeySerializer.new(user, scope: {}) - - hash = user_serializer.as_json - - assert_equal({ - user_attributes_with_key: { f_name: "Jose", l_name: "Valim", ok: true } - }, hash) - end - - def test_attributes_method_specifying_some_keys - user = User.new - user_serializer = UserAttributesWithSomeKeySerializer.new(user, scope: {}) - - hash = user_serializer.as_json - - assert_equal({ - user_attributes_with_some_key: { first_name: "Jose", l_name: "Valim", ok: true } - }, hash) - end - - def test_attributes_method_with_unsymbolizable_key - user = User.new - user_serializer = UserAttributesWithUnsymbolizableKeySerializer.new(user, scope: {}) - - hash = user_serializer.as_json - - assert_equal({ - user_attributes_with_unsymbolizable_key: { first_name: "Jose", :"last-name" => "Valim", ok: true } - }, hash) - end - - def test_attribute_method_with_name_as_serializer_prefix - object = SomeObject.new("something") - object_serializer = SomeSerializer.new(object, {}) - - hash = object_serializer.as_json - - assert_equal({ - some: { some: "something" } - }, hash) - end - - def test_serializer_receives_scope - user = User.new - user_serializer = UserSerializer.new(user, scope: { scope: true }) - - hash = user_serializer.as_json - - assert_equal({ - user: { - first_name: "Jose", - last_name: "Valim", - ok: true, - scope: true - } - }, hash) - end - - def test_serializer_receives_url_options - user = User.new - user_serializer = UserSerializer.new(user, url_options: { host: "test.local" }) - assert_equal({ host: "test.local" }, user_serializer.url_options) - end - - def test_serializer_returns_empty_hash_without_url_options - user = User.new - user_serializer = UserSerializer.new(user) - assert_equal({}, user_serializer.url_options) - end - - def test_pretty_accessors - user = User.new - user.superuser = true - user_serializer = MyUserSerializer.new(user) - - 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, scope: user) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ] - } - }, post_serializer.as_json) - end - - def test_conditionally_included_associations - 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 = PostWithConditionalCommentsSerializer.new(post, scope: user) - - # comments enabled - post.comments_disabled = false - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ] - } - }, post_serializer.as_json) - - # comments disabled - post.comments_disabled = true - assert_equal({ - post: { - title: "New Post", - body: "Body of new post" - } - }, post_serializer.as_json) - end - - def test_conditionally_included_associations_and_attributes - user = User.new - - post = Post.new(title: "New Post", body: "Body of new post", author: 'Sausage King', email: "tenderlove@tenderlove.com") - comments = [Comment.new(title: "Comment1"), Comment.new(title: "Comment2")] - post.comments = comments - - post_serializer = PostWithMultipleConditionalsSerializer.new(post, scope: user) - - # comments enabled - post.comments_disabled = false - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ] - } - }, post_serializer.as_json) - - # comments disabled - post.comments_disabled = true - assert_equal({ - post: { - title: "New Post", - body: "Body of new post" - } - }, post_serializer.as_json) - - # superuser - should see author - user.superuser = true - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - author: "Sausage King" - } - }, post_serializer.as_json) - end - - def test_has_one - user = User.new - blog = Blog.new - blog.author = user - - json = BlogSerializer.new(blog, scope: user).as_json - assert_equal({ - blog: { - author: { - first_name: "Jose", - last_name: "Valim" - } - } - }, json) - end - - def test_overridden_associations - author_serializer = Class.new(ActiveModel::Serializer) do - attributes :first_name - end - - blog_serializer = Class.new(ActiveModel::Serializer) do - def person - object.author - end - - has_one :person, serializer: author_serializer - end - - user = User.new - blog = Blog.new - blog.author = user - - json = blog_serializer.new(blog, scope: user).as_json - assert_equal({ - person: { - first_name: "Jose" - } - }, json) - end - - def post_serializer - Class.new(ActiveModel::Serializer) do - attributes :title, :body - has_many :comments, serializer: CommentSerializer - has_one :author, serializer: DefaultUserSerializer - end - end - - def test_associations_with_nil_association - user = User.new - blog = Blog.new - - json = BlogSerializer.new(blog, scope: user).as_json - assert_equal({ - blog: { author: nil } - }, json) - - serializer = Class.new(BlogSerializer) do - root :blog - end - - json = serializer.new(blog, scope: 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, scope: user).as_json) - end - - def test_nil_root_object - user = User.new - blog = nil - - serializer = Class.new(BlogSerializer) do - root false - end - - assert_equal(nil, serializer.new(blog, scope: user).as_json) - end - - def test_custom_root_with_nil_root_object - user = User.new - blog = nil - - serializer = Class.new(BlogSerializer) do - root :my_blog - end - - assert_equal({ my_blog: nil }, serializer.new(blog, scope: user).as_json) - end - - def test_false_root - user = User.new - blog = Blog.new - - serializer = Class.new(BlogSerializer) do - root false - end - - another_serializer = Class.new(BlogSerializer) do - self.root = false - end - - assert_equal({ author: nil }, serializer.new(blog, scope: user).as_json) - assert_equal({ author: nil }, another_serializer.new(blog, scope: user).as_json) - - # test inherited false root - serializer = Class.new(serializer) - assert_equal({ author: nil }, serializer.new(blog, scope: user).as_json) - end - - def test_true_root - blog = Blog.new - - assert_equal({ - blog_with_root: { - author: nil, - } - }, BlogWithRootSerializer.new(blog).as_json) - end - - def test_root_false_on_load_active_model_serializers - begin - ActiveSupport.on_load(:active_model_serializers) do - self.root = false - end - - blog = Blog.new - serializer = BlogSerializer.new(blog) - - assert_equal({ author: nil }, serializer.as_json) - ensure - ActiveSupport.on_load(:active_model_serializers) do - self.root = nil - end - end - end - - def test_embed_ids - serializer = post_serializer - - 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) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comment_ids: [1, 2], - author_id: nil - } - }, serializer.as_json) - end - - def test_embed_ids_include_true - serializer_class = post_serializer - - serializer_class.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_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comment_ids: [1, 2], - author_id: nil - }, - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ], - authors: [] - }, serializer.as_json) - - post.author = User.new(id: 1) - - serializer = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comment_ids: [1, 2], - author_id: 1 - }, - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ], - authors: [{ first_name: "Jose", last_name: "Valim" }] - }, serializer.as_json) - end - - def test_methods_take_priority_over_associations - post_serializer = Class.new(ActiveModel::Serializer) do - attributes :title - has_many :comments - embed :ids - - def comments - object.comments[0,1] - end - end - - post = Post.new(title: "My Post") - comments = [Comment.new(title: "Comment1", id: 1), Comment.new(title: "Comment2", id: 2)] - post.comments = comments - - post.class_eval do - define_method :comment_ids, lambda { - self.comments.map { |c| c.read_attribute_for_serialization(:id) } - } - end - json = post_serializer.new(post).as_json - assert_equal({ - title: "My Post", - comment_ids: [1] - }, json) - end - - def test_methods_take_priority_over_associations_and_call_the_appropriate_id_method - comment_serializer = Class.new(ActiveModel::Serializer) do - def id - "OMG" - end - end - - post_serializer = Class.new(ActiveModel::Serializer) do - attributes :title - has_many :comments, serializer: comment_serializer - embed :ids - - def comments - object.comments[0,1] - end - end - - post = Post.new(title: "My Post") - comments = [Comment.new(title: "Comment1", id: 1), Comment.new(title: "Comment2", id: 2)] - post.comments = comments - - post.class_eval do - define_method :comment_ids, lambda { - self.comments.map { |c| c.read_attribute_for_serialization(:id) } - } - end - json = post_serializer.new(post).as_json - assert_equal({ - title: "My Post", - comment_ids: ["OMG"] - }, json) - end - - def test_embed_objects - serializer = post_serializer - - 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) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - author: nil, - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ] - } - }, serializer.as_json) - end - - def test_sets_can_be_serialized - post1 = Post.new(title: "Post1", author: "Author1", id: 1) - post2 = Post.new(title: "Post2", author: "Author2", id: 2) - - set = Set.new - set << post1 - set << post2 - - serializer = set.active_model_serializer.new set, each_serializer: CustomPostSerializer - - as_json = serializer.as_json - assert_equal 2, as_json.size - assert as_json.include?({ title: "Post1" }) - assert as_json.include?({ title: "Post2" }) - end - - def test_associations_with_as - posts = [ - Post.new(title: 'First Post', body: 'text'), - Post.new(title: 'Second Post', body: 'text') - ] - user = User.new - - custom_blog = CustomBlog.new - custom_blog.public_posts = posts - custom_blog.public_user = user - - serializer = CustomBlogSerializer.new(custom_blog, scope: { scope: true }) - - assert_equal({ - custom_blog: { - posts: [ - {title: 'First Post', body: 'text', comments: []}, - {title: 'Second Post', body: 'text', comments: []} - ], - user: { - first_name: "Jose", - last_name: "Valim", ok: true, - scope: true - } - } - }, serializer.as_json) - end - - def test_implicity_detection_for_association_serializers - implicit_serializer = Class.new(ActiveModel::Serializer) do - root :custom_blog - const_set(:UserSerializer, UserSerializer) - const_set(:PostSerializer, PostSerializer) - - has_many :public_posts, key: :posts - has_one :public_user, key: :user - end - - posts = [ - Post.new(title: 'First Post', body: 'text', comments: []), - Post.new(title: 'Second Post', body: 'text', comments: []) - ] - user = User.new - - custom_blog = CustomBlog.new - custom_blog.public_posts = posts - custom_blog.public_user = user - - serializer = implicit_serializer.new(custom_blog, scope: { scope: true }) - - assert_equal({ - custom_blog: { - posts: [ - {title: 'First Post', body: 'text', comments: []}, - {title: 'Second Post', body: 'text', comments: []} - ], - user: { - first_name: "Jose", - last_name: "Valim", ok: true, - scope: true - } - } - }, serializer.as_json) - end - - def test_attribute_key - serializer_class = Class.new(ActiveModel::Serializer) do - root :user - - attribute :first_name, key: :firstName - attribute :last_name, key: :lastName - attribute :password - end - - serializer = serializer_class.new(User.new) - - assert_equal({ - user: { - firstName: "Jose", - lastName: "Valim", - password: "oh noes yugive my password" - } - }, serializer.as_json) - end - - def setup_model - Class.new do - class << self - def columns_hash - { "name" => Struct.new(:type).new(:string), "age" => Struct.new(:type).new(:integer) } - end - - def reflect_on_association(name) - case name - when :posts - Struct.new(:macro, :name).new(:has_many, :posts) - when :parent - Struct.new(:macro, :name).new(:belongs_to, :parent) - end - end - end - end - end - - def test_schema - model = setup_model - - serializer = Class.new(ActiveModel::Serializer) do - class << self; self; end.class_eval do - define_method(:model_class) do model end - end - - # Computed attributes (not real columns or associations). - def can_edit; end - def can_view; end - def drafts; end - - attributes :name, :age, { can_edit: :boolean }, :can_view - has_many :posts, serializer: Class.new - has_many :drafts, serializer: Class.new - has_one :parent, serializer: Class.new - end - - assert_equal serializer.schema, { - attributes: { name: :string, age: :integer, can_edit: :boolean, can_view: nil }, - associations: { - posts: { has_many: :posts }, - drafts: nil, - parent: { belongs_to: :parent } - } - } - end - - def test_schema_with_as - model = setup_model - - serializer = Class.new(ActiveModel::Serializer) do - class << self; self; end.class_eval do - define_method(:model_class) do model end - end - - attributes :name, :age - has_many :posts, key: :my_posts, serializer: Class.new - has_one :parent, key: :my_parent, serializer: Class.new - end - - assert_equal serializer.schema, { - attributes: { name: :string, age: :integer }, - associations: { - my_posts: { has_many: :posts }, - my_parent: { belongs_to: :parent } - } - } - end - - def test_embed_id_for_has_one - author_serializer = Class.new(ActiveModel::Serializer) - - serializer_class = Class.new(ActiveModel::Serializer) do - embed :ids - root :post - - attributes :title, :body - has_one :author, serializer: author_serializer - end - - post_class = Class.new(Model) do - attr_accessor :author - end - - author_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "It's a new post!") - author = author_class.new(id: 5) - post.author = author - - hash = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "It's a new post!", - author_id: 5 - } - }, hash.as_json) - end - - def test_embed_id_for_has_one_overriding_associated_id - author_serializer = Class.new(ActiveModel::Serializer) do - def id - "OMG" - end - end - - serializer_class = Class.new(ActiveModel::Serializer) do - embed :ids - root :post - - attributes :title, :body - has_one :author, serializer: author_serializer - end - - post_class = Class.new(Model) do - attr_accessor :author - end - - author_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "It's a new post!") - author = author_class.new(id: 5) - post.author = author - - hash = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "It's a new post!", - author_id: "OMG" - } - }, hash.as_json) - end - - def test_embed_objects_for_has_one - author_serializer = Class.new(ActiveModel::Serializer) do - attributes :id, :name - end - - serializer_class = Class.new(ActiveModel::Serializer) do - root :post - - attributes :title, :body - has_one :author, serializer: author_serializer - end - - post_class = Class.new(Model) do - attr_accessor :author - end - - author_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "It's a new post!") - author = author_class.new(id: 5, name: "Tom Dale") - post.author = author - - hash = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "It's a new post!", - author: { id: 5, name: "Tom Dale" } - } - }, hash.as_json) - end - - def test_root_provided_in_options - author_serializer = Class.new(ActiveModel::Serializer) do - attributes :id, :name - end - - serializer_class = Class.new(ActiveModel::Serializer) do - root :post - - attributes :title, :body - has_one :author, serializer: author_serializer - end - - post_class = Class.new(Model) do - attr_accessor :author - end - - author_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "It's a new post!") - author = author_class.new(id: 5, name: "Tom Dale") - post.author = author - - assert_equal({ - blog_post: { - title: "New Post", - body: "It's a new post!", - author: { id: 5, name: "Tom Dale" } - } - }, serializer_class.new(post, root: :blog_post).as_json) - - assert_equal({ - title: "New Post", - body: "It's a new post!", - author: { id: 5, name: "Tom Dale" } - }, serializer_class.new(post, root: false).as_json) - - assert_equal({ - blog_post: { - title: "New Post", - body: "It's a new post!", - author: { id: 5, name: "Tom Dale" } - } - }, serializer_class.new(post).as_json(root: :blog_post)) - - assert_equal({ - title: "New Post", - body: "It's a new post!", - author: { id: 5, name: "Tom Dale" } - }, serializer_class.new(post).as_json(root: false)) - end - - def test_serializer_has_access_to_root_object - hash_object = nil - - author_serializer = Class.new(ActiveModel::Serializer) do - attributes :id, :name - - define_method :serializable_hash do - hash_object = @options[:hash] - super() - end - end - - serializer_class = Class.new(ActiveModel::Serializer) do - root :post - - attributes :title, :body - has_one :author, serializer: author_serializer - end - - post_class = Class.new(Model) do - attr_accessor :author - end - - author_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "It's a new post!") - author = author_class.new(id: 5, name: "Tom Dale") - post.author = author - - expected = serializer_class.new(post).as_json - assert_equal expected, hash_object - end - - def test_embed_ids_include_true_with_root - serializer_class = post_serializer - - serializer_class.class_eval do - root :post - embed :ids, include: true - has_many :comments, key: :comment_ids, root: :comments - has_one :author, serializer: DefaultUserSerializer, key: :author_id, root: :author - 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_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comment_ids: [1, 2], - author_id: nil - }, - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ], - author: [] - }, serializer.as_json) - - post.author = User.new(id: 1) - - serializer = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comment_ids: [1, 2], - author_id: 1 - }, - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ], - author: [{ first_name: "Jose", last_name: "Valim" }] - }, serializer.as_json) - end - - # the point of this test is to illustrate that deeply nested serializers - # still side-load at the root. - def test_embed_with_include_inserts_at_root - tag_serializer = Class.new(ActiveModel::Serializer) do - attributes :id, :name - end - - comment_serializer = Class.new(ActiveModel::Serializer) do - embed :ids, include: true - attributes :id, :body - has_many :tags, serializer: tag_serializer - end - - post_serializer = Class.new(ActiveModel::Serializer) do - embed :ids, include: true - attributes :id, :title, :body - has_many :comments, serializer: comment_serializer - end - - post_class = Class.new(Model) do - attr_accessor :comments - - define_method :active_model_serializer do - post_serializer - end - end - - comment_class = Class.new(Model) do - attr_accessor :tags - end - - tag_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "NEW POST", id: 1) - comment1 = comment_class.new(body: "EWOT", id: 1) - comment2 = comment_class.new(body: "YARLY", id: 2) - tag1 = tag_class.new(name: "lolcat", id: 1) - tag2 = tag_class.new(name: "nyancat", id: 2) - tag3 = tag_class.new(name: "violetcat", id: 3) - - post.comments = [comment1, comment2] - comment1.tags = [tag1, tag3] - comment2.tags = [tag1, tag2] - - actual = ActiveModel::ArraySerializer.new([post], root: :posts).as_json - assert_equal({ - posts: [ - { title: "New Post", body: "NEW POST", id: 1, comment_ids: [1,2] } - ], - - comments: [ - { body: "EWOT", id: 1, tag_ids: [1,3] }, - { body: "YARLY", id: 2, tag_ids: [1,2] } - ], - - tags: [ - { name: "lolcat", id: 1 }, - { name: "violetcat", id: 3 }, - { name: "nyancat", id: 2 } - ] - }, actual) - end - - def test_can_customize_attributes - serializer = Class.new(ActiveModel::Serializer) do - attributes :title, :body - - def title - object.title.upcase - end - end - - klass = Class.new do - def read_attribute_for_serialization(name) - { title: "New post!", body: "First post body" }[name] - end - - def title - read_attribute_for_serialization(:title) - end - - def body - read_attribute_for_serialization(:body) - end - end - - object = klass.new - - actual = serializer.new(object, root: :post).as_json - - assert_equal({ - post: { - title: "NEW POST!", - body: "First post body" - } - }, actual) - end - - def test_can_customize_attributes_with_read_attributes - serializer = Class.new(ActiveModel::Serializer) do - attributes :title, :body - - def read_attribute_for_serialization(name) - { title: "New post!", body: "First post body" }[name] - end - end - - actual = serializer.new(Object.new, root: :post).as_json - - assert_equal({ - post: { - title: "New post!", - body: "First post body" - } - }, actual) - end - - def test_active_support_on_load_hooks_fired - loaded = nil - ActiveSupport.on_load(:active_model_serializers) do - loaded = self - end - assert_equal ActiveModel::Serializer, loaded - end - - def tests_query_attributes_strip_question_mark - todo = Class.new do - def overdue? - true - end - - def read_attribute_for_serialization(name) - send name - end - end - - serializer = Class.new(ActiveModel::Serializer) do - attribute :overdue? - end - - actual = serializer.new(todo.new).as_json - - assert_equal({ - overdue: true - }, actual) - end - - def tests_query_attributes_allow_key_option - todo = Class.new do - def overdue? - true - end - - def read_attribute_for_serialization(name) - send name - end - end - - serializer = Class.new(ActiveModel::Serializer) do - attribute :overdue?, key: :foo - end - - actual = serializer.new(todo.new).as_json - - assert_equal({ - foo: true - }, actual) - end - - def tests_can_handle_polymorphism - email_serializer = Class.new(ActiveModel::Serializer) do - attributes :subject, :body - end - - email_class = Class.new(Model) do - def self.to_s - "Email" - end - - define_method :active_model_serializer do - email_serializer - end - end - - attachment_serializer = Class.new(ActiveModel::Serializer) do - attributes :name, :url - has_one :attachable, polymorphic: true - end - - email = email_class.new subject: 'foo', body: 'bar' - - attachment = Attachment.new name: 'logo.png', url: 'http://example.com/logo.png', attachable: email - - actual = attachment_serializer.new(attachment, {}).as_json - - assert_equal({ - name: 'logo.png', - url: 'http://example.com/logo.png', - attachable: { - type: :email, - email: { subject: 'foo', body: 'bar' } - } - }, actual) - end - - def test_can_handle_polymoprhic_ids - email_serializer = Class.new(ActiveModel::Serializer) do - attributes :subject, :body - end - - email_class = Class.new(Model) do - def self.to_s - "Email" - end - - define_method :active_model_serializer do - email_serializer - end - end - - attachment_serializer = Class.new(ActiveModel::Serializer) do - embed :ids - attributes :name, :url - has_one :attachable, polymorphic: true - end - - email = email_class.new id: 1 - - attachment = Attachment.new name: 'logo.png', url: 'http://example.com/logo.png', attachable: email - - actual = attachment_serializer.new(attachment, {}).as_json - - assert_equal({ - name: 'logo.png', - url: 'http://example.com/logo.png', - attachable: { - type: :email, - id: 1 - } - }, actual) - end - - def test_polymorphic_associations_are_included_at_root - email_serializer = Class.new(ActiveModel::Serializer) do - attributes :subject, :body, :id - end - - email_class = Class.new(Model) do - def self.to_s - "Email" - end - - define_method :active_model_serializer do - email_serializer - end - end - - attachment_serializer = Class.new(ActiveModel::Serializer) do - root :attachment - embed :ids, include: true - attributes :name, :url - has_one :attachable, polymorphic: true - end - - email = email_class.new id: 1, subject: "Hello", body: "World" - - attachment = Attachment.new name: 'logo.png', url: 'http://example.com/logo.png', attachable: email - - actual = attachment_serializer.new(attachment, {}).as_json - - assert_equal({ - attachment: { - name: 'logo.png', - url: 'http://example.com/logo.png', - attachable: { - type: :email, - id: 1 - }}, - emails: [{ - id: 1, - subject: "Hello", - body: "World" - }] - }, actual) - end - - def test_multiple_polymorphic_associations - email_serializer = Class.new(ActiveModel::Serializer) do - attributes :subject, :body, :id - end - - orange_serializer = Class.new(ActiveModel::Serializer) do - embed :ids, include: true - - attributes :plu, :id - has_one :readable, polymorphic: true - end - - email_class = Class.new(Model) do - def self.to_s - "Email" - end - - define_method :active_model_serializer do - email_serializer - end - end - - orange_class = Class.new(Model) do - def self.to_s - "Orange" - end - - def readable - @attributes[:readable] - end - - define_method :active_model_serializer do - orange_serializer - end - end - - attachment_serializer = Class.new(ActiveModel::Serializer) do - root :attachment - embed :ids, include: true - - attributes :name, :url - - has_one :attachable, polymorphic: true - has_one :readable, polymorphic: true - has_one :edible, polymorphic: true - end - - email = email_class.new id: 1, subject: "Hello", body: "World" - orange = orange_class.new id: 1, plu: "3027", readable: email - - attachment = Attachment.new({ - name: 'logo.png', - url: 'http://example.com/logo.png', - attachable: email, - readable: email, - edible: orange - }) - - actual = attachment_serializer.new(attachment, {}).as_json - - assert_equal({ - emails: [{ - subject: "Hello", - body: "World", - id: 1 - }], - - oranges: [{ - plu: "3027", - id: 1, - readable: { type: :email, id: 1 } - }], - - attachment: { - name: 'logo.png', - url: 'http://example.com/logo.png', - attachable: { type: :email, id: 1 }, - readable: { type: :email, id: 1 }, - edible: { type: :orange, id: 1 } - } - }, actual) - end - - def test_raises_an_error_when_a_child_serializer_includes_associations_when_the_source_doesnt - attachment_serializer = Class.new(ActiveModel::Serializer) do - attributes :name - end - - fruit_serializer = Class.new(ActiveModel::Serializer) do - embed :ids, include: true - has_one :attachment, serializer: attachment_serializer - attribute :color - end - - banana_class = Class.new Model do - def self.to_s - 'banana' - end - - def attachment - @attributes[:attachment] - end - - define_method :active_model_serializer do - fruit_serializer - end - end - - strawberry_class = Class.new Model do - def self.to_s - 'strawberry' - end - - def attachment - @attributes[:attachment] - end - - define_method :active_model_serializer do - fruit_serializer - end - end - - smoothie = Class.new do - attr_reader :base, :flavor - - def initialize(base, flavor) - @base, @flavor = base, flavor - end - end - - smoothie_serializer = Class.new(ActiveModel::Serializer) do - root false - embed :ids, include: true - - has_one :base, polymorphic: true - has_one :flavor, polymorphic: true - end - - banana_attachment = Attachment.new({ - name: 'banana_blending.md', - id: 3, - }) - - strawberry_attachment = Attachment.new({ - name: 'strawberry_cleaning.doc', - id: 4 - }) - - banana = banana_class.new color: "yellow", id: 1, attachment: banana_attachment - strawberry = strawberry_class.new color: "red", id: 2, attachment: strawberry_attachment - - smoothie = smoothie_serializer.new(smoothie.new(banana, strawberry)) - - assert_raise ActiveModel::Serializer::IncludeError do - smoothie.as_json - end - end - - def tests_includes_does_not_include_nil_polymoprhic_associations - post_serializer = Class.new(ActiveModel::Serializer) do - root :post - embed :ids, include: true - has_one :author, polymorphic: true - attributes :title - end - - post = Post.new(title: 'Foo') - - actual = post_serializer.new(post).as_json - - assert_equal({ - post: { - title: 'Foo', - author: nil - } - }, actual) - end - - def test_meta_key_serialization - tag_serializer = Class.new(ActiveModel::Serializer) do - attributes :name - end - - tag_class = Class.new(Model) do - def name - @attributes[:name] - end - - define_method :active_model_serializer do - tag_serializer - end - end - - serializable_array = Class.new(Array) - - array = serializable_array.new - array << tag_class.new(name: 'Rails') - array << tag_class.new(name: 'Sinatra') - - actual = array.active_model_serializer.new(array, root: :tags, meta: {total: 10}).as_json - - assert_equal({ - meta: { - total: 10, - }, - tags: [ - { name: "Rails" }, - { name: "Sinatra" }, - ] - }, actual) - - actual = array.active_model_serializer.new(array, root: :tags, meta: {total: 10}, meta_key: 'meta_object').as_json - - assert_equal({ - meta_object: { - total: 10, - }, - tags: [ - { name: "Rails" }, - { name: "Sinatra" }, - ] - }, actual) - end - - def test_inheritance_does_not_used_cached_attributes - parent = Class.new(ActiveModel::Serializer) do - attributes :title - end - - child = Class.new(parent) do - attributes :body - end - - data_class = Class.new do - attr_accessor :title, :body - end - - item = data_class.new - item.title = "title" - item.body = "body" - - 2.times do - assert_equal({title: "title"}, - parent.new(item).attributes) - assert_equal({body: "body", title: "title"}, - child.new(item).attributes) - end - - end - - def test_scope_name_method - serializer = Class.new(ActiveModel::Serializer) do - def has_permission? - current_user.super_user? - end - end - - user = User.new - user.superuser = true - post = Post.new(title: 'Foo') - - a_serializer = serializer.new(post, scope: user, scope_name: :current_user) - assert a_serializer.has_permission? - end - - def test_only_option_filters_attributes_and_associations - post = Post.new(title: "New Post", body: "Body of new post") - comments = [Comment.new(title: "Comment1")] - post.comments = comments - - post_serializer = PostSerializer.new(post, only: :title) - - assert_equal({ - post: { - title: "New Post" - } - }, post_serializer.as_json) - end - - def test_except_option_filters_attributes_and_associations - post = Post.new(title: "New Post", body: "Body of new post") - comments = [Comment.new(title: "Comment1")] - post.comments = comments - - post_serializer = PostSerializer.new(post, except: [:body, :comments]) - - assert_equal({ - post: { - title: "New Post" - } - }, post_serializer.as_json) - end - - def test_only_option_takes_precedence_over_custom_defined_include_methods - user = User.new - - post = Post.new(title: "New Post", body: "Body of new post", author: "Sausage King") - comments = [Comment.new(title: "Comment")] - post.comments = comments - - post_serializer = PostWithMultipleConditionalsSerializer.new(post, scope: user, only: :title) - - # comments enabled - post.comments_disabled = false - # superuser - should see author - user.superuser = true - - assert_equal({ - post: { - title: "New Post" - } - }, post_serializer.as_json) - end -end diff --git a/test/test_app.rb b/test/test_app.rb new file mode 100644 index 00000000..8b702e51 --- /dev/null +++ b/test/test_app.rb @@ -0,0 +1,8 @@ +class TestApp < Rails::Application + if Rails.version.to_s.first >= '4' + config.eager_load = false + config.secret_key_base = 'abc123' + end +end + +TestApp.load_generators diff --git a/test/test_fakes.rb b/test/test_fakes.rb deleted file mode 100644 index a0a244c1..00000000 --- a/test/test_fakes.rb +++ /dev/null @@ -1,202 +0,0 @@ -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 ModelWithActiveModelSerializer < Model - include ActiveModel::Serializers::JSON - attr_accessor :attributes - def read_attribute_for_serialization(name) - @attributes[name] - end -end - -class User - include ActiveModel::SerializerSupport - - 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 - def initialize(attributes) - super(attributes) - self.comments ||= [] - self.comments_disabled = false - self.author = nil - end - - attr_accessor :comments, :comments_disabled, :author - 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(options[:scope]) - end -end - -class UserAttributesWithKeySerializer < ActiveModel::Serializer - attributes first_name: :f_name, last_name: :l_name - - def serializable_hash - attributes.merge(ok: true).merge(options[:scope]) - end -end - -class UserAttributesWithSomeKeySerializer < ActiveModel::Serializer - attributes :first_name, last_name: :l_name - - def serializable_hash - attributes.merge(ok: true).merge(options[:scope]) - end -end - -class UserAttributesWithUnsymbolizableKeySerializer < ActiveModel::Serializer - attributes :first_name, last_name: :"last-name" - - def serializable_hash - attributes.merge(ok: true).merge(options[: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 object.super_user? - hash - end -end - -class CommentSerializer - def initialize(comment, options={}) - @object = comment - end - - attr_reader :object - - def serializable_hash - { title: @object.read_attribute_for_serialization(:title) } - end - - def as_json(options=nil) - options ||= {} - if options[:root] == false - serializable_hash - else - { comment: serializable_hash } - end - end -end - -class PostSerializer < ActiveModel::Serializer - attributes :title, :body - has_many :comments, serializer: CommentSerializer -end - -class PostWithConditionalCommentsSerializer < ActiveModel::Serializer - root :post - attributes :title, :body - has_many :comments, serializer: CommentSerializer - - def include_associations! - include! :comments unless object.comments_disabled - end -end - -class PostWithMultipleConditionalsSerializer < ActiveModel::Serializer - root :post - attributes :title, :body, :author - has_many :comments, serializer: CommentSerializer - - def include_comments? - !object.comments_disabled - end - - def include_author? - scope.super_user? - end -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 - -class BlogWithRootSerializer < BlogSerializer - root true -end - -class CustomPostSerializer < ActiveModel::Serializer - attributes :title -end - -class CustomBlog < Blog - attr_accessor :public_posts, :public_user -end - -class CustomBlogSerializer < ActiveModel::Serializer - has_many :public_posts, key: :posts, serializer: PostSerializer - has_one :public_user, key: :user, serializer: UserSerializer -end - -class SomeSerializer < ActiveModel::Serializer - attributes :some -end - -class SomeObject < Struct.new(:some) -end - -# Set up some classes for polymorphic testing -class Attachment < Model - def attachable - @attributes[:attachable] - end - - def readable - @attributes[:readable] - end - - def edible - @attributes[:edible] - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb index 889d7a60..d60b8bc6 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,41 +1,21 @@ -require "rubygems" -require "bundler/setup" - -require 'simplecov' -SimpleCov.start do - add_group "lib", "lib" - add_group "spec", "spec" -end - -require 'coveralls' -Coveralls.wear! - -require "pry" - -require "active_model_serializers" -require "active_support/json" -require "minitest/autorun" - -require 'rails' +require 'bundler/setup' +require 'coverage_setup' +require 'minitest/autorun' +require 'active_model_serializers' +require 'fixtures/poro' module TestHelper Routes = ActionDispatch::Routing::RouteSet.new Routes.draw do - resource :hypermedia get ':controller(/:action(/:id))' get ':controller(/:action)' end ActionController::Base.send :include, Routes.url_helpers - ActiveModel::Serializer.send :include, Routes.url_helpers end -ActiveSupport::TestCase.class_eval do - setup do - @routes = ::TestHelper::Routes +ActionController::TestCase.class_eval do + def setup + @routes = TestHelper::Routes end end - -class Object - undef_method :id if respond_to?(:id) -end diff --git a/test/unit/active_model/array_serializer/meta_test.rb b/test/unit/active_model/array_serializer/meta_test.rb new file mode 100644 index 00000000..08b3db06 --- /dev/null +++ b/test/unit/active_model/array_serializer/meta_test.rb @@ -0,0 +1,53 @@ +require 'test_helper' +require 'active_model/serializer' + +module ActiveModel + class ArraySerializer + class MetaTest < ActiveModel::TestCase + def setup + @profile1 = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @profile2 = Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }) + @serializer = ArraySerializer.new([@profile1, @profile2], root: 'profiles') + end + + def test_meta + @serializer.meta = { total: 10 } + + assert_equal({ + 'profiles' => [ + { + name: 'Name 1', + description: 'Description 1' + }, { + name: 'Name 2', + description: 'Description 2' + } + ], + meta: { + total: 10 + } + }, @serializer.as_json) + end + + def test_meta_using_meta_key + @serializer.meta_key = :my_meta + @serializer.meta = { total: 10 } + + assert_equal({ + 'profiles' => [ + { + name: 'Name 1', + description: 'Description 1' + }, { + name: 'Name 2', + description: 'Description 2' + } + ], + my_meta: { + total: 10 + } + }, @serializer.as_json) + end + end + end +end diff --git a/test/unit/active_model/array_serializer/root_test.rb b/test/unit/active_model/array_serializer/root_test.rb new file mode 100644 index 00000000..3453b7a2 --- /dev/null +++ b/test/unit/active_model/array_serializer/root_test.rb @@ -0,0 +1,102 @@ +require 'test_helper' + +module ActiveModel + class ArraySerializer + class RootAsOptionTest < ActiveModel::TestCase + def setup + @old_root = ArraySerializer._root + @profile1 = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @profile2 = Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }) + @serializer = ArraySerializer.new([@profile1, @profile2], root: :initialize) + end + + def teardown + ArraySerializer._root = @old_root + end + + def test_root_is_not_displayed_using_serializable_array + assert_equal([ + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } + ], @serializer.serializable_array) + end + + def test_root_using_as_json + assert_equal({ + initialize: [ + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } + ] + }, @serializer.as_json) + end + + def test_root_as_argument_takes_precedence + assert_equal({ + argument: [ + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } + ] + }, @serializer.as_json(root: :argument)) + end + + def test_using_false_root_in_initialize_takes_precedence + ArraySerializer._root = 'root' + @serializer = ArraySerializer.new([@profile1, @profile2], root: false) + + assert_equal([ + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } + ], @serializer.as_json) + end + end + + class RootInSerializerTest < ActiveModel::TestCase + def setup + @old_root = ArraySerializer._root + ArraySerializer._root = :in_serializer + @profile1 = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @profile2 = Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }) + @serializer = ArraySerializer.new([@profile1, @profile2]) + @rooted_serializer = ArraySerializer.new([@profile1, @profile2], root: :initialize) + end + + def teardown + ArraySerializer._root = @old_root + end + + def test_root_is_not_displayed_using_serializable_hash + assert_equal([ + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } + ], @serializer.serializable_array) + end + + def test_root_using_as_json + assert_equal({ + in_serializer: [ + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } + ] + }, @serializer.as_json) + end + + def test_root_in_initializer_takes_precedence + assert_equal({ + initialize: [ + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } + ] + }, @rooted_serializer.as_json) + end + + def test_root_as_argument_takes_precedence + assert_equal({ + argument: [ + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } + ] + }, @rooted_serializer.as_json(root: :argument)) + end + end + end +end diff --git a/test/unit/active_model/array_serializer/scope_test.rb b/test/unit/active_model/array_serializer/scope_test.rb new file mode 100644 index 00000000..b9c6034f --- /dev/null +++ b/test/unit/active_model/array_serializer/scope_test.rb @@ -0,0 +1,24 @@ +require 'test_helper' + +module ActiveModel + class ArraySerializer + class ScopeTest < ActiveModel::TestCase + def test_array_serializer_pass_options_to_items_serializers + array = [Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), + Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })] + serializer = ArraySerializer.new(array, scope: current_user) + + expected = [{ name: 'Name 1', description: 'Description 1 - user' }, + { name: 'Name 2', description: 'Description 2 - user' }] + + assert_equal expected, serializer.serializable_array + end + + private + + def current_user + 'user' + end + end + end +end diff --git a/test/unit/active_model/array_serializer/serialize_test.rb b/test/unit/active_model/array_serializer/serialize_test.rb new file mode 100644 index 00000000..01f2bfa7 --- /dev/null +++ b/test/unit/active_model/array_serializer/serialize_test.rb @@ -0,0 +1,45 @@ +require 'test_helper' + +module ActiveModel + class ArraySerializer + class SerializeTest < ActiveModel::TestCase + def setup + array = [1, 2, 3] + @serializer = ActiveModel::Serializer.serializer_for(array).new(array) + end + + def test_serializer_for_array_returns_appropriate_type + assert_kind_of ArraySerializer, @serializer + end + + def test_array_serializer_serializes_simple_objects + assert_equal [1, 2, 3], @serializer.serializable_array + assert_equal [1, 2, 3], @serializer.as_json + end + + def test_array_serializer_serializes_models + array = [Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), + Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })] + serializer = ArraySerializer.new(array) + + expected = [{ name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' }] + + assert_equal expected, serializer.serializable_array + assert_equal expected, serializer.as_json + end + + def test_array_serializers_each_serializer + array = [::Model.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), + ::Model.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })] + serializer = ArraySerializer.new(array, each_serializer: ProfileSerializer) + + expected = [{ name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' }] + + assert_equal expected, serializer.serializable_array + assert_equal expected, serializer.as_json + end + end + end +end diff --git a/test/unit/active_model/default_serializer_test.rb b/test/unit/active_model/default_serializer_test.rb new file mode 100644 index 00000000..4acafb78 --- /dev/null +++ b/test/unit/active_model/default_serializer_test.rb @@ -0,0 +1,15 @@ +require 'test_helper' + +module ActiveModel + class DefaultSerializer + class Test < ActiveModel::TestCase + def test_serialize_objects + assert_equal(nil, DefaultSerializer.new(nil).serializable_hash) + assert_equal(1, DefaultSerializer.new(1).serializable_hash) + assert_equal('hi', DefaultSerializer.new('hi').serializable_hash) + obj = Struct.new(:a, :b).new(1, 2) + assert_equal({ a: 1, b: 2 }, DefaultSerializer.new(obj).serializable_hash) + end + end + end +end diff --git a/test/unit/active_model/serializer/attributes_test.rb b/test/unit/active_model/serializer/attributes_test.rb new file mode 100644 index 00000000..0914747e --- /dev/null +++ b/test/unit/active_model/serializer/attributes_test.rb @@ -0,0 +1,29 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class AttributesTest < ActiveModel::TestCase + def setup + @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @profile_serializer = ProfileSerializer.new(@profile) + end + + def test_attributes_definition + assert_equal([:name, :description], + @profile_serializer.class._attributes) + end + + def test_attributes_serialization_using_serializable_hash + assert_equal({ + name: 'Name 1', description: 'Description 1' + }, @profile_serializer.serializable_hash) + end + + def test_attributes_serialization_using_as_json + assert_equal({ + 'profile' => { name: 'Name 1', description: 'Description 1' } + }, @profile_serializer.as_json) + end + end + end +end diff --git a/test/unit/active_model/serializer/filter_test.rb b/test/unit/active_model/serializer/filter_test.rb new file mode 100644 index 00000000..bf33b5e7 --- /dev/null +++ b/test/unit/active_model/serializer/filter_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class FilterAttributesTest < ActiveModel::TestCase + def setup + @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @profile_serializer = ProfileSerializer.new(@profile) + @profile_serializer.instance_eval do + def filter(keys) + keys - [:description] + end + end + end + + def test_filtered_attributes_serialization + assert_equal({ + 'profile' => { name: 'Name 1' } + }, @profile_serializer.as_json) + end + end + + class FilterAssociationsTest < ActiveModel::TestCase + def setup + @association = PostSerializer._associations[:comments] + @old_association = @association.dup + @association.embed = :ids + @association.embed_in_root = true + @post = Post.new({ title: 'Title 1', body: 'Body 1', date: '1/1/2000' }) + @post_serializer = PostSerializer.new(@post) + @post_serializer.instance_eval do + def filter(keys) + keys - [:body, :comments] + end + end + end + + def teardown + PostSerializer._associations[:comments] = @old_association + end + + def test_filtered_associations_serialization + assert_equal({ + 'post' => { title: 'Title 1' } + }, @post_serializer.as_json) + end + end + end +end diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb new file mode 100644 index 00000000..abdc8ed2 --- /dev/null +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -0,0 +1,117 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class HasManyTest < ActiveModel::TestCase + def setup + @association = PostSerializer._associations[:comments] + @old_association = @association.dup + @association.embed = :ids + @post = Post.new({ title: 'Title 1', body: 'Body 1', date: '1/1/2000' }) + @post_serializer = PostSerializer.new(@post) + end + + def teardown + PostSerializer._associations[:comments] = @old_association + end + + def test_associations_definition + assert_equal 1, PostSerializer._associations.length + assert_kind_of Association::HasMany, @association + assert_equal 'comments', @association.name + end + + def test_associations_embedding_ids_serialization_using_serializable_hash + assert_equal({ + title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } + }, @post_serializer.serializable_hash) + end + + def test_associations_embedding_ids_serialization_using_as_json + assert_equal({ + 'post' => { title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } } + }, @post_serializer.as_json) + end + + def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options + @association.key = 'key' + assert_equal({ + title: 'Title 1', body: 'Body 1', 'key' => @post.comments.map { |c| c.object_id } + }, @post_serializer.serializable_hash) + end + + def test_associations_embedding_objects_serialization_using_serializable_hash + @association.embed = :objects + assert_equal({ + title: 'Title 1', body: 'Body 1', comments: [{ content: 'C1' }, { content: 'C2' }] + }, @post_serializer.serializable_hash) + end + + def test_associations_embedding_objects_serialization_using_as_json + @association.embed = :objects + assert_equal({ + 'post' => { title: 'Title 1', body: 'Body 1', comments: [{ content: 'C1' }, { content: 'C2' }] } + }, @post_serializer.as_json) + end + + def test_associations_embedding_nil_objects_serialization_using_as_json + @association.embed = :objects + @post.instance_eval do + def comments + [nil] + end + end + + assert_equal({ + 'post' => { title: 'Title 1', body: 'Body 1', comments: [nil] } + }, @post_serializer.as_json) + end + + def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options + @association.embed = :objects + @association.embedded_key = 'root' + assert_equal({ + title: 'Title 1', body: 'Body 1', 'root' => [{ content: 'C1' }, { content: 'C2' }] + }, @post_serializer.serializable_hash) + end + + def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash + @association.embed_in_root = true + @post_serializer.root = nil + assert_equal({ + title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } + }, @post_serializer.serializable_hash) + end + + def test_associations_embedding_ids_including_objects_serialization_using_as_json + PostSerializer.embed :ids, include: true + PostSerializer._associations[:comments].send :initialize, @association.name, @association.options + + @post_serializer.root = nil + assert_equal({ + 'post' => { title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, + 'comments' => [{ content: 'C1' }, { content: 'C2' }] + }, @post_serializer.as_json) + ensure + SETTINGS.clear + end + + def test_associations_using_a_given_serializer + @association.embed_in_root = true + @post_serializer.root = nil + @association.serializer_class = Class.new(ActiveModel::Serializer) do + def content + 'fake' + end + + attributes :content + end + + assert_equal({ + 'post' => { title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, + comments: [{ content: 'fake' }, { content: 'fake' }] + }, @post_serializer.as_json) + end + end + end +end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb new file mode 100644 index 00000000..3450cbf0 --- /dev/null +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -0,0 +1,125 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class HasOneTest < ActiveModel::TestCase + def setup + @association = UserSerializer._associations[:profile] + @old_association = @association.dup + @association.embed = :ids + @user = User.new({ name: 'Name 1', email: 'mail@server.com', gender: 'M' }) + @user_serializer = UserSerializer.new(@user) + end + + def teardown + UserSerializer._associations[:profile] = @old_association + end + + def test_associations_definition + assert_equal 1, UserSerializer._associations.length + assert_kind_of Association::HasOne, @association + assert_equal 'profile', @association.name + end + + def test_associations_embedding_ids_serialization_using_serializable_hash + assert_equal({ + name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id + }, @user_serializer.serializable_hash) + end + + def test_associations_embedding_ids_serialization_using_as_json + assert_equal({ + 'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id } + }, @user_serializer.as_json) + end + + def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options + @association.key = 'key' + assert_equal({ + name: 'Name 1', email: 'mail@server.com', 'key' => @user.profile.object_id + }, @user_serializer.serializable_hash) + end + + def test_associations_embedding_objects_serialization_using_serializable_hash + @association.embed = :objects + assert_equal({ + name: 'Name 1', email: 'mail@server.com', profile: { name: 'N1', description: 'D1' } + }, @user_serializer.serializable_hash) + end + + def test_associations_embedding_objects_serialization_using_as_json + @association.embed = :objects + assert_equal({ + 'user' => { name: 'Name 1', email: 'mail@server.com', profile: { name: 'N1', description: 'D1' } } + }, @user_serializer.as_json) + end + + def test_associations_embedding_nil_ids_serialization_using_as_json + @user.instance_eval do + def profile + nil + end + end + + assert_equal({ + 'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => nil } + }, @user_serializer.as_json) + end + + def test_associations_embedding_nil_objects_serialization_using_as_json + @association.embed = :objects + @user.instance_eval do + def profile + nil + end + end + + assert_equal({ + 'user' => { name: 'Name 1', email: 'mail@server.com', profile: nil } + }, @user_serializer.as_json) + end + + def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options + @association.embed = :objects + @association.embedded_key = 'root' + assert_equal({ + name: 'Name 1', email: 'mail@server.com', 'root' => { name: 'N1', description: 'D1' } + }, @user_serializer.serializable_hash) + end + + def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash + @association.embed_in_root = true + @user_serializer.root = nil + assert_equal({ + name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id + }, @user_serializer.serializable_hash) + end + + def test_associations_embedding_ids_including_objects_serialization_using_as_json + @association.embed_in_root = true + @user_serializer.root = nil + assert_equal({ + 'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id }, + profile: { name: 'N1', description: 'D1' } + }, @user_serializer.as_json) + end + + def test_associations_using_a_given_serializer + @association.embed_in_root = true + @user_serializer.root = nil + @association.serializer_class = Class.new(ActiveModel::Serializer) do + def name + 'fake' + end + + attributes :name + end + + assert_equal({ + 'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id }, + profile: { name: 'fake' } + }, @user_serializer.as_json) + end + end + end +end diff --git a/test/unit/active_model/serializer/meta_test.rb b/test/unit/active_model/serializer/meta_test.rb new file mode 100644 index 00000000..c5d00bf6 --- /dev/null +++ b/test/unit/active_model/serializer/meta_test.rb @@ -0,0 +1,39 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class MetaTest < ActiveModel::TestCase + def setup + @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + end + + def test_meta + profile_serializer = ProfileSerializer.new(@profile, root: 'profile', meta: { total: 10 }) + + assert_equal({ + 'profile' => { + name: 'Name 1', + description: 'Description 1' + }, + meta: { + total: 10 + } + }, profile_serializer.as_json) + end + + def test_meta_using_meta_key + profile_serializer = ProfileSerializer.new(@profile, root: 'profile', meta_key: :my_meta, my_meta: { total: 10 }) + + assert_equal({ + 'profile' => { + name: 'Name 1', + description: 'Description 1' + }, + my_meta: { + total: 10 + } + }, profile_serializer.as_json) + end + end + end +end diff --git a/test/unit/active_model/serializer/root_test.rb b/test/unit/active_model/serializer/root_test.rb new file mode 100644 index 00000000..6cf2c546 --- /dev/null +++ b/test/unit/active_model/serializer/root_test.rb @@ -0,0 +1,103 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class RootAsOptionTest < ActiveModel::TestCase + def setup + @old_root = ProfileSerializer._root + @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @serializer = ProfileSerializer.new(@profile, root: :initialize) + ProfileSerializer._root = true + end + + def teardown + ProfileSerializer._root = @old_root + end + + def test_root_is_not_displayed_using_serializable_hash + assert_equal({ + name: 'Name 1', description: 'Description 1' + }, @serializer.serializable_hash) + end + + def test_root_using_as_json + assert_equal({ + initialize: { + name: 'Name 1', description: 'Description 1' + } + }, @serializer.as_json) + end + + def test_root_from_serializer_name + @serializer = ProfileSerializer.new(@profile) + + assert_equal({ + 'profile' => { + name: 'Name 1', description: 'Description 1' + } + }, @serializer.as_json) + end + + def test_root_as_argument_takes_precedence + assert_equal({ + argument: { + name: 'Name 1', description: 'Description 1' + } + }, @serializer.as_json(root: :argument)) + end + + def test_using_false_root_in_initializer_takes_precedence + ProfileSerializer._root = 'root' + @serializer = ProfileSerializer.new(@profile, root: false) + + assert_equal({ + name: 'Name 1', description: 'Description 1' + }, @serializer.as_json) + end + end + + class RootInSerializerTest < ActiveModel::TestCase + def setup + @old_root = ProfileSerializer._root + ProfileSerializer._root = :in_serializer + profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @serializer = ProfileSerializer.new(profile) + @rooted_serializer = ProfileSerializer.new(profile, root: :initialize) + end + + def teardown + ProfileSerializer._root = @old_root + end + + def test_root_is_not_displayed_using_serializable_hash + assert_equal({ + name: 'Name 1', description: 'Description 1' + }, @serializer.serializable_hash) + end + + def test_root_using_as_json + assert_equal({ + in_serializer: { + name: 'Name 1', description: 'Description 1' + } + }, @serializer.as_json) + end + + def test_root_in_initializer_takes_precedence + assert_equal({ + initialize: { + name: 'Name 1', description: 'Description 1' + } + }, @rooted_serializer.as_json) + end + + def test_root_as_argument_takes_precedence + assert_equal({ + argument: { + name: 'Name 1', description: 'Description 1' + } + }, @rooted_serializer.as_json(root: :argument)) + end + end + end +end diff --git a/test/unit/active_model/serializer/scope_test.rb b/test/unit/active_model/serializer/scope_test.rb new file mode 100644 index 00000000..78c1b8d7 --- /dev/null +++ b/test/unit/active_model/serializer/scope_test.rb @@ -0,0 +1,21 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class ScopeTest < ActiveModel::TestCase + def setup + @serializer = ProfileSerializer.new(nil, scope: current_user) + end + + def test_scope + assert_equal('user', @serializer.scope) + end + + private + + def current_user + 'user' + end + end + end +end diff --git a/test/unit/active_model/serializer/settings_test.rb b/test/unit/active_model/serializer/settings_test.rb new file mode 100644 index 00000000..abd5531b --- /dev/null +++ b/test/unit/active_model/serializer/settings_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class Settings + class Test < ActiveModel::TestCase + def test_settings_const_is_an_instance_of_settings + assert_kind_of Settings, SETTINGS + end + + def test_settings_instance + settings = Settings.new + settings[:setting1] = 'value1' + + assert_equal 'value1', settings[:setting1] + end + + def test_each_settings + settings = Settings.new + settings['setting1'] = 'value1' + settings['setting2'] = 'value2' + + actual = {} + + settings.each do |k, v| + actual[k] = v + end + + assert_equal({ 'setting1' => 'value1', 'setting2' => 'value2' }, actual) + + end + end + + class SetupTest < ActiveModel::TestCase + def test_setup + ActiveModel::Serializer.setup do |settings| + settings[:a] = 'v1' + settings[:b] = 'v2' + end + + assert_equal 'v1', SETTINGS[:a] + assert_equal 'v2', SETTINGS[:b] + ensure + SETTINGS.clear + end + end + end + end +end