diff --git a/lib/toy.rb b/lib/toy.rb index f7bb5cc..ea33d07 100644 --- a/lib/toy.rb +++ b/lib/toy.rb @@ -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' @@ -101,4 +102,4 @@ module Identity require 'toy/object' require 'toy/store' -Toy::IdentityMap.enabled = false \ No newline at end of file +Toy::IdentityMap.enabled = false diff --git a/lib/toy/association_serialization.rb b/lib/toy/association_serialization.rb new file mode 100644 index 0000000..64e9337 --- /dev/null +++ b/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 :includes 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 diff --git a/lib/toy/object.rb b/lib/toy/object.rb index 192bd52..7cbe123 100644 --- a/lib/toy/object.rb +++ b/lib/toy/object.rb @@ -12,6 +12,7 @@ module Object include Inspect include Logger include Inheritance + include Serialization end def persisted? diff --git a/lib/toy/serialization.rb b/lib/toy/serialization.rb index d1be65d..9f98789 100644 --- a/lib/toy/serialization.rb +++ b/lib/toy/serialization.rb @@ -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 :includes 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 \ No newline at end of file +end diff --git a/lib/toy/store.rb b/lib/toy/store.rb index 12478b5..c7f8a1f 100644 --- a/lib/toy/store.rb +++ b/lib/toy/store.rb @@ -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 \ No newline at end of file +end diff --git a/spec/toy/association_serialization_spec.rb b/spec/toy/association_serialization_spec.rb new file mode 100644 index 0000000..788b871 --- /dev/null +++ b/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 diff --git a/spec/toy/serialization_spec.rb b/spec/toy/serialization_spec.rb index b1948ae..c7d4c60 100644 --- a/spec/toy/serialization_spec.rb +++ b/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 @@ -28,7 +28,6 @@ 'age' => 28 } } - end it "correctly serializes methods" do @@ -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) @@ -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