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
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ module Middleware
autoload 'Querying', 'toy/querying' autoload 'Querying', 'toy/querying'
autoload 'Reloadable', 'toy/reloadable' autoload 'Reloadable', 'toy/reloadable'
autoload 'Serialization', 'toy/serialization' autoload 'Serialization', 'toy/serialization'
autoload 'AssociationSerialization','toy/association_serialization'
autoload 'Timestamps', 'toy/timestamps' autoload 'Timestamps', 'toy/timestamps'
autoload 'Validations', 'toy/validations' autoload 'Validations', 'toy/validations'


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


Toy::IdentityMap.enabled = false Toy::IdentityMap.enabled = false
50 changes: 50 additions & 0 deletions lib/toy/association_serialization.rb
Original file line number Original file line Diff line number Diff line change
@@ -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
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module Object
include Inspect include Inspect
include Logger include Logger
include Inheritance include Inheritance
include Serialization
end end


def persisted? def persisted?
Expand Down
41 changes: 1 addition & 40 deletions lib/toy/serialization.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -37,46 +37,7 @@ def serializable_hash(options = nil)
end end
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 hash
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
end end
4 changes: 2 additions & 2 deletions lib/toy/store.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ module Store


include Callbacks include Callbacks
include Validations include Validations
include Serialization
include Timestamps include Timestamps


include Lists include Lists
include References include References
include AssociationSerialization


include IdentityMap include IdentityMap
include Caching include Caching
end end
end end
end end
103 changes: 103 additions & 0 deletions spec/toy/association_serialization_spec.rb
Original file line number Original file line Diff line number Diff line change
@@ -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
Original file line number Original file line Diff line number Diff line change
@@ -1,7 +1,7 @@
require 'helper' require 'helper'


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


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

end end


it "correctly serializes methods" do it "correctly serializes methods" do
Expand Down Expand Up @@ -65,99 +64,6 @@ def foo
MultiJson.load(json)['user'].should_not have_key('id') MultiJson.load(json)['user'].should_not have_key('id')
end 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 describe "serializing specific attributes" do
before do before do
Move.attribute(:index, Integer) Move.attribute(:index, Integer)
Expand Down Expand Up @@ -228,16 +134,15 @@ def calculated_attribute
describe "#serializable_hash" do describe "#serializable_hash" do
context "with method that is another toystore object" do context "with method that is another toystore object" do
before do before do
Game.reference(:creator, User) Move.class_eval { attr_accessor :creator }
@game = Game.create(:creator => User.create)
end end
let(:game) { @game }
let(:move) { Move.new(:creator => User.new) }


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

0 comments on commit d943155

Please sign in to comment.