require 'test_helper'
# parse_strategy: :sync
# parse_strategy: :replace
# parse_strategy: :find_or_instantiate ("expand" since we don't delete existing unmatched in target)
class ParseStrategySyncTest < BaseTest
for_formats(
:hash => [Representable::Hash, {"song"=>{"title"=>"Resist Stance"}}, {"song"=>{"title"=>"Suffer"}}],
:xml => [Representable::XML, "Resist Stance", "Suffer",],
:yaml => [Representable::YAML, "---\nsong:\n title: Resist Stance\n", "---\nsong:\n title: Suffer\n"],
) do |format, mod, output, input|
describe "[#{format}] property with parse_strategy: :sync" do # TODO: introduce :representable option?
let (:format) { format }
representer!(:module => mod, :name => :song_representer) do
property :title
self.representation_wrap = :song if format == :xml
end
representer!(:inject => :song_representer, :module => mod) do
property :song, :parse_strategy => :sync, :extend => song_representer
end
let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) }
it "calls #to_hash on song instance, nothing else" do
render(hit).must_equal_document(output)
end
it "calls #from_hash on the existing song instance, nothing else" do
song_id = hit.song.object_id
parse(hit, input)
hit.song.title.must_equal "Suffer"
hit.song.object_id.must_equal song_id
end
end
end
# FIXME: there's a bug with XML and the collection name!
for_formats(
:hash => [Representable::Hash, {"songs"=>[{"title"=>"Resist Stance"}]}, {"songs"=>[{"title"=>"Suffer"}]}],
#:json => [Representable::JSON, "{\"song\":{\"name\":\"Alive\"}}", "{\"song\":{\"name\":\"You've Taken Everything\"}}"],
:xml => [Representable::XML, "Resist Stance", "Suffer"],
:yaml => [Representable::YAML, "---\nsongs:\n- title: Resist Stance\n", "---\nsongs:\n- title: Suffer\n"],
) do |format, mod, output, input|
describe "[#{format}] collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
let (:format) { format }
representer!(:module => mod, :name => :song_representer) do
property :title
self.representation_wrap = :song if format == :xml
end
representer!(:inject => :song_representer, :module => mod) do
collection :songs, :parse_strategy => :sync, :extend => song_representer
end
let (:album) { OpenStruct.new(:songs => [song]).extend(representer) }
it "calls #to_hash on song instances, nothing else" do
render(album).must_equal_document(output)
end
it "calls #from_hash on the existing song instance, nothing else" do
collection_id = album.songs.object_id
song = album.songs.first
song_id = song.object_id
parse(album, input)
album.songs.first.title.must_equal "Suffer"
song.title.must_equal "Suffer"
#album.songs.object_id.must_equal collection_id # TODO: don't replace!
song.object_id.must_equal song_id
end
end
end
# Sync errors, when model and incoming are not in sync.
describe ":sync with error" do
representer! do
property :song, :parse_strategy => :sync do
property :title
end
end
# object.song is nil whereas the document contains one.
it do
assert_raises Representable::DeserializeError do
OpenStruct.new.extend(representer).from_hash({"song" => {"title" => "Perpetual"}})
end
end
end
# Lonely Collection
for_formats(
:hash => [Representable::Hash::Collection, [{"title"=>"Resist Stance"}], [{"title"=>"Suffer"}]],
# :xml => [Representable::XML, "Resist Stance", "Suffer"],
) do |format, mod, output, input|
describe "[#{format}] lonely collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
let (:format) { format }
representer!(:module => Representable::Hash, :name => :song_representer) do
property :title
self.representation_wrap = :song if format == :xml
end
representer!(:inject => :song_representer, :module => mod) do
items :parse_strategy => :sync, :extend => song_representer
end
let (:album) { [song].extend(representer) }
it "calls #to_hash on song instances, nothing else" do
render(album).must_equal_document(output)
end
it "calls #from_hash on the existing song instance, nothing else" do
#collection_id = album.object_id
song = album.first
song_id = song.object_id
parse(album, input)
album.first.title.must_equal "Suffer"
song.title.must_equal "Suffer"
song.object_id.must_equal song_id
end
end
end
end
class ParseStrategyFindOrInstantiateTest < BaseTest
# parse_strategy: :find_or_instantiate
Song = Struct.new(:id, :title)
Song.class_eval do
def self.find_by(attributes={})
return new(1, "Resist Stan") if attributes[:id]==1# we should return the same object here
new
end
end
representer!(:name => :song_representer) do
property :title
end
describe "collection" do
representer!(:inject => :song_representer) do
collection :songs, :parse_strategy => :find_or_instantiate, :extend => song_representer, :class => Song
end
let (:album) { Struct.new(:songs).new([]).extend(representer) }
it "replaces the existing collection with a new consisting of existing items or new items" do
songs_id = album.songs.object_id
album.from_hash({"songs"=>[{"id" => 1, "title"=>"Resist Stance"}, {"title"=>"Suffer"}]})
album.songs[0].title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
album.songs[0].id.must_equal 1
album.songs[1].title.must_equal "Suffer"
album.songs[1].id.must_equal nil
album.songs.object_id.wont_equal songs_id
end
# TODO: test with existing collection
end
describe "property" do
representer!(:inject => :song_representer) do
property :song, :parse_strategy => :find_or_instantiate, :extend => song_representer, :class => Song
end
let (:album) { Struct.new(:song).new.extend(representer) }
it "finds song by id" do
album.from_hash({"song"=>{"id" => 1, "title"=>"Resist Stance"}})
album.song.title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
album.song.id.must_equal 1
end
it "creates song" do
album.from_hash({"song"=>{"title"=>"Off The Track"}})
album.song.title.must_equal "Off The Track"
album.song.id.must_equal nil
end
end
describe "property with dynamic :class" do
representer!(:inject => :song_representer) do
property :song, :parse_strategy => :find_or_instantiate, :extend => song_representer,
:class => lambda { |options| options[:fragment]["class"] }
end
let (:album) { Struct.new(:song).new.extend(representer) }
it "finds song by id" do
album.from_hash({"song"=>{"id" => 1, "title"=>"Resist Stance", "class"=>Song}})
album.song.title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
album.song.id.must_equal 1
end
end
end
class ParseStrategyLambdaTest < MiniTest::Spec
Song = Struct.new(:id, :title)
Song.class_eval do
def self.find_by(attributes={})
return new(1, "Resist Stan") if attributes[:id]==1# we should return the same object here
new
end
end
representer!(:name => :song_representer) do
property :title
end
# property with instance: lambda, using representable's setter. # TODO: that should be handled better via my api.
describe "property parse_strategy: lambda, representable: false" do
representer! do
property :title,
:instance => lambda { |options| options[:fragment].to_s }, # this will still call song.title= "8675309".
:representable => false # don't call object.from_hash
end
let (:song) { Song.new(nil, nil) }
it { song.extend(representer).from_hash("title" => 8675309).title.must_equal "8675309" }
end
describe "collection" do
representer!(:inject => :song_representer) do
collection :songs, :parse_strategy => lambda { |options|
songs << song = Song.new
song
}, :extend => song_representer
end
let (:album) { Struct.new(:songs).new([Song.new(1, "A Walk")]).extend(representer) }
it "adds to existing collection" do
songs_id = album.songs.object_id
album.from_hash({"songs"=>[{"title"=>"Resist Stance"}]})
album.songs[0].title.must_equal "A Walk" # note how title is updated from "Resist Stan"
album.songs[0].id.must_equal 1
album.songs[1].title.must_equal "Resist Stance"
album.songs[1].id.must_equal nil
album.songs.object_id.must_equal songs_id
end
# TODO: test with existing collection
end
end