forked from mongomapper/mongomapper
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ported JSON serialization from ActiveRecord
- Loading branch information
Showing
6 changed files
with
292 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
require 'active_support/json' | ||
|
||
module MongoMapper #:nodoc: | ||
module Serialization | ||
class Serializer #:nodoc: | ||
attr_reader :options | ||
|
||
def initialize(record, options = {}) | ||
@record, @options = record, options.dup | ||
end | ||
|
||
def serializable_key_names | ||
key_names = @record.send :defined_key_names | ||
|
||
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 | ||
end | ||
end | ||
|
||
def serializable_names | ||
serializable_key_names + serializable_method_names | ||
end | ||
|
||
def serializable_record | ||
returning(serializable_record = {}) do | ||
serializable_names.each { |name| serializable_record[name] = @record.send(name) } | ||
end | ||
end | ||
|
||
def serialize | ||
# overwrite to implement | ||
end | ||
|
||
def to_s(&block) | ||
serialize(&block) | ||
end | ||
end | ||
end | ||
end | ||
|
||
require 'mongo_mapper/serializers/json_serializer' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
module MongoMapper #:nodoc: | ||
module Serialization | ||
def self.included(base) | ||
base.cattr_accessor :include_root_in_json, :instance_writer => false | ||
base.extend ClassMethods | ||
end | ||
|
||
# Returns a JSON string representing the model. Some configuration is | ||
# available through +options+. | ||
# | ||
# The option <tt>include_root_in_json</tt> controls the top-level behavior of | ||
# to_json. When it is <tt>true</tt>, to_json will emit a single root node named | ||
# after the object's type. For example: | ||
# | ||
# konata = User.find(1) | ||
# User.include_root_in_json = true | ||
# konata.to_json | ||
# # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16, | ||
# "created_at": "2006/08/01", "awesome": true} } | ||
# | ||
# User.include_root_in_json = false | ||
# konata.to_json | ||
# # => {"id": 1, "name": "Konata Izumi", "age": 16, | ||
# "created_at": "2006/08/01", "awesome": true} | ||
# | ||
# The remainder of the examples in this section assume include_root_in_json is set to | ||
# <tt>false</tt>. | ||
# | ||
# Without any +options+, the returned JSON string will include all | ||
# the model's attributes. For example: | ||
# | ||
# konata = User.find(1) | ||
# konata.to_json | ||
# # => {"id": 1, "name": "Konata Izumi", "age": 16, | ||
# "created_at": "2006/08/01", "awesome": true} | ||
# | ||
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes | ||
# included, and work similar to the +attributes+ method. For example: | ||
# | ||
# konata.to_json(:only => [ :id, :name ]) | ||
# # => {"id": 1, "name": "Konata Izumi"} | ||
# | ||
# konata.to_json(:except => [ :id, :created_at, :age ]) | ||
# # => {"name": "Konata Izumi", "awesome": true} | ||
# | ||
# To include any methods on the model, use <tt>:methods</tt>. | ||
# | ||
# konata.to_json(:methods => :permalink) | ||
# # => {"id": 1, "name": "Konata Izumi", "age": 16, | ||
# "created_at": "2006/08/01", "awesome": true, | ||
# "permalink": "1-konata-izumi"} | ||
def to_json(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 | ||
end | ||
|
||
def from_json(json) | ||
self.attributes = ActiveSupport::JSON.decode(json) | ||
self | ||
end | ||
|
||
class JsonSerializer < MongoMapper::Serialization::Serializer #:nodoc: | ||
def serialize | ||
serializable_record.to_json | ||
end | ||
end | ||
|
||
module ClassMethods | ||
def json_class_name | ||
@json_class_name ||= name.demodulize.underscore.inspect | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
require 'test_helper' | ||
|
||
class JsonSerializationTest < Test::Unit::TestCase | ||
class Contact | ||
include MongoMapper::EmbeddedDocument | ||
key :name, String | ||
key :age, Integer | ||
key :created_at, Time | ||
key :awesome, Boolean | ||
key :preferences, Hash | ||
end | ||
|
||
def setup | ||
Contact.include_root_in_json = false | ||
@contact = Contact.new( | ||
:name => 'Konata Izumi', | ||
:age => 16, | ||
:created_at => Time.utc(2006, 8, 1), | ||
:awesome => true, | ||
:preferences => { :shows => 'anime' } | ||
) | ||
end | ||
|
||
should "include demodulized root" do | ||
Contact.include_root_in_json = true | ||
assert_match %r{^\{"contact": \{}, @contact.to_json | ||
end | ||
|
||
should "encode all encodable attributes" do | ||
json = @contact.to_json | ||
|
||
assert_match %r{"name": "Konata Izumi"}, json | ||
assert_match %r{"age": 16}, json | ||
assert json.include?(%("created_at": #{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) | ||
assert_match %r{"awesome": true}, json | ||
assert_match %r{"preferences": \{"shows": "anime"\}}, json | ||
end | ||
|
||
should "allow attribute filtering with only" do | ||
json = @contact.to_json(:only => [:name, :age]) | ||
|
||
assert_match %r{"name": "Konata Izumi"}, json | ||
assert_match %r{"age": 16}, json | ||
assert_no_match %r{"awesome"}, json | ||
assert_no_match %r{"created_at"}, json | ||
assert_no_match %r{"preferences"}, json | ||
end | ||
|
||
should "allow attribute filtering with except" do | ||
json = @contact.to_json(:except => [:name, :age]) | ||
|
||
assert_no_match %r{"name"}, json | ||
assert_no_match %r{"age"}, json | ||
assert_match %r{"awesome"}, json | ||
assert_match %r{"created_at"}, json | ||
assert_match %r{"preferences"}, json | ||
end | ||
|
||
context "including methods" do | ||
setup do | ||
def @contact.label; "Has cheezburger"; end | ||
def @contact.favorite_quote; "Constraints are liberating"; end | ||
end | ||
|
||
should "include single method" do | ||
# Single method. | ||
assert_match %r{"label": "Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label) | ||
end | ||
|
||
should "include multiple methods" do | ||
json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote]) | ||
assert_match %r{"label": "Has cheezburger"}, json | ||
assert_match %r{"favorite_quote": "Constraints are liberating"}, json | ||
end | ||
end | ||
|
||
context "array of records" do | ||
setup do | ||
@contacts = [ | ||
Contact.new(:name => 'David', :age => 39), | ||
Contact.new(:name => 'Mary', :age => 14) | ||
] | ||
end | ||
|
||
should "allow attribute filtering with only" do | ||
assert_equal %([{"name": "David"}, {"name": "Mary"}]), @contacts.to_json(:only => :name) | ||
end | ||
|
||
should "allow attribute filtering with except" do | ||
json = @contacts.to_json(:except => [:name, :preferences, :awesome, :created_at]) | ||
assert_equal %([{"age": 39}, {"age": 14}]), json | ||
end | ||
end | ||
|
||
should "allow options for hash of records" do | ||
contacts = { | ||
1 => Contact.new(:name => 'David', :age => 39), | ||
2 => Contact.new(:name => 'Mary', :age => 14) | ||
} | ||
|
||
assert_equal %({"1": {"name": "David"}}), contacts.to_json(:only => [1, :name]) | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
require 'test_helper' | ||
|
||
class SerializationTest < Test::Unit::TestCase | ||
def setup | ||
@document = Class.new do | ||
include MongoMapper::EmbeddedDocument | ||
key :name, String | ||
key :age, Integer | ||
key :awesome, Boolean | ||
key :preferences, Hash | ||
key :created_at, Time | ||
end | ||
|
||
@instance = @document.new( | ||
:name => 'John Doe', | ||
:age => 25, | ||
:awesome => true, | ||
:preferences => {:language => 'Ruby'}, | ||
:created_at => Time.now.change(:usec => 0) | ||
) | ||
end | ||
|
||
# [:xml, :json].each do |format| | ||
[:json].each do |format| | ||
context format do | ||
should "be reversable" do | ||
serialized = @instance.send("to_#{format}") | ||
unserialized = @document.new.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) | ||
|
||
assert_equal @instance.name, unserialized.name | ||
assert_equal @instance.age, unserialized.age | ||
assert_nil unserialized.awesome | ||
assert_nil unserialized.created_at | ||
end | ||
|
||
should "allow attribute except filtering" do | ||
serialized = @instance.send("to_#{format}", :except => [ :age, :name ]) | ||
unserialized = @document.new.send("from_#{format}", serialized) | ||
|
||
assert_nil unserialized.name | ||
assert_nil unserialized.age | ||
assert_equal @instance.awesome, unserialized.awesome | ||
end | ||
|
||
end | ||
end | ||
end |