Skip to content

Commit

Permalink
Make serialization AS2.3/3 agnostic
Browse files Browse the repository at this point in the history
  • Loading branch information
boffbowsh authored and jnunemaker committed Mar 2, 2010
1 parent fd8b044 commit c05560c
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 103 deletions.
132 changes: 51 additions & 81 deletions lib/mongo_mapper/plugins/serialization.rb
Expand Up @@ -4,102 +4,72 @@ module MongoMapper
module Plugins
module Serialization
def self.configure(model)
model.class_eval { include Json }
model.class_eval { cattr_accessor :include_root_in_json, :instance_writer => true }
end

class Serializer
attr_reader :options

def initialize(record, options={})
@record, @options = record, options.dup
end

def serializable_key_names
key_names = @record.attributes.keys

if options[:only]
options.delete(:except)
key_names = key_names & Array(options[:only]).collect { |n| n.to_s }
else
options[:except] = Array(options[:except])
key_names = key_names - options[:except].collect { |n| n.to_s }
end

key_names
end

def serializable_method_names
Array(options[:methods]).inject([]) do |method_attributes, name|
method_attributes << name if @record.respond_to?(name.to_s)
method_attributes
module InstanceMethods
def as_json options={}
options ||= {}
unless options[:only]
methods = [options.delete(:methods)].flatten.compact
methods << :id
options[:methods] = methods.uniq
end
end

def serializable_names
serializable_key_names + serializable_method_names
end
except = [options.delete(:except)].flatten.compact
except << :_id
options[:except] = except

def serializable_record
returning(serializable_record = {}) do
serializable_names.each { |name| serializable_record[name] = @record.send(name) }
end
end
# Direct rip from Rails 3 ActiveModel Serialization (#serializable_hash)
hash = begin
options[:only] = Array.wrap(options[:only]).map { |n| n.to_s }
options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }

def serialize
# overwrite to implement
end
attribute_names = attributes.keys.sort
if options[:only].any?
attribute_names &= options[:only]
elsif options[:except].any?
attribute_names -= options[:except]
end

def to_s(&block)
serialize(&block)
end
end

module Json
def self.included(base)
base.cattr_accessor :include_root_in_json, :instance_writer => false
base.extend ClassMethods
end
method_names = Array.wrap(options[:methods]).inject([]) do |methods, name|
methods << name if respond_to?(name.to_s)
methods
end

module ClassMethods
def json_class_name
@json_class_name ||= name.demodulize.underscore.inspect
(attribute_names + method_names).inject({}) { |hash, name|
hash[name] = send(name)
hash
}
end
end

def to_json(options={})
apply_to_json_defaults(options)

if include_root_in_json
"{#{self.class.json_class_name}: #{JsonSerializer.new(self, options).to_s}}"
else
JsonSerializer.new(self, options).to_s
# End rip

options.delete(:only) if options[:only].nil? or options[:only].empty?

hash.each do |key, value|
if value.is_a?(Array)
hash[key] = value.map do |item|
item.respond_to?(:as_json) ? item.as_json(options) : item
end
elsif value.is_a? Mongo::ObjectID
hash[key] = value.to_s
elsif value.respond_to?(:as_json)
hash[key] = value.as_json(options)
end
end

# Replicate Rails 3 naming - and also bin anytihng after : for use in our dynamic classes from unit tests
hash = { ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).gsub(/:.*/,'') => hash } if include_root_in_json
hash
end
end

module ClassMethods
def from_json(json)
self.attributes = ActiveSupport::JSON.decode(json)
self
self.new(ActiveSupport::JSON.decode(json))
end

class JsonSerializer < Serializer
def serialize
serializable_record.to_json
end
end

private
def apply_to_json_defaults(options)
unless options[:only]
methods = [options.delete(:methods)].flatten.compact
methods << :id
options[:methods] = methods.uniq
end

except = [options.delete(:except)].flatten.compact
except << :_id
options[:except] = except
end
end

end
end
end
46 changes: 27 additions & 19 deletions test/unit/serializers/test_json_serializer.rb
@@ -1,6 +1,14 @@
require 'test_helper'
require 'active_support/version'

class JsonSerializationTest < Test::Unit::TestCase

# Helper function in case things change in the future
# - replacing all those to_json calls was a nightmare
def convert_to_json object, options = {}
ActiveSupport::JSON.encode(object, options)
end

class Tag
include MongoMapper::EmbeddedDocument
key :name, String
Expand Down Expand Up @@ -30,11 +38,11 @@ def setup

should "include demodulized root" do
Contact.include_root_in_json = true
assert_match %r{^\{"contact": \{}, @contact.to_json
assert_match %r{^\{"contact":\s?\{}, convert_to_json(@contact)
end

should "encode all encodable attributes" do
json = @contact.to_json
json = convert_to_json(@contact)

assert_no_match %r{"_id"}, json
assert_match %r{"name":"Konata Izumi"}, json
Expand All @@ -45,7 +53,7 @@ def setup
end

should "allow attribute filtering with only" do
json = @contact.to_json(:only => [:name, :age])
json = convert_to_json(@contact, :only => [:name, :age])

assert_no_match %r{"_id"}, json
assert_match %r{"name":"Konata Izumi"}, json
Expand All @@ -56,7 +64,7 @@ def setup
end

should "allow attribute filtering with except" do
json = @contact.to_json(:except => [:name, :age])
json = convert_to_json(@contact, :except => [:name, :age])

assert_no_match %r{"_id"}, json
assert_no_match %r{"name"}, json
Expand All @@ -68,12 +76,12 @@ def setup

context "_id key" do
should "not be included by default" do
json = @contact.to_json
json = convert_to_json(@contact)
assert_no_match %r{"_id":}, json
end

should "not be included even if :except is used" do
json = @contact.to_json(:except => :name)
json = convert_to_json(@contact, :except => :name)
assert_no_match %r{"_id":}, json
end
end
Expand All @@ -85,35 +93,35 @@ def @contact.favorite_quote; "Constraints are liberating"; end
end

should "be included by default" do
json = @contact.to_json
json = convert_to_json(@contact)
assert_match %r{"id"}, json
end

should "be included when single method included" do
json = @contact.to_json(:methods => :label)
json = convert_to_json(@contact, :methods => :label)
assert_match %r{"id"}, json
assert_match %r{"label":"Has cheezburger"}, json
assert_match %r{"name":"Konata Izumi"}, json
assert_no_match %r{"favorite_quote":"Constraints are liberating"}, json
end

should "be included when multiple methods included" do
json = @contact.to_json(:methods => [:label, :favorite_quote])
json = convert_to_json(@contact, :methods => [:label, :favorite_quote])
assert_match %r{"id"}, json
assert_match %r{"label":"Has cheezburger"}, json
assert_match %r{"favorite_quote":"Constraints are liberating"}, json
assert_match %r{"name":"Konata Izumi"}, json
end

should "not be included if :only is present" do
json = @contact.to_json(:only => :name)
json = convert_to_json(@contact, :only => :name)
assert_no_match %r{"id":}, json
end

should "be represented by a string" do
json = convert_to_json(@contact)
assert_match %r{"id":"}, json
end
end
end

context "including methods" do
Expand All @@ -123,12 +131,12 @@ def @contact.favorite_quote; "Constraints are liberating"; end
end

should "include single method" do
json = @contact.to_json(:methods => :label)
json = convert_to_json(@contact, :methods => :label)
assert_match %r{"label":"Has cheezburger"}, json
end

should "include multiple methods" do
json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote])
json = convert_to_json(@contact, :only => :name, :methods => [:label, :favorite_quote])
assert_match %r{"label":"Has cheezburger"}, json
assert_match %r{"favorite_quote":"Constraints are liberating"}, json
assert_match %r{"name":"Konata Izumi"}, json
Expand All @@ -148,13 +156,13 @@ def @contact.favorite_quote; "Constraints are liberating"; end
end

should "allow attribute filtering with only" do
json = @contacts.to_json(:only => :name)
json =convert_to_json(@contacts, :only => :name)
assert_match %r{\{"name":"David"\}}, json
assert_match %r{\{"name":"Mary"\}}, json
end

should "allow attribute filtering with except" do
json = @contacts.to_json(:except => [:name, :preferences, :awesome, :created_at, :updated_at])
json = convert_to_json(@contacts, :except => [:name, :preferences, :awesome, :created_at, :updated_at])
assert_match %r{"age":39}, json
assert_match %r{"age":14}, json
assert_no_match %r{"name":}, json
Expand All @@ -170,7 +178,7 @@ def @contact.favorite_quote; "Constraints are liberating"; end
1 => Contact.new(:name => 'David', :age => 39),
2 => Contact.new(:name => 'Mary', :age => 14)
}
json = contacts.to_json(:only => [1, :name])
json = convert_to_json(contacts, :only => [1, :name])
assert_match %r{"1":}, json
assert_match %r{\{"name":"David"\}}, json
assert_no_match %r{"2":}, json
Expand All @@ -179,7 +187,7 @@ def @contact.favorite_quote; "Constraints are liberating"; end
should "include embedded attributes" do
contact = Contact.new(:name => 'John', :age => 27)
contact.tags = [Tag.new(:name => 'awesome'), Tag.new(:name => 'ruby')]
json = contact.to_json
json = convert_to_json(contact)
assert_match %r{"tags":}, json
assert_match %r{"name":"awesome"}, json
assert_match %r{"name":"ruby"}, json
Expand All @@ -188,7 +196,7 @@ def @contact.favorite_quote; "Constraints are liberating"; end
should "include dynamic attributes" do
contact = Contact.new(:name => 'John', :age => 27, :foo => 'bar')
contact['smell'] = 'stinky'
json = contact.to_json
json = convert_to_json(contact)
assert_match %r{"smell":"stinky"}, json
end
end
6 changes: 3 additions & 3 deletions test/unit/test_serialization.rb
Expand Up @@ -23,14 +23,14 @@ def setup
context format do
should "be reversable" do
serialized = @instance.send("to_#{format}")
unserialized = @document.new.send("from_#{format}", serialized)
unserialized = @document.send("from_#{format}", serialized)

assert_equal @instance, unserialized
end

should "allow attribute only filtering" do
serialized = @instance.send("to_#{format}", :only => [ :age, :name ])
unserialized = @document.new.send("from_#{format}", serialized)
unserialized = @document.send("from_#{format}", serialized)

assert_equal @instance.name, unserialized.name
assert_equal @instance.age, unserialized.age
Expand All @@ -40,7 +40,7 @@ def setup

should "allow attribute except filtering" do
serialized = @instance.send("to_#{format}", :except => [ :age, :name ])
unserialized = @document.new.send("from_#{format}", serialized)
unserialized = @document.send("from_#{format}", serialized)

assert_nil unserialized.name
assert_nil unserialized.age
Expand Down

0 comments on commit c05560c

Please sign in to comment.