Skip to content

Commit

Permalink
Ported JSON serialization from ActiveRecord
Browse files Browse the repository at this point in the history
  • Loading branch information
bkeepers committed Jun 18, 2009
1 parent dbcb15a commit 12c2e8e
Show file tree
Hide file tree
Showing 6 changed files with 292 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/mongo_mapper.rb
Expand Up @@ -11,9 +11,11 @@
require 'mongo_mapper/key'
require 'mongo_mapper/finder_options'
require 'mongo_mapper/save_with_validation'
require 'mongo_mapper/serialization'
require 'mongo_mapper/embedded_document'
require 'mongo_mapper/document'


module MongoMapper
class DocumentNotFound < StandardError; end

Expand Down
1 change: 1 addition & 0 deletions lib/mongo_mapper/embedded_document.rb
Expand Up @@ -8,6 +8,7 @@ def self.included(model)
extend ClassMethods
include Validatable
include ActiveSupport::Callbacks
include MongoMapper::Serialization

define_callbacks :before_validation_on_create, :before_validation_on_update,
:before_validation, :after_validation,
Expand Down
54 changes: 54 additions & 0 deletions lib/mongo_mapper/serialization.rb
@@ -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'
77 changes: 77 additions & 0 deletions lib/mongo_mapper/serializers/json_serializer.rb
@@ -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
104 changes: 104 additions & 0 deletions test/serializers/test_json_serializer.rb
@@ -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
54 changes: 54 additions & 0 deletions test/test_serializations.rb
@@ -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

0 comments on commit 12c2e8e

Please sign in to comment.