Skip to content

Commit

Permalink
Separated object and association serialization.
Browse files Browse the repository at this point in the history
Moved object serialization into Toy::Object and included association
serialization in Toy::Store.
  • Loading branch information
jnunemaker committed Apr 19, 2012
1 parent 162f141 commit d943155
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 145 deletions.
3 changes: 2 additions & 1 deletion lib/toy.rb
Expand Up @@ -79,6 +79,7 @@ module Middleware
autoload 'Querying', 'toy/querying'
autoload 'Reloadable', 'toy/reloadable'
autoload 'Serialization', 'toy/serialization'
autoload 'AssociationSerialization','toy/association_serialization'
autoload 'Timestamps', 'toy/timestamps'
autoload 'Validations', 'toy/validations'

Expand All @@ -101,4 +102,4 @@ module Identity
require 'toy/object'
require 'toy/store'

Toy::IdentityMap.enabled = false
Toy::IdentityMap.enabled = false
50 changes: 50 additions & 0 deletions lib/toy/association_serialization.rb
@@ -0,0 +1,50 @@
module Toy
module AssociationSerialization
extend ActiveSupport::Concern
include Serialization

def serializable_hash(options = nil)
options ||= {}
super.tap { |hash|
serializable_add_includes(options) do |association, records, opts|
hash[association] = records.is_a?(Enumerable) ?
records.map { |r| r.serializable_hash(opts) } :
records.serializable_hash(opts)
end
}
end

private

# Add associations specified via the <tt>:includes</tt> option.
# Expects a block that takes as arguments:
# +association+ - name of the association
# +records+ - the association record(s) to be serialized
# +opts+ - options for the association records
def serializable_add_includes(options = {})
return unless include_associations = options.delete(:include)

base_only_or_except = { :except => options[:except],
:only => options[:only] }

include_has_options = include_associations.is_a?(Hash)
associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)

for association in associations
records = if self.class.list?(association)
send(association).to_a
elsif self.class.reference?(association) || self.class.parent_reference?(association)
send(association)
end

unless records.nil?
association_options = include_has_options ? include_associations[association] : base_only_or_except
opts = options.merge(association_options)
yield(association, records, opts)
end
end

options[:include] = include_associations
end
end
end
1 change: 1 addition & 0 deletions lib/toy/object.rb
Expand Up @@ -12,6 +12,7 @@ module Object
include Inspect
include Logger
include Inheritance
include Serialization
end

def persisted?
Expand Down
41 changes: 1 addition & 40 deletions lib/toy/serialization.rb
Expand Up @@ -37,46 +37,7 @@ def serializable_hash(options = nil)
end
end

serializable_add_includes(options) do |association, records, opts|
hash[association] = records.is_a?(Enumerable) ?
records.map { |r| r.serializable_hash(opts) } :
records.serializable_hash(opts)
end

hash
end

private

# Add associations specified via the <tt>:includes</tt> option.
# Expects a block that takes as arguments:
# +association+ - name of the association
# +records+ - the association record(s) to be serialized
# +opts+ - options for the association records
def serializable_add_includes(options = {})
return unless include_associations = options.delete(:include)

base_only_or_except = { :except => options[:except],
:only => options[:only] }

include_has_options = include_associations.is_a?(Hash)
associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)

for association in associations
records = if self.class.list?(association)
send(association).to_a
elsif self.class.reference?(association) || self.class.parent_reference?(association)
send(association)
end

unless records.nil?
association_options = include_has_options ? include_associations[association] : base_only_or_except
opts = options.merge(association_options)
yield(association, records, opts)
end
end

options[:include] = include_associations
end
end
end
end
4 changes: 2 additions & 2 deletions lib/toy/store.rb
Expand Up @@ -13,14 +13,14 @@ module Store

include Callbacks
include Validations
include Serialization
include Timestamps

include Lists
include References
include AssociationSerialization

include IdentityMap
include Caching
end
end
end
end
103 changes: 103 additions & 0 deletions spec/toy/association_serialization_spec.rb
@@ -0,0 +1,103 @@
require 'helper'

describe Toy::AssociationSerialization do
uses_constants('User', 'Game', 'Move')

before do
User.attribute :name, String
User.attribute :age, Integer
end

describe "serializing relationships" do
before do
User.list :games, :inverse_of => :user
Game.reference :user
end

it "should include references" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create

MultiJson.load(game.to_json(:include => [:user])).should == {
'game' => {
'id' => game.id,
'user_id' => user.id,
'user' => {
'name' => 'John',
'game_ids' => [game.id],
'id' => user.id,
'age' => 28,
}
}
}
end

it "should include lists" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create
MultiJson.load(user.to_json(:include => [:games])).should == {
'user' => {
'name' => 'John',
'game_ids' => [game.id],
'id' => user.id,
'age' => 28,
'games' => [{'id' => game.id, 'user_id' => user.id}],
}
}
end

it "should not cause circular reference JSON errors for references" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create

MultiJson.load(ActiveSupport::JSON.encode(game.user)).should == {
'user' => {
'name' => 'John',
'game_ids' => [game.id],
'id' => user.id,
'age' => 28
}
}
end

it "should not cause circular reference JSON errors for references when called indirectly" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create

MultiJson.load(ActiveSupport::JSON.encode([game.user])).should == [
'user' => {
'name' => 'John',
'game_ids' => [game.id],
'id' => user.id,
'age' => 28
}
]
end

it "should not cause circular reference JSON errors for lists" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create

MultiJson.load(ActiveSupport::JSON.encode(user.games)).should == [{
'game' => {
'id' => game.id,
'user_id' => user.id
}
}]
end

it "should not cause circular reference JSON errors for lists when called indirectly" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create

MultiJson.load(ActiveSupport::JSON.encode({:games => user.games})).should == {
'games' => [{
'game' => {
'id' => game.id,
'user_id' => user.id
}
}]
}
end
end
end
109 changes: 7 additions & 102 deletions spec/toy/serialization_spec.rb
@@ -1,7 +1,7 @@
require 'helper'

describe Toy::Serialization do
uses_constants('User', 'Game', 'Move')
uses_objects('User', 'Move')

before do
User.attribute :name, String
Expand All @@ -28,7 +28,6 @@
'age' => 28
}
}

end

it "correctly serializes methods" do
Expand Down Expand Up @@ -65,99 +64,6 @@ def foo
MultiJson.load(json)['user'].should_not have_key('id')
end

describe "serializing relationships" do
before do
User.list :games, :inverse_of => :user
Game.reference :user
end

it "should include references" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create

MultiJson.load(game.to_json(:include => [:user])).should == {
'game' => {
'id' => game.id,
'user_id' => user.id,
'user' => {
'name' => 'John',
'game_ids' => [game.id],
'id' => user.id,
'age' => 28,
}
}
}
end

it "should include lists" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create
MultiJson.load(user.to_json(:include => [:games])).should == {
'user' => {
'name' => 'John',
'game_ids' => [game.id],
'id' => user.id,
'age' => 28,
'games' => [{'id' => game.id, 'user_id' => user.id}],
}
}
end

it "should not cause circular reference JSON errors for references" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create

MultiJson.load(ActiveSupport::JSON.encode(game.user)).should == {
'user' => {
'name' => 'John',
'game_ids' => [game.id],
'id' => user.id,
'age' => 28
}
}
end

it "should not cause circular reference JSON errors for references when called indirectly" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create

MultiJson.load(ActiveSupport::JSON.encode([game.user])).should == [
'user' => {
'name' => 'John',
'game_ids' => [game.id],
'id' => user.id,
'age' => 28
}
]
end

it "should not cause circular reference JSON errors for lists" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create

MultiJson.load(ActiveSupport::JSON.encode(user.games)).should == [{
'game' => {
'id' => game.id,
'user_id' => user.id
}
}]
end

it "should not cause circular reference JSON errors for lists when called indirectly" do
user = User.create(:name => 'John', :age => 28)
game = user.games.create

MultiJson.load(ActiveSupport::JSON.encode({:games => user.games})).should == {
'games' => [{
'game' => {
'id' => game.id,
'user_id' => user.id
}
}]
}
end
end

describe "serializing specific attributes" do
before do
Move.attribute(:index, Integer)
Expand Down Expand Up @@ -228,16 +134,15 @@ def calculated_attribute
describe "#serializable_hash" do
context "with method that is another toystore object" do
before do
Game.reference(:creator, User)
@game = Game.create(:creator => User.create)
Move.class_eval { attr_accessor :creator }
end
let(:game) { @game }

let(:move) { Move.new(:creator => User.new) }

it "returns serializable hash of object" do
game.serializable_hash(:methods => [:creator]).should == {
'id' => game.id,
'creator_id' => game.creator_id,
'creator' => {'id' => game.creator.id}
move.serializable_hash(:methods => [:creator]).should == {
'id' => move.id,
'creator' => {'id' => move.creator.id}
}
end
end
Expand Down

0 comments on commit d943155

Please sign in to comment.