representable-3.2.0/0000755000004100000410000000000014240717435014406 5ustar www-datawww-datarepresentable-3.2.0/test/0000755000004100000410000000000014240717435015365 5ustar www-datawww-datarepresentable-3.2.0/test/binding_test.rb0000644000004100000410000000343314240717435020366 0ustar www-datawww-datarequire 'test_helper' class BindingTest < MiniTest::Spec Binding = Representable::Binding let(:render_nil_definition) { Representable::Definition.new(:song, :render_nil => true) } describe "#skipable_empty_value?" do let(:binding) { Binding.new(render_nil_definition) } # don't skip when present. it { _(binding.skipable_empty_value?("Disconnect, Disconnect")).must_equal false } # don't skip when it's nil and render_nil: true it { _(binding.skipable_empty_value?(nil)).must_equal false } # skip when nil and :render_nil undefined. it { _(Binding.new(Representable::Definition.new(:song)).skipable_empty_value?(nil)).must_equal true } # don't skip when nil and :render_nil undefined. it { _(Binding.new(Representable::Definition.new(:song)).skipable_empty_value?("Fatal Flu")).must_equal false } end describe "#default_for" do let(:definition) { Representable::Definition.new(:song, :default => "Insider") } let(:binding) { Binding.new(definition) } # return value when value present. it { _(binding.default_for("Black And Blue")).must_equal "Black And Blue" } # return false when value false. it { _(binding.default_for(false)).must_equal false } # return default when value nil. it { _(binding.default_for(nil)).must_equal "Insider" } # return nil when value nil and render_nil: true. it { _(Binding.new(render_nil_definition).default_for(nil)).must_be_nil } # return nil when value nil and render_nil: true, even when :default is set" do it { _(Binding.new(Representable::Definition.new(:song, :render_nil => true, :default => "The Quest")).default_for(nil)).must_be_nil } # return nil if no :default it { _(Binding.new(Representable::Definition.new(:song)).default_for(nil)).must_be_nil } end endrepresentable-3.2.0/test/decorator_test.rb0000644000004100000410000000630614240717435020740 0ustar www-datawww-datarequire 'test_helper' class DecoratorTest < MiniTest::Spec class SongRepresentation < Representable::Decorator include Representable::JSON property :name end class AlbumRepresentation < Representable::Decorator include Representable::JSON collection :songs, :class => Song, :extend => SongRepresentation end class RatingRepresentation < Representable::Decorator include Representable::JSON property :system property :value end let(:song) { Song.new("Mama, I'm Coming Home") } let(:album) { Album.new([song]) } let(:rating) { OpenStruct.new(system: 'MPAA', value: 'R') } describe "inheritance" do let(:inherited_decorator) do Class.new(AlbumRepresentation) do property :best_song end.new(Album.new([song], "Stand Up")) end it { _(inherited_decorator.to_hash).must_equal({"songs"=>[{"name"=>"Mama, I'm Coming Home"}], "best_song"=>"Stand Up"}) } end let(:decorator) { AlbumRepresentation.new(album) } let(:rating_decorator) { RatingRepresentation.new(rating) } it "renders" do _(decorator.to_hash).must_equal({"songs"=>[{"name"=>"Mama, I'm Coming Home"}]}) _(album).wont_respond_to :to_hash _(song).wont_respond_to :to_hash # DISCUSS: weak test, how to assert blank slate? # no @representable_attrs in decorated objects _(song).wont_be(:instance_variable_defined?, :@representable_attrs) _(rating_decorator.to_hash).must_equal({"system" => "MPAA", "value" => "R"}) end describe "#from_hash" do it "returns represented" do _(decorator.from_hash({"songs"=>[{"name"=>"Mama, I'm Coming Home"}]})).must_equal album end it "parses" do decorator.from_hash({"songs"=>[{"name"=>"Atomic Garden"}]}) _(album.songs.first).must_be_kind_of Song _(album.songs).must_equal [Song.new("Atomic Garden")] _(album).wont_respond_to :to_hash _(song).wont_respond_to :to_hash # DISCUSS: weak test, how to assert blank slate? end end describe "#decorated" do it "is aliased to #represented" do _(AlbumRepresentation.prepare(album).decorated).must_equal album end end describe "inline decorators" do representer!(decorator: true) do collection :songs, :class => Song do property :name end end it "does not pollute represented" do representer.new(album).from_hash({"songs"=>[{"name"=>"Atomic Garden"}]}) # no @representable_attrs in decorated objects _(song).wont_be(:instance_variable_defined?, :@representable_attrs) _(album).wont_be(:instance_variable_defined?, :@representable_attrs) end end end require "uber/inheritable_attr" class InheritanceWithDecoratorTest < MiniTest::Spec class Twin extend Uber::InheritableAttr inheritable_attr :representer_class self.representer_class = Class.new(Representable::Decorator){ include Representable::Hash } end class Album < Twin representer_class.property :title # Twin.representer_class.clone end class Song < Twin # Twin.representer_class.clone end it do _(Twin.representer_class.definitions.size).must_equal 0 _(Album.representer_class.definitions.size).must_equal 1 _(Song.representer_class.definitions.size).must_equal 0 end endrepresentable-3.2.0/test/exec_context_test.rb0000644000004100000410000000670014240717435021444 0ustar www-datawww-datarequire 'test_helper' class ExecContextTest < MiniTest::Spec for_formats( :hash => [Representable::Hash, {Song => "Rebel Fate"}, {Song=>"Timing"}], # :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything/open_struct>"], # :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"], ) do |format, mod, input, output| let(:song) { representer.prepare(Song.new("Timing")) } let(:format) { format } describe "exec_context: nil" do representer!(:module => mod) do property :name, :as => lambda { |*| self.class } end it { render(song).must_equal_document output } it { _(parse(song, input).name).must_equal "Rebel Fate" } end describe "exec_context: :decorator" do representer!(:module => mod) do property :name, :as => lambda { |*| self.class }, :exec_context => :decorator end it { render(song).must_equal_document output } it { _(parse(song, input).name).must_equal "Rebel Fate" } end describe "exec_context: :binding" do representer!(:module => mod) do property :name, :as => lambda { |*| self.class }, # to actually test :exec_context => :binding, :setter => lambda { |options| options[:represented].name = options[:fragment] # to make parsing work. } end it { render(song).must_equal_document({Representable::Hash::Binding => "name"}) } it { _(parse(song, {Representable::Hash::Binding => "Rebel Fate"}).name).must_equal "Rebel Fate" } end describe "Decorator" do # DISCUSS: do we need this test? describe "exec_context: nil" do representer!(:module => mod, :decorator => true) do property :name, :as => lambda { |*| self.class } end it { render(song).must_equal_document output } it { _(parse(song, input).name).must_equal "Rebel Fate" } end describe "exec_context: :decorator" do # this tests if lambdas are run in the right context, if methods are called in the right context and if we can access the represented object. representer!(:module => mod, :decorator => true) do property :name, :as => lambda { |*| self.class.superclass }, :exec_context => :decorator define_method :name do # def in Decorator class. "Timebomb" end define_method :"name=" do |v| # def in Decorator class. represented.name = v end end it { render(song).must_equal_document({Representable::Decorator=>"Timebomb"}) } it { _(parse(song, {Representable::Decorator=>"Listless"}).name).must_equal "Listless" } end # DISCUSS: do we need this test? describe "exec_context: :binding" do representer!(:module => mod, :decorator => true) do property :name, :as => lambda { |*| self.class }, # to actually test :exec_context => :binding, :setter => lambda { |options| options[:represented].name = options[:fragment ] # to make parsing work. } end it { render(song).must_equal_document({Representable::Hash::Binding => "name"}) } it("xxx") { _(parse(song, {Representable::Hash::Binding => "Rebel Fate"}).name).must_equal "Rebel Fate" } end end end endrepresentable-3.2.0/test/xml_bindings_test.rb0000644000004100000410000000313414240717435021427 0ustar www-datawww-datarequire 'test_helper' require 'representable/xml/hash' class XMLBindingTest < MiniTest::Spec module SongRepresenter include Representable::XML property :name self.representation_wrap = :song end class SongWithRepresenter < ::Song include Representable include SongRepresenter self.representation_wrap = :song end before do @doc = Nokogiri::XML::Document.new @song = SongWithRepresenter.new("Thinning the Herd") end describe "AttributeBinding" do describe "with plain text items" do before do @property = Representable::XML::Binding::Attribute.new(Representable::Definition.new(:name, :attribute => true)) end it "extracts with #read" do assert_equal "The Gargoyle", @property.read(Nokogiri::XML("").root, "name") end it "inserts with #write" do parent = Nokogiri::XML::Node.new("song", @doc) @property.write(parent, "The Gargoyle", "name") assert_xml_equal("", parent.to_s) end end end describe "ContentBinding" do before do @property = Representable::XML::Binding::Content.new(Representable::Definition.new(:name, :content => true)) end it "extracts with #read" do assert_equal "The Gargoyle", @property.read(Nokogiri::XML("The Gargoyle").root, "song") end it "inserts with #write" do parent = Nokogiri::XML::Node.new("song", @doc) @property.write(parent, "The Gargoyle", "song") assert_xml_equal("The Gargoyle", parent.to_s) end end end representable-3.2.0/test/instance_test.rb0000644000004100000410000002005014240717435020552 0ustar www-datawww-datarequire 'test_helper' class InstanceTest < BaseTest Song = Struct.new(:id, :title) Song.class_eval do def self.find(id) new(id, "Invincible") end end describe "lambda { fragment } (new way of class: lambda { nil })" do representer! do property :title, :instance => lambda { |options| options[:fragment] } end it "skips creating new instance" do object = Object.new object.instance_eval do def from_hash(hash, *args) hash end end song = OpenStruct.new.extend(representer).from_hash({"title" => object}) _(song.title).must_equal object end end # TODO: use *args in from_hash. # DISCUSS: do we need parse_strategy? describe "property with :instance" do representer!(:inject => :song_representer) do property :song, :instance => lambda { |options| options[:fragment]["id"] == song.id ? song : Song.find(options[:fragment]["id"]) }, :extend => song_representer end it( "xxx") { _(OpenStruct.new(:song => Song.new(1, "The Answer Is Still No")).extend(representer). from_hash("song" => {"id" => 1}).song).must_equal Song.new(1, "The Answer Is Still No") } it { _(OpenStruct.new(:song => Song.new(1, "The Answer Is Still No")).extend(representer). from_hash("song" => {"id" => 2}).song).must_equal Song.new(2, "Invincible") } end describe "collection with :instance" do representer!(:inject => :song_representer) do collection :songs, :instance => lambda { |options| options[:fragment]["id"] == songs[options[:index]].id ? songs[options[:index]] : Song.find(options[:fragment]["id"]) }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync. :extend => song_representer end it { album= Struct.new(:songs).new([ Song.new(1, "The Answer Is Still No"), Song.new(2, "")]) _(album. extend(representer). from_hash("songs" => [{"id" => 2},{"id" => 2, "title"=>"The Answer Is Still No"}]).songs).must_equal [ Song.new(2, "Invincible"), Song.new(2, "The Answer Is Still No")] } end describe "property with lambda receiving fragment and args" do representer!(:inject => :song_representer) do property :song, :instance => lambda { |options| Struct.new(:args, :id).new([options[:fragment], options[:user_options]]) }, :extend => song_representer end it { _(OpenStruct.new(:song => Song.new(1, "The Answer Is Still No")).extend(representer). from_hash({"song" => {"id" => 1}}, user_options: { volume: 1 }).song.args).must_equal([{"id"=>1}, {:volume=>1}]) } end # TODO: raise and test instance:{nil} # describe "property with instance: { nil }" do # TODO: introduce :representable option? # representer!(:inject => :song_representer) do # property :song, :instance => lambda { |*| nil }, :extend => song_representer # end # let(:hit) { hit = OpenStruct.new(:song => song).extend(representer) } # it "calls #to_hash on song instance, nothing else" do # hit.to_hash.must_equal("song"=>{"title"=>"Resist Stance"}) # end # it "calls #from_hash on the existing song instance, nothing else" do # song_id = hit.song.object_id # hit.from_hash("song"=>{"title"=>"Suffer"}) # hit.song.title.must_equal "Suffer" # hit.song.object_id.must_equal song_id # end # end # lambda { |fragment, i, Context(binding: <..>, args: [..])| } describe "sync" do representer!(:inject => :song_representer) do collection :songs, :instance => lambda { |options| songs[options[:index]] }, :extend => song_representer, # :parse_strategy => :sync :setter => lambda { |*| } end it { album= Struct.new(:songs).new(songs = [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invncble")]) album. extend(representer). from_hash("songs" => [{"title" => "The Answer Is Still No"}, {"title" => "Invincible"}]) _(album.songs).must_equal [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invincible")] _(songs.object_id).must_equal album.songs.object_id _(songs[0].object_id).must_equal album.songs[0].object_id _(songs[1].object_id).must_equal album.songs[1].object_id } end describe "update existing elements, only" do representer!(:inject => :song_representer) do collection :songs, :instance => lambda { |options| #fragment["id"] == songs[i].id ? songs[i] : Song.find(fragment["id"]) songs.find { |s| s.id == options[:fragment]["id"] } }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync. :extend => song_representer, # :parse_strategy => :sync :setter => lambda { |*| } end it("hooray") { album= Struct.new(:songs).new(songs = [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invncble")]) _(album. extend(representer). from_hash("songs" => [{"id" => 2, "title" => "Invincible"}]). songs).must_equal [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invincible")] # TODO: check elements object_id! _(songs.object_id).must_equal album.songs.object_id _(songs[0].object_id).must_equal album.songs[0].object_id _(songs[1].object_id).must_equal album.songs[1].object_id } end describe "add incoming elements, only" do representer!(:inject => :song_representer) do collection :songs, :instance => lambda { |options| songs << song=Song.new(2) song }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync. :extend => song_representer, # :parse_strategy => :sync :setter => lambda { |*| } end it { album= Struct.new(:songs).new(songs = [ Song.new(1, "The Answer Is Still No")]) _(album. extend(representer). from_hash("songs" => [{"title" => "Invincible"}]). songs).must_equal [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invincible")] _(songs.object_id).must_equal album.songs.object_id _(songs[0].object_id).must_equal album.songs[0].object_id } end # not sure if this must be a library strategy describe "replace existing element" do representer!(:inject => :song_representer) do collection :songs, :instance => lambda { |options| id = options[:fragment].delete("replace_id") replaced = songs.find { |s| s.id == id } songs[songs.index(replaced)] = song=Song.new(3) song }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync. :extend => song_representer, # :parse_strategy => :sync :setter => lambda { |*| } end it { album= Struct.new(:songs).new(songs = [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invincible")]) _(album. extend(representer). from_hash("songs" => [{"replace_id"=>2, "id" => 3, "title" => "Soulmate"}]). songs).must_equal [ Song.new(1, "The Answer Is Still No"), Song.new(3, "Soulmate")] _(songs.object_id).must_equal album.songs.object_id _(songs[0].object_id).must_equal album.songs[0].object_id } end describe "replace collection" do representer!(:inject => :song_representer) do collection :songs, :extend => song_representer, :class => Song end it { album= Struct.new(:songs).new(songs = [ Song.new(1, "The Answer Is Still No")]) _(album. extend(representer). from_hash("songs" => [{"title" => "Invincible"}]). songs).must_equal [ Song.new(nil, "Invincible")] _(songs.object_id).wont_equal album.songs.object_id } end endrepresentable-3.2.0/test/for_collection_test.rb0000644000004100000410000000430114240717435021750 0ustar www-datawww-datarequire 'test_helper' class ForCollectionTest < MiniTest::Spec module SongRepresenter include Representable::JSON property :name end let(:songs) { [Song.new("Days Go By"), Song.new("Can't Take Them All")] } let(:json) { "[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]" } # Module.for_collection # Decorator.for_collection for_formats( :hash => [Representable::Hash, out=[{"name" => "Days Go By"}, {"name"=>"Can't Take Them All"}], out], :json => [Representable::JSON, out="[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]", out], # :xml => [Representable::XML, out="", out] ) do |format, mod, output, input| describe "Module::for_collection [#{format}]" do let(:format) { format } let(:representer) { Module.new do include mod property :name#, :as => :title collection_representer :class => Song # self.representation_wrap = :songs if format == :xml end } it { render(songs.extend(representer.for_collection)).must_equal_document output } it { render(representer.for_collection.prepare(songs)).must_equal_document output } # parsing needs the class set, at least it { _(parse([].extend(representer.for_collection), input)).must_equal songs } end describe "Module::for_collection without configuration [#{format}]" do let(:format) { format } let(:representer) { Module.new do include mod property :name end } # rendering works out of the box, no config necessary it { render(songs.extend(representer.for_collection)).must_equal_document output } end describe "Decorator::for_collection [#{format}]" do let(:format) { format } let(:representer) { Class.new(Representable::Decorator) do include mod property :name collection_representer :class => Song end } it { render(representer.for_collection.new(songs)).must_equal_document output } it { _(parse(representer.for_collection.new([]), input)).must_equal songs } end end # with module including module endrepresentable-3.2.0/test/default_test.rb0000644000004100000410000000222314240717435020374 0ustar www-datawww-datarequire "test_helper" class DefaultTest < MiniTest::Spec Song = Struct.new(:id, :title) representer! do property :id property :title, default: "Huber Breeze" #->(options) { options[:default] } end describe "#from_hash" do let(:song) { Song.new.extend(representer) } it { _(song.from_hash({})).must_equal Song.new(nil, "Huber Breeze") } # default doesn't apply when empty string. it { _(song.from_hash({"title"=>""})).must_equal Song.new(nil, "") } it { _(song.from_hash({"title"=>nil})).must_equal Song.new(nil, nil) } it { _(song.from_hash({"title"=>"Blindfold"})).must_equal Song.new(nil, "Blindfold") } end describe "#to_json" do it "uses :default when not available from object" do _(Song.new.extend(representer).to_hash).must_equal({"title"=>"Huber Breeze"}) end it "uses value from represented object when present" do _(Song.new(nil, "After The War").extend(representer).to_hash).must_equal({"title"=>"After The War"}) end it "uses value from represented object when emtpy string" do _(Song.new(nil, "").extend(representer).to_hash).must_equal({"title"=>""}) end end endrepresentable-3.2.0/test/features_test.rb0000644000004100000410000000321014240717435020563 0ustar www-datawww-datarequire 'test_helper' class FeaturesTest < MiniTest::Spec module Title def title; "Is It A Lie"; end end module Length def length; "2:31"; end end definition = lambda { feature Title feature Length # exec_context: :decorator, so the readers are called on the Decorator instance (which gets readers from features!). property :title, exec_context: :decorator property :length, exec_context: :decorator property :details do property :title, exec_context: :decorator end } let(:song) { OpenStruct.new(:details => Object.new) } describe "Module" do representer! do instance_exec(&definition) end it { _(song.extend(representer).to_hash).must_equal({"title"=>"Is It A Lie", "length"=>"2:31", "details"=>{"title"=>"Is It A Lie"}}) } end describe "Decorator" do representer!(:decorator => true) do instance_exec(&definition) end it { _(representer.new(song).to_hash).must_equal({"title"=>"Is It A Lie", "length"=>"2:31", "details"=>{"title"=>"Is It A Lie"}}) } end end class FeatureInclusionOrderTest < MiniTest::Spec module Title def title "I was first!" end end module OverridingTitle def title "I am number two, " + super end end representer!(decorator: true) do feature Title feature OverridingTitle property :title, exec_context: :decorator property :song do property :title, exec_context: :decorator end end it do _(representer.new(OpenStruct.new(song: Object)).to_hash).must_equal({"title"=>"I am number two, I was first!", "song"=>{"title"=>"I am number two, I was first!"}}) end endrepresentable-3.2.0/test/lonely_test.rb0000644000004100000410000001713514240717435020262 0ustar www-datawww-datarequire 'test_helper' require 'representable/json/hash' class LonelyRepresenterTest < MiniTest::Spec # test ::items without arguments, render-only. for_formats( :hash => [Representable::Hash::Collection, [{"name"=>"Resist Stance"}, {"name"=>"Suffer"}]], :json => [Representable::JSON::Collection, "[{\"name\":\"Resist Stance\"},{\"name\":\"Suffer\"}]"], :xml => [Representable::XML::Collection, "Resist StanceSuffer"], ) do |format, mod, output, input| describe "[#{format}] lonely collection, render-only" do # TODO: introduce :representable option? let(:format) { format } representer!(module: mod) do items do property :name end end let(:album) { [Song.new("Resist Stance"), Song.new("Suffer")].extend(representer) } it "calls #to_hash on song instances, nothing else" do render(album).must_equal_document(output) end end end module SongRepresenter include Representable::JSON property :name end let(:decorator) { rpr = representer; Class.new(Representable::Decorator) { include Representable::Hash; include rpr } } describe "JSON::Collection" do let(:songs) { [Song.new("Days Go By"), Song.new("Can't Take Them All")] } let(:json) { "[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]" } describe "with contained objects" do let(:representer) { Module.new do include Representable::JSON::Collection items :class => Song, :extend => SongRepresenter end } it "renders array" do assert_json json, songs.extend(representer).to_json end it "renders array with decorator" do assert_json json, decorator.new(songs).to_json end it "parses array" do _([].extend(representer).from_json(json)).must_equal songs end it "parses array with decorator" do _(decorator.new([]).from_json(json)).must_equal songs end end describe "with inline representer" do representer!(:module => Representable::JSON::Collection) do items :class => Song do property :name end end it { _(songs.extend(representer).to_json).must_equal json } it { _([].extend(representer).from_json(json)).must_equal songs } end describe "with contained text" do let(:representer) { Module.new do include Representable::JSON::Collection end } let(:songs) { ["Days Go By", "Can't Take Them All"] } let(:json) { "[\"Days Go By\",\"Can't Take Them All\"]" } it "renders contained items #to_json" do assert_json json, songs.extend(representer).to_json end it "returns objects array from #from_json" do _([].extend(representer).from_json(json)).must_equal songs end end end describe "Hash::Collection with dynamic options" do class One < Representable::Decorator def to_hash(*); "One: #{represented}"; end end class Two < Representable::Decorator def to_hash(*); "Two: #{represented}"; end end representer!(module: Representable::Hash::Collection) do items extend: ->(options) { options[:input] == 1 ? options[:user_options][:one] : options[:user_options][:two] } end it { _([1,2].extend(representer).to_hash(user_options: {one: One, two: Two})).must_equal(["One: 1", "Two: 2"]) } end describe "JSON::Hash" do # TODO: move to HashTest. describe "with contained objects" do let(:representer) { Module.new do include Representable::JSON::Hash values :class => Song, :extend => SongRepresenter end } let(:json) { "{\"one\":{\"name\":\"Days Go By\"},\"two\":{\"name\":\"Can't Take Them All\"}}" } let(:songs) { {"one" => Song.new("Days Go By"), "two" => Song.new("Can't Take Them All")} } describe "#to_json" do it "renders hash" do _(songs.extend(representer).to_json).must_equal json end it "renders hash with decorator" do _(decorator.new(songs).to_json).must_equal json end it "respects :exclude" do assert_json "{\"two\":{\"name\":\"Can't Take Them All\"}}", {:one => Song.new("Days Go By"), :two => Song.new("Can't Take Them All")}.extend(representer).to_json(:exclude => [:one]) end it "respects :include" do assert_json "{\"two\":{\"name\":\"Can't Take Them All\"}}", {:one => Song.new("Days Go By"), :two => Song.new("Can't Take Them All")}.extend(representer).to_json(:include => [:two]) end end describe "#from_json" do it "returns objects array" do _({}.extend(representer).from_json(json)).must_equal songs end it "parses hash with decorator" do _(decorator.new({}).from_json(json)).must_equal songs end it "respects :exclude" do assert_equal({"two" => Song.new("Can't Take Them All")}, {}.extend(representer).from_json(json, :exclude => [:one])) end it "respects :include" do assert_equal({"one" => Song.new("Days Go By")}, {}.extend(representer).from_json(json, :include => [:one])) end end describe "with inline representer" do representer!(:module => Representable::JSON::Hash) do values :class => Song do property :name end end it { _(songs.extend(representer).to_json).must_equal json } it { _({}.extend(representer).from_json(json)).must_equal songs } end end describe "with scalar" do let(:representer) { Module.new do include Representable::JSON::Hash end } let(:json) { %{{"one":1,"two":2}} } let(:data) { {one: 2, two: 3} } describe "#to_json" do it { _(data.extend(representer).to_json).must_equal %{{"one":2,"two":3}} } # it "respects :exclude" do # assert_json "{\"two\":{\"name\":\"Can't Take Them All\"}}", {:one => Song.new("Days Go By"), :two => Song.new("Can't Take Them All")}.extend(representer).to_json(:exclude => [:one]) # end # it "respects :include" do # assert_json "{\"two\":{\"name\":\"Can't Take Them All\"}}", {:one => Song.new("Days Go By"), :two => Song.new("Can't Take Them All")}.extend(representer).to_json(:include => [:two]) # end end describe "#from_json" do # FIXME: what's the point of this? it { _(data.extend(representer).from_hash(data)).must_equal data } end end describe "with contained text" do before do @songs_representer = Module.new do include Representable::JSON::Collection end end it "renders contained items #to_json" do assert_json "[\"Days Go By\",\"Can't Take Them All\"]", ["Days Go By", "Can't Take Them All"].extend(@songs_representer).to_json end it "returns objects array from #from_json" do assert_equal ["Days Go By", "Can't Take Them All"], [].extend(@songs_representer).from_json("[\"Days Go By\",\"Can't Take Them All\"]") end end end end # describe "Hash::Collection with :include" do class CollectionWithIncludeTest < MiniTest::Spec Song = Struct.new(:id, :title) representer!(decorator: true, module: Representable::Hash::Collection) do items do property :id property :title end end it { _(representer.new([Song.new(1, "ACAB")]).to_hash).must_equal([{"id"=>1, "title"=>"ACAB"}]) } it { _(representer.new([Song.new(1, "ACAB")]).to_hash(include: [:title])).must_equal([{"title"=>"ACAB"}]) } end representable-3.2.0/test/definition_test.rb0000644000004100000410000001646014240717435021110 0ustar www-datawww-datarequire 'test_helper' class DefinitionTest < MiniTest::Spec Definition = Representable::Definition # TODO: test that we DON'T clone options, that must happen in describe "#initialize" do it do # new yields the defaultized options HASH. definition = Definition.new(:song, :extend => Module) do |options| options[:awesome] = true options[:parse_filter] << 1 # default variables _(options[:as]).must_be_nil _(options[:extend]).must_equal Module end _(definition.name).must_equal "song" # _(definition[:awesome]).must_equal true _(definition[:parse_filter]).must_equal Representable::Pipeline[1] _(definition[:render_filter]).must_equal Representable::Pipeline[] end end describe "#[]" do let(:definition) { Definition.new(:song) } # default is nil. it { _(definition[:bla]).must_be_nil } end # merge! describe "#merge!" do let(:definition) { Definition.new(:song, :whatever => true) } # merges new options. it { _(definition.merge!(:something => true)[:something]).must_equal true } # doesn't override original options. it { _(definition.merge!({:something => true})[:whatever]).must_equal true } # override original when passed in #merge!. it { _(definition.merge!({:whatever => false})[:whatever]).must_equal false } # with block it do definition = Definition.new(:song, :extend => Module).merge!({:something => true}) do |options| options[:awesome] = true options[:render_filter] << 1 # default variables # options[:as].must_equal "song" # options[:extend].must_equal Module end _(definition[:awesome]).must_equal true _(definition[:something]).must_equal true _(definition[:render_filter]).must_equal Representable::Pipeline[1] _(definition[:parse_filter]).must_equal Representable::Pipeline[] end describe "with :parse_filter" do let(:definition) { Definition.new(:title, :parse_filter => 1) } # merges :parse_filter and :render_filter. it do merged = definition.merge!(:parse_filter => 2)[:parse_filter] _(merged).must_be_kind_of Representable::Pipeline _(merged.size).must_equal 2 end # :parse_filter can also be array. it { _(definition.merge!(:parse_filter => [2, 3])[:parse_filter].size).must_equal 3 } end # does not change arguments it do Definition.new(:title).merge!(options = {:whatever => 1}) _(options).must_equal(:whatever => 1) end end # delete! describe "#delete!" do let(:definition) { Definition.new(:song, serialize: "remove me!") } before { _(definition[:serialize].(nil)).must_equal "remove me!" } it { _(definition.delete!(:serialize)[:serialize]).must_be_nil } end # #inspect describe "#inspect" do it { _(Definition.new(:songs).inspect).must_equal "#songs @options={:name=>\"songs\", :parse_filter=>[], :render_filter=>[]}>" } end describe "generic API" do before do @def = Representable::Definition.new(:songs) end it "responds to #representer_module" do assert_nil Representable::Definition.new(:song).representer_module assert_equal Hash, Representable::Definition.new(:song, :extend => Hash).representer_module end describe "#typed?" do it "is false per default" do refute @def.typed? end it "is true when :class is present" do assert Representable::Definition.new(:songs, :class => Hash).typed? end it "is true when :extend is present, only" do assert Representable::Definition.new(:songs, :extend => Hash).typed? end it "is true when :instance is present, only" do assert Representable::Definition.new(:songs, :instance => Object.new).typed? end end describe "#representable?" do it { assert Definition.new(:song, :representable => true).representable? } it { _(Definition.new(:song, :representable => true, :extend => Object).representable?).must_equal true } it { refute Definition.new(:song, :representable => false, :extend => Object).representable? } it { assert Definition.new(:song, :extend => Object).representable? } it { refute Definition.new(:song).representable? } end it "responds to #getter and returns string" do assert_equal "songs", @def.getter end it "responds to #name" do assert_equal "songs", @def.name end it "responds to #setter" do assert_equal :"songs=", @def.setter end describe "nested: FIXME" do it do dfn = Representable::Definition.new(:songs, nested: Module) assert dfn.typed? _(dfn[:extend].(nil)).must_equal Module end end describe "#clone" do subject { Representable::Definition.new(:title, :volume => 9, :clonable => ::Representable::Option(1)) } it { _(subject.clone).must_be_kind_of Representable::Definition } it { _(subject.clone[:clonable].(nil)).must_equal 1 } it "clones @options" do @def.merge!(:volume => 9) cloned = @def.clone cloned.merge!(:volume => 8) assert_equal @def[:volume], 9 assert_equal cloned[:volume], 8 end end end describe "#has_default?" do it "returns false if no :default set" do refute Representable::Definition.new(:song).has_default? end it "returns true if :default set" do assert Representable::Definition.new(:song, :default => nil).has_default? end end describe "#binding" do it "returns true when :binding is set" do assert Representable::Definition.new(:songs, :binding => Object)[:binding] end it "returns false when :binding is not set" do refute Representable::Definition.new(:songs)[:binding] end end describe "#create_binding" do it "executes the block (without special context)" do definition = Representable::Definition.new(:title, :binding => lambda { |*args| @binding = Representable::Binding.new(*args) }) _(definition.create_binding).must_equal @binding end end describe ":collection => true" do before do @def = Representable::Definition.new(:songs, :collection => true, :tag => :song) end it "responds to #array?" do assert @def.array? end end describe ":default => value" do it "responds to #default" do @def = Representable::Definition.new(:song) assert_nil @def[:default] end it "accepts a default value" do @def = Representable::Definition.new(:song, :default => "Atheist Peace") assert_equal "Atheist Peace", @def[:default] end end describe ":hash => true" do before do @def = Representable::Definition.new(:songs, :hash => true) end it "responds to #hash?" do assert @def.hash? refute Representable::Definition.new(:songs).hash? end end describe ":binding => Object" do subject do Representable::Definition.new(:songs, :binding => Object) end it "responds to #binding" do assert_equal subject[:binding], Object end end describe "#[]=" do it "raises exception since it's deprecated" do assert_raises NoMethodError do Definition.new(:title)[:extend] = Module.new # use merge! after initialize. end end end end representable-3.2.0/test/include_exclude_test.rb0000644000004100000410000000535714240717435022117 0ustar www-datawww-datarequire "test_helper" class IncludeExcludeTest < Minitest::Spec Song = Struct.new(:title, :artist, :id) Artist = Struct.new(:name, :id, :songs) representer!(decorator: true) do property :title property :artist, class: Artist do property :name property :id collection :songs, class: Song do property :title property :id end end end let(:song) { Song.new("Listless", Artist.new("7yearsbadluck", 1 )) } let(:decorator) { representer.new(song) } describe "#from_hash" do it "accepts :exclude option" do decorator.from_hash({"title"=>"Don't Smile In Trouble", "artist"=>{"id"=>2}}, exclude: [:title]) _(song.title).must_equal "Listless" _(song.artist).must_equal Artist.new(nil, 2) end it "accepts :include option" do decorator.from_hash({"title"=>"Don't Smile In Trouble", "artist"=>{"id"=>2}}, include: [:title]) _(song.title).must_equal "Don't Smile In Trouble" _(song.artist).must_equal Artist.new("7yearsbadluck", 1) end it "accepts nested :exclude/:include option" do decorator.from_hash({"title"=>"Don't Smile In Trouble", "artist"=>{"name"=>"Foo", "id"=>2, "songs"=>[{"id"=>1, "title"=>"Listless"}]}}, exclude: [:title], artist: { exclude: [:id], songs: { include: [:title] } } ) _(song.title).must_equal "Listless" _(song.artist).must_equal Artist.new("Foo", nil, [Song.new("Listless", nil, nil)]) end end describe "#to_hash" do it "accepts :exclude option" do _(decorator.to_hash(exclude: [:title])).must_equal({"artist"=>{"name"=>"7yearsbadluck", "id"=>1}}) end it "accepts :include option" do _(decorator.to_hash(include: [:title])).must_equal({"title"=>"Listless"}) end it "accepts nested :exclude/:include option" do decorator = representer.new(Song.new("Listless", Artist.new("7yearsbadluck", 1, [Song.new("C.O.A.B.I.E.T.L.")]))) _(decorator.to_hash( exclude: [:title], artist: { exclude: [:id], songs: { include: [:title] } } )).must_equal({"artist"=>{"name"=>"7yearsbadluck", "songs"=>[{"title"=>"C.O.A.B.I.E.T.L."}]}}) end end it "xdoes not propagate private options to nested objects" do Cover = Struct.new(:title, :original) cover_rpr = Module.new do include Representable::Hash property :title property :original, extend: self end # FIXME: we should test all representable-options (:include, :exclude, ?) _(Cover.new("Roxanne", Cover.new("Roxanne (Don't Put On The Red Light)")).extend(cover_rpr). to_hash(:include => [:original])).must_equal({"original"=>{"title"=>"Roxanne (Don't Put On The Red Light)"}}) end end representable-3.2.0/test/private_options_test.rb0000644000004100000410000000067514240717435022206 0ustar www-datawww-datarequire "test_helper" class PrivateOptionsTest < MiniTest::Spec # TODO: move me to separate file. representer!(decorator: true) do end options = {exclude: "name"} it "render: doesn't modify options" do representer.new(nil).to_hash(options) _(options).must_equal({exclude: "name"}) end it "parse: doesn't modify options" do representer.new(nil).from_hash(options) _(options).must_equal({exclude: "name"}) end endrepresentable-3.2.0/test/benchmarking.rb0000644000004100000410000000401114240717435020336 0ustar www-datawww-datarequire 'test_helper' require 'benchmark' SONG_PROPERTIES = 1000.times.collect do |i| "property_#{i}" end module SongRepresenter include Representable::JSON SONG_PROPERTIES.each { |p| property p } end class SongDecorator < Representable::Decorator include Representable::JSON SONG_PROPERTIES.each { |p| property p } end module AlbumRepresenter include Representable::JSON # collection :songs, extend: SongRepresenter collection :songs, extend: SongDecorator end def random_song attrs = Hash[SONG_PROPERTIES.collect { |n| [n,n] }] OpenStruct.new(attrs) end times = [] 3.times.each do album = OpenStruct.new(songs: 100.times.collect { random_song }) times << Benchmark.measure do puts "================ next!" album.extend(AlbumRepresenter).to_json end end puts times.join("") # 100 songs, 100 attrs # 0.050000 0.000000 0.050000 ( 0.093157) ## 100 songs, 1000 attrs # 0.470000 0.010000 0.480000 ( 0.483708) ### without binding cache: # 2.790000 0.030000 2.820000 ( 2.820190) ### with extend: on Song, with binding cache> # 2.490000 0.030000 2.520000 ( 2.517433) 2.4-3.0 ### without skip? # 2.030000 0.020000 2.050000 ( 2.050796) 2.1-2.3 ### without :writer # 2.270000 0.010000 2.280000 ( 2.284530 1.9-2.2 ### without :render_filter # 2.020000 0.000000 2.020000 ( 2.030234) 1.5-2.0 ###without default_for and skipable? # 1.730000 0.010000 1.740000 ( 1.735597 1.4-1.7 ### without :serialize # 1.780000 0.010000 1.790000 ( 1.786791) 1.4-1.7 ### using decorator # 1.400000 0.030000 1.430000 ( 1.434206) 1.4-1.6 ### with prepare AFTER representable? # 1.330000 0.010000 1.340000 ( 1.335900) 1.1-1.3 # representable 2.0 # 3.000000 0.020000 3.020000 ( 3.013031) 2.7-3.0 # no method missing # 2.280000 0.030000 2.310000 ( 2.313522) 2.2-2.5 # no def_delegator in Definition # 2.130000 0.010000 2.140000 ( 2.136115) 1.7-2.1 representable-3.2.0/test/config_test.rb0000644000004100000410000000630414240717435020221 0ustar www-datawww-datarequire 'test_helper' class ConfigTest < MiniTest::Spec subject { Representable::Config.new(Representable::Definition) } PunkRock = Class.new Definition = Representable::Definition describe "wrapping" do it "returns false per default" do assert_nil subject.wrap_for("Punk", nil) end # it "infers a printable class name if set to true" do # subject.wrap = true # assert_equal "punk_rock", subject.wrap_for(PunkRock, nil) # end # it "can be set explicitely" do # subject.wrap = "Descendents" # assert_equal "Descendents", subject.wrap_for(PunkRock, nil) # end end describe "#[]" do # does return nil for non-existent it { _(subject[:hello]).must_be_nil } end # describe "#[]" do # before { subject.add(:title, {:me => true}) } # it { subject[:unknown].must_be_nil } # it { subject.get(:title)[:me].must_equal true } # it { subject["title"][:me].must_equal true } # end # []= # []=(... inherit: true) # forwarded to Config#definitions # that goes to ConfigDefinitionsTest describe "#add" do describe "returns" do it do # #add returns Definition.` subject = Representable::Config.new(Representable::Definition).add(:title, {:me => true}) _(subject).must_be_kind_of Representable::Definition _(subject[:me]).must_equal true end end before { subject.add(:title, {:me => true}) } # must be kind of Definition it { _(subject.size).must_equal 1 } it { _(subject.get(:title).name).must_equal "title" } it { _(subject.get(:title)[:me]).must_equal true } # this is actually tested in context in inherit_test. it "overrides former definition" do subject.add(:title, {:peer => Module}) _(subject.get(:title)[:me]).must_be_nil _(subject.get(:title)[:peer]).must_equal Module end describe "inherit: true" do before { subject.add(:title, {:me => true}) subject.add(:title, {:peer => Module, :inherit => true}) } it { _(subject.get(:title)[:me]).must_equal true } it { _(subject.get(:title)[:peer]).must_equal Module } end end describe "#remove" do subject { Representable::Config.new(Representable::Definition) } it do subject.add(:title, {:me => true}) subject.add(:genre, {}) _(subject.get(:genre)).must_be_kind_of Representable::Definition subject.remove(:genre) _(subject.get(:genre)).must_be_nil end end describe "#each" do before { subject.add(:title, {:me => true}) } it "what" do definitions = [] subject.each { |dfn| definitions << dfn } _(definitions.size).must_equal 1 _(definitions[0][:me]).must_equal true end end describe "#options" do it { _(subject.options).must_equal({}) } it do subject.options[:namespacing] = true _(subject.options[:namespacing]).must_equal true end end describe "#get" do subject { Representable::Config.new(Representable::Definition) } it do title = subject.add(:title, {}) length = subject.add(:length, {}) _(subject.get(:title)).must_equal title _(subject.get(:length)).must_equal length end end endrepresentable-3.2.0/test/if_test.rb0000644000004100000410000000445714240717435017361 0ustar www-datawww-datarequire 'test_helper' class IfTest < MiniTest::Spec let(:band_class) { Class.new do include Representable::Hash attr_accessor :fame self end } it "respects property when condition true" do band_class.class_eval { property :fame, :if => lambda { |*| true } } band = band_class.new band.from_hash({"fame"=>"oh yes"}) assert_equal "oh yes", band.fame end it "ignores property when condition false" do band_class.class_eval { property :fame, :if => lambda { |*| false } } band = band_class.new band.from_hash({"fame"=>"oh yes"}) assert_nil band.fame end it "ignores property when :exclude'ed even when condition is true" do band_class.class_eval { property :fame, :if => lambda { |*| true } } band = band_class.new band.from_hash({"fame"=>"oh yes"}, {:exclude => [:fame]}) assert_nil band.fame end it "executes block in instance context" do band_class.class_eval { property :fame, :if => lambda { |*| groupies }; attr_accessor :groupies } band = band_class.new band.groupies = true band.from_hash({"fame"=>"oh yes"}) assert_equal "oh yes", band.fame end describe "executing :if lambda in represented instance context" do representer! do property :label, :if => lambda { |*| signed_contract } end subject { OpenStruct.new(:signed_contract => false, :label => "Fat") } it "skips when false" do _(subject.extend(representer).to_hash).must_equal({}) end it "represents when true" do subject.signed_contract= true _(subject.extend(representer).to_hash).must_equal({"label"=>"Fat"}) end it "works with decorator" do rpr = representer _(Class.new(Representable::Decorator) do include Representable::Hash include rpr end.new(subject).to_hash).must_equal({}) end end describe "propagating user options to the block" do representer! do property :name, :if => lambda { |opts| opts[:user_options][:include_name] } end subject { OpenStruct.new(:name => "Outbound").extend(representer) } it "works without specifying options" do _(subject.to_hash).must_equal({}) end it "passes user options to block" do _(subject.to_hash(user_options: { include_name: true })).must_equal({"name" => "Outbound"}) end end endrepresentable-3.2.0/test/prepare_test.rb0000644000004100000410000000334114240717435020410 0ustar www-datawww-datarequire 'test_helper' class PrepareTest < BaseTest class PreparerClass def initialize(object) @object = object end def ==(b) return unless b.instance_of?(PreparerClass) object == b.object end attr_reader :object end describe "#to_hash" do # TODO: introduce :representable option? representer! do property :song, :prepare => lambda { |options| options[:binding][:arbitrary].new(options[:input]) }, :arbitrary => PreparerClass, :extend => true, :representable => false # don't call #to_hash. end let(:hit) { Struct.new(:song).new(song).extend(representer) } it "calls prepare:, nothing else" do # render(hit).must_equal_document(output) _(hit.to_hash).must_equal({"song" => PreparerClass.new(song)}) 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 describe "#from_hash" do representer! do property :song, :prepare => lambda { |options| options[:binding][:arbitrary].new(options[:input]) }, :arbitrary => PreparerClass, #:extend => true, # TODO: typed: true would be better. :instance => String.new, # pass_fragment :pass_options => true, :representable => false # don't call #to_hash. end let(:hit) { Struct.new(:song).new.extend(representer) } it "calls prepare:, nothing else" do # render(hit).must_equal_document(output) hit.from_hash("song" => {}) _(hit.song).must_equal(PreparerClass.new(String.new)) end end endrepresentable-3.2.0/test/render_nil_test.rb0000644000004100000410000000076514240717435021102 0ustar www-datawww-datarequire "test_helper" class RenderNilTest < MiniTest::Spec Song = Struct.new(:title) describe "render_nil: true" do representer! do property :title, render_nil: true end it { _(Song.new.extend(representer).to_hash).must_equal({"title"=>nil}) } end describe "with :extend it shouldn't extend nil" do representer! do property :title, render_nil: true, extend: Class end it { _(Song.new.extend(representer).to_hash).must_equal({"title"=>nil}) } end end representable-3.2.0/test/reader_writer_test.rb0000644000004100000410000000124014240717435021604 0ustar www-datawww-datarequire 'test_helper' class ReaderWriterTest < BaseTest representer! do property :name, :writer => lambda { |options| options[:doc]["title"] = "#{options[:user_options][:nr]}) #{options[:input]}" }, :reader => lambda { |options| self.name = options[:doc]["title"].split(") ").last } end subject { OpenStruct.new(:name => "Disorder And Disarray").extend(representer) } it "uses :writer when rendering" do _(subject.to_hash(user_options: {nr: 14})).must_equal({"title" => "14) Disorder And Disarray"}) end it "uses :reader when parsing" do _(subject.from_hash({"title" => "15) The Wars End"}).name).must_equal "The Wars End" end endrepresentable-3.2.0/test/uncategorized_test.rb0000644000004100000410000000507314240717435021621 0ustar www-datawww-datarequire "test_helper" class StopWhenIncomingObjectFragmentIsNilTest < MiniTest::Spec Album = Struct.new(:id, :songs) Song = Struct.new(:title) representer!(decorator: true) do property :id collection :songs, class: Song, parse_pipeline: ->(input, options) { # TODO: test if :doc is set for parsing. test if options are ok and contain :user_options! Representable::Pipeline[*parse_functions.insert(3, Representable::StopOnNil)] } do property :title end end it do album = Album.new _(representer.new(album).from_hash({"id"=>1, "songs"=>[{"title"=>"Walkie Talkie"}]}).songs).must_equal [Song.new("Walkie Talkie")] end it do album = Album.new(2, ["original"]) _(representer.new(album).from_hash({"id"=>1, "songs"=>nil}).songs).must_equal ["original"] end end class RenderPipelineOptionTest < MiniTest::Spec Album = Struct.new(:id, :songs) NilToNA = ->(input, options) { input.nil? ? "n/a" : input } representer!(decorator: true) do property :id, render_pipeline: ->(input, options) do Representable::Pipeline[*render_functions.insert(2, options[:options][:user_options][:function])] end end it { _(representer.new(Album.new).to_hash(user_options: {function: NilToNA})).must_equal({"id"=>"n/a"}) } it { _(representer.new(Album.new(1)).to_hash(user_options: {function: NilToNA})).must_equal({"id"=>1}) } it "is cached" do decorator = representer.new(Album.new) _(decorator.to_hash(user_options: {function: NilToNA})).must_equal({"id"=>"n/a"}) _(decorator.to_hash(user_options: {function: nil})).must_equal({"id"=>"n/a"}) # non-sense function is not applied. end end class ParsePipelineOptionTest < MiniTest::Spec Album = Struct.new(:id, :songs) NilToNA = ->(input, options) { input.nil? ? "n/a" : input } representer!(decorator: true) do property :id, parse_pipeline: ->(input, options) do Representable::Pipeline[*parse_functions.insert(3, options[:options][:user_options][:function])].extend(Representable::Pipeline::Debug) end end it { _(representer.new(Album.new).from_hash({"id"=>nil}, user_options: {function: NilToNA}).id).must_equal "n/a" } it { _(representer.new(Album.new(1)).to_hash(user_options: {function: NilToNA})).must_equal({"id"=>1}) } it "is cached" do decorator = representer.new(Album.new) _(decorator.from_hash({"id"=>nil}, user_options: {function: NilToNA}).id).must_equal "n/a" _(decorator.from_hash({"id"=>nil}, user_options: {function: "nonsense"}).id).must_equal "n/a" # non-sense function is not applied. end endrepresentable-3.2.0/test/skip_test.rb0000644000004100000410000000546714240717435017733 0ustar www-datawww-datarequire 'test_helper' class SkipParseTest < MiniTest::Spec representer! do property :title, skip_parse: lambda { |options| options[:user_options][:skip?] and options[:fragment] == "skip me" } property :band, skip_parse: lambda { |options| options[:user_options][:skip?] and options[:fragment]["name"].nil? }, class: OpenStruct do property :name end collection :airplays, skip_parse: lambda { |options| options[:user_options][:skip?] and options[:fragment]["station"].nil? }, class: OpenStruct do property :station end end let(:song) { OpenStruct.new.extend(representer) } # do parse. it do song.from_hash({ "title" => "Victim Of Fate", "band" => {"name" => "Mute 98"}, "airplays" => [{"station" => "JJJ"}] }, user_options: { skip?: true }) _(song.title).must_equal "Victim Of Fate" _(song.band.name).must_equal "Mute 98" _(song.airplays[0].station).must_equal "JJJ" end # skip parsing. let(:airplay) { OpenStruct.new(station: "JJJ") } it do song.from_hash({ "title" => "skip me", "band" => {}, "airplays" => [{}, {"station" => "JJJ"}, {}], }, user_options: { skip?: true }) _(song.title).must_be_nil _(song.band).must_be_nil _(song.airplays).must_equal [airplay] end end class SkipRenderTest < MiniTest::Spec representer! do property :title property :band, skip_render: lambda { |options| options[:user_options][:skip?] and options[:input].name == "Rancid" } do property :name end collection :airplays, skip_render: lambda { |options| options[:user_options][:skip?] and options[:input].station == "Radio Dreyeckland" } do property :station end end let(:song) { OpenStruct.new(title: "Black Night", band: OpenStruct.new(name: "Time Again")).extend(representer) } let(:skip_song) { OpenStruct.new(title: "Time Bomb", band: OpenStruct.new(name: "Rancid")).extend(representer) } # do render. it { _(song.to_hash(user_options: { skip?: true })).must_equal({"title"=>"Black Night", "band"=>{"name"=>"Time Again"}}) } # skip. it { _(skip_song.to_hash(user_options: { skip?: true })).must_equal({"title"=>"Time Bomb"}) } # do render all collection items. it do song = OpenStruct.new(airplays: [OpenStruct.new(station: "JJJ"), OpenStruct.new(station: "ABC")]).extend(representer) _(song.to_hash(user_options: { skip?: true })).must_equal({"airplays"=>[{"station"=>"JJJ"}, {"station"=>"ABC"}]}) end # skip middle item. it do song = OpenStruct.new(airplays: [OpenStruct.new(station: "JJJ"), OpenStruct.new(station: "Radio Dreyeckland"), OpenStruct.new(station: "ABC")]).extend(representer) _(song.to_hash(user_options: { skip?: true })).must_equal({"airplays"=>[{"station"=>"JJJ"}, {"station"=>"ABC"}]}) end endrepresentable-3.2.0/test/json_test.rb0000644000004100000410000002536614240717435017736 0ustar www-datawww-datarequire 'test_helper' require 'json' module JsonTest class JSONPublicMethodsTest < Minitest::Spec #--- # from_json class BandRepresenter < Representable::Decorator include Representable::JSON property :id property :name end let(:json) { '{"id":1,"name":"Rancid"}' } it { _(BandRepresenter.new(Band.new).from_json(json)[:id, :name]).must_equal [1, "Rancid"] } it { _(BandRepresenter.new(Band.new).parse(json)[:id, :name]).must_equal [1, "Rancid"] } #--- # to_json let(:band) { Band.new(1, "Rancid") } it { _(BandRepresenter.new(band).to_json).must_equal json } it { _(BandRepresenter.new(band).render).must_equal json } end class APITest < MiniTest::Spec Json = Representable::JSON Def = Representable::Definition describe "JSON module" do before do @Band = Class.new do include Representable::JSON property :name property :label attr_accessor :name, :label def initialize(name=nil) self.name = name if name end end @band = @Band.new end describe "#from_json" do before do @band = @Band.new @json = {:name => "Nofx", :label => "NOFX"}.to_json end it "parses JSON and assigns properties" do @band.from_json(@json) assert_equal ["Nofx", "NOFX"], [@band.name, @band.label] end end describe "#from_hash" do before do @band = @Band.new @hash = {"name" => "Nofx", "label" => "NOFX"} end it "receives hash and assigns properties" do @band.from_hash(@hash) assert_equal ["Nofx", "NOFX"], [@band.name, @band.label] end it "respects :wrap option" do @band.from_hash({"band" => {"name" => "This Is A Standoff"}}, :wrap => :band) assert_equal "This Is A Standoff", @band.name end it "respects :wrap option over representation_wrap" do @Band.class_eval do self.representation_wrap = :group end @band.from_hash({"band" => {"name" => "This Is A Standoff"}}, :wrap => :band) assert_equal "This Is A Standoff", @band.name end end describe "#to_json" do it "delegates to #to_hash and returns string" do assert_json "{\"name\":\"Rise Against\"}", @Band.new("Rise Against").to_json end end describe "#to_hash" do it "returns unwrapped hash" do hash = @Band.new("Rise Against").to_hash assert_equal({"name"=>"Rise Against"}, hash) end it "respects :wrap option" do assert_equal({:band=>{"name"=>"NOFX"}}, @Band.new("NOFX").to_hash(:wrap => :band)) end it "respects :wrap option over representation_wrap" do @Band.class_eval do self.representation_wrap = :group end assert_equal({:band=>{"name"=>"Rise Against"}}, @Band.new("Rise Against").to_hash(:wrap => :band)) end end describe "#build_for" do it "returns TextBinding" do assert_kind_of Representable::Hash::Binding, Representable::Hash::Binding.build_for(Def.new(:band)) end it "returns CollectionBinding" do assert_kind_of Representable::Hash::Binding::Collection, Representable::Hash::Binding.build_for(Def.new(:band, :collection => true)) end end end describe "DCI" do module SongRepresenter include Representable::JSON property :name end module AlbumRepresenter include Representable::JSON property :best_song, :class => Song, :extend => SongRepresenter collection :songs, :class => Song, :extend => SongRepresenter end it "allows adding the representer by using #extend" do module BandRepresenter include Representable::JSON property :name end civ = Object.new civ.instance_eval do def name; "CIV"; end def name=(v) @name = v end end civ.extend(BandRepresenter) assert_json "{\"name\":\"CIV\"}", civ.to_json end it "extends contained models when serializing" do @album = Album.new([Song.new("I Hate My Brain"), mr=Song.new("Mr. Charisma")], mr) @album.extend(AlbumRepresenter) assert_json "{\"best_song\":{\"name\":\"Mr. Charisma\"},\"songs\":[{\"name\":\"I Hate My Brain\"},{\"name\":\"Mr. Charisma\"}]}", @album.to_json end it "extends contained models when deserializing" do #@album = Album.new(Song.new("I Hate My Brain"), Song.new("Mr. Charisma")) @album = Album.new @album.extend(AlbumRepresenter) @album.from_json("{\"best_song\":{\"name\":\"Mr. Charisma\"},\"songs\":[{\"name\":\"I Hate My Brain\"},{\"name\":\"Mr. Charisma\"}]}") assert_equal "Mr. Charisma", @album.best_song.name end end end class PropertyTest < MiniTest::Spec describe "property :name" do class Band include Representable::JSON property :name attr_accessor :name end it "#from_json creates correct accessors" do band = Band.new.from_json({:name => "Bombshell Rocks"}.to_json) assert_equal "Bombshell Rocks", band.name end it "#to_json serializes correctly" do band = Band.new band.name = "Cigar" assert_json '{"name":"Cigar"}', band.to_json end end describe ":class => Item" do class Label include Representable::JSON property :name attr_accessor :name end class Album include Representable::JSON property :label, :class => Label attr_accessor :label end it "#from_json creates one Item instance" do album = Album.new.from_json('{"label":{"name":"Fat Wreck"}}') assert_equal "Fat Wreck", album.label.name end it "#to_json serializes" do label = Label.new; label.name = "Fat Wreck" album = Album.new; album.label = label assert_json '{"label":{"name":"Fat Wreck"}}', album.to_json end describe ":different_name, :class => Label" do before do @Album = Class.new do include Representable::JSON property :seller, :class => Label attr_accessor :seller end end it "#to_xml respects the different name" do label = Label.new; label.name = "Fat Wreck" album = @Album.new; album.seller = label assert_json "{\"seller\":{\"name\":\"Fat Wreck\"}}", album.to_json(:wrap => false) end end end describe ":as => :songName" do class Song include Representable::JSON property :name, :as => :songName attr_accessor :name end it "respects :as in #from_json" do song = Song.new.from_json({:songName => "Run To The Hills"}.to_json) assert_equal "Run To The Hills", song.name end it "respects :as in #to_json" do song = Song.new; song.name = "22 Acacia Avenue" assert_json '{"songName":"22 Acacia Avenue"}', song.to_json end end end class CollectionTest < MiniTest::Spec describe "collection :name" do class CD include Representable::JSON collection :songs attr_accessor :songs end it "#from_json creates correct accessors" do cd = CD.new.from_json({:songs => ["Out in the cold", "Microphone"]}.to_json) assert_equal ["Out in the cold", "Microphone"], cd.songs end it "zzz#to_json serializes correctly" do cd = CD.new cd.songs = ["Out in the cold", "Microphone"] assert_json '{"songs":["Out in the cold","Microphone"]}', cd.to_json end end describe "collection :name, :class => Band" do class Band include Representable::JSON property :name attr_accessor :name def initialize(name="") self.name = name end end class Compilation include Representable::JSON collection :bands, :class => Band attr_accessor :bands end describe "#from_json" do it "pushes collection items to array" do cd = Compilation.new.from_json({:bands => [ {:name => "Cobra Skulls"}, {:name => "Diesel Boy"}]}.to_json) assert_equal ["Cobra Skulls", "Diesel Boy"], cd.bands.map(&:name).sort end end it "responds to #to_json" do cd = Compilation.new cd.bands = [Band.new("Diesel Boy"), Band.new("Bad Religion")] assert_json '{"bands":[{"name":"Diesel Boy"},{"name":"Bad Religion"}]}', cd.to_json end end describe ":as => :songList" do class Songs include Representable::JSON collection :tracks, :as => :songList attr_accessor :tracks end it "respects :as in #from_json" do songs = Songs.new.from_json({:songList => ["Out in the cold", "Microphone"]}.to_json) assert_equal ["Out in the cold", "Microphone"], songs.tracks end it "respects option in #to_json" do songs = Songs.new songs.tracks = ["Out in the cold", "Microphone"] assert_json '{"songList":["Out in the cold","Microphone"]}', songs.to_json end end end class HashTest < MiniTest::Spec describe "hash :songs" do representer!(:module => Representable::JSON) do hash :songs end subject { OpenStruct.new.extend(representer) } it "renders with #to_json" do subject.songs = {:one => "65", :two => "Emo Boy"} assert_json "{\"songs\":{\"one\":\"65\",\"two\":\"Emo Boy\"}}", subject.to_json end it "parses with #from_json" do assert_equal({"one" => "65", "two" => ["Emo Boy"]}, subject.from_json("{\"songs\":{\"one\":\"65\",\"two\":[\"Emo Boy\"]}}").songs) end end describe "hash :songs, :class => Song" do representer!(:module => Representable::JSON) do hash :songs, :extend => Module.new { include Representable::JSON; property :name }, :class => Song end it "renders" do _(OpenStruct.new(:songs => {"7" => Song.new("Contemplation")}).extend(representer).to_hash).must_equal("songs"=>{"7"=>{"name"=>"Contemplation"}}) end describe "parsing" do subject { OpenStruct.new.extend(representer) } let(:hsh) { {"7"=>{"name"=>"Contemplation"}} } it "parses incoming hash" do _(subject.from_hash("songs"=>hsh).songs).must_equal({"7"=>Song.new("Contemplation")}) end it "doesn't modify the incoming hash" do subject.from_hash("songs"=> incoming_hash = hsh.dup) _(hsh).must_equal incoming_hash end end end end end representable-3.2.0/test/getter_setter_test.rb0000644000004100000410000000150314240717435021630 0ustar www-datawww-datarequire 'test_helper' class GetterSetterTest < BaseTest representer! do property :name, # key under :name. :getter => lambda { |user_options:, **| "#{user_options[:welcome]} #{song_name}" }, :setter => lambda { |user_options:, input:, **| self.song_name = "#{user_options[:welcome]} #{input}" } end subject { Struct.new(:song_name).new("Mony Mony").extend(representer) } it "uses :getter when rendering" do subject.instance_eval { def name; raise; end } _(subject.to_hash(user_options: {welcome: "Hi"})).must_equal({"name" => "Hi Mony Mony"}) end it "uses :setter when parsing" do subject.instance_eval { def name=(*); raise; end; self } _(subject.from_hash({"name" => "Eyes Without A Face"}, user_options: {welcome: "Hello"}).song_name).must_equal "Hello Eyes Without A Face" end end representable-3.2.0/test/hash_test.rb0000644000004100000410000001244114240717435017676 0ustar www-datawww-datarequire 'test_helper' class HashPublicMethodsTest < Minitest::Spec #--- # from_hash class BandRepresenter < Representable::Decorator include Representable::Hash property :id property :name end let(:data) { {"id"=>1,"name"=>"Rancid"} } it { _(BandRepresenter.new(Band.new).from_hash(data)[:id, :name]).must_equal [1, "Rancid"] } it { _(BandRepresenter.new(Band.new).parse(data)[:id, :name]).must_equal [1, "Rancid"] } #--- # to_hash let(:band) { Band.new(1, "Rancid") } it { _(BandRepresenter.new(band).to_hash).must_equal data } it { _(BandRepresenter.new(band).render).must_equal data } end class HashWithScalarPropertyTest < MiniTest::Spec Album = Struct.new(:title) representer! do property :title end let(:album) { Album.new("Liar") } describe "#to_hash" do it "renders plain property" do _(album.extend(representer).to_hash).must_equal("title" => "Liar") end end describe "#from_hash" do it "parses plain property" do _(album.extend(representer).from_hash("title" => "This Song Is Recycled").title).must_equal "This Song Is Recycled" end # Fixes issue #115 it "allows nil value in the incoming document and corresponding nil value for the represented" do album = Album.new _(album.title).must_be_nil album.extend(representer).from_hash("title" => nil) end end end class HashWithTypedPropertyTest < MiniTest::Spec Album = Struct.new(:best_song) representer! do property :best_song, :class => Song do property :name end end let(:album) { Album.new(Song.new("Liar")) } describe "#to_hash" do it "renders embedded typed property" do _(album.extend(representer).to_hash).must_equal("best_song" => {"name" => "Liar"}) end end describe "#from_hash" do it "parses embedded typed property" do album.extend(representer).from_hash("best_song" => {"name" => "Go With Me"}) _(album.best_song.name).must_equal "Go With Me" end # nested nil removes nested object. it do album = Album.new(Song.new("Pre-medicated Murder")) album.extend(representer).from_hash("best_song" => nil) _(album.best_song).must_be_nil end # nested blank hash creates blank object when not populated. it do album = Album.new#(Song.new("Pre-medicated Murder")) album.extend(representer).from_hash("best_song" => {}) _(album.best_song.name).must_be_nil end # Fixes issue #115 it "allows nil value in the incoming document and corresponding nil value for the represented" do album = Album.new album.extend(representer).from_hash("best_song" => nil) _(album.best_song).must_be_nil end end end # TODO: move to AsTest. class HashWithTypedPropertyAndAs < MiniTest::Spec representer! do property :song, :class => Song, :as => :hit do property :name end end let(:album) { OpenStruct.new(:song => Song.new("Liar")).extend(representer) } it { _(album.to_hash).must_equal("hit" => {"name" => "Liar"}) } it { _(album.from_hash("hit" => {"name" => "Go With Me"})).must_equal OpenStruct.new(:song => Song.new("Go With Me")) } end # # describe "FIXME COMBINE WITH ABOVE with :extend and :as" do # # hash_song = Module.new do # # include Representable::XML # # self.representation_wrap = :song # # property :name # # end # # let(:hash_album) { Module.new do # # include Representable::XML # # self.representation_wrap = :album # # property :song, :extend => hash_song, :class => Song, :as => :hit # # end } # # let(:album) { OpenStruct.new(:song => Song.new("Liar")).extend(hash_album) } # # it { album.to_xml.must_equal_xml("Liar") } # # #it { album.from_hash("hit" => {"name" => "Go With Me"}).must_equal OpenStruct.new(:song => Song.new("Go With Me")) } # # end # end class HashWithTypedCollectionTest < MiniTest::Spec Album = Struct.new(:songs) representer! do collection :songs, class: Song do property :name property :track end end let(:album) { Album.new([Song.new("Liar", 1), Song.new("What I Know", 2)]) } describe "#to_hash" do it "renders collection of typed property" do _(album.extend(representer).to_hash).must_equal("songs" => [{"name" => "Liar", "track" => 1}, {"name" => "What I Know", "track" => 2}]) end end describe "#from_hash" do it "parses collection of typed property" do _(album.extend(representer).from_hash("songs" => [{"name" => "One Shot Deal", "track" => 4}, {"name" => "Three Way Dance", "track" => 5}])).must_equal Album.new([Song.new("One Shot Deal", 4), Song.new("Three Way Dance", 5)]) end end end class HashWithScalarCollectionTest < MiniTest::Spec Album = Struct.new(:songs) representer! { collection :songs } let(:album) { Album.new(["Jackhammer", "Terrible Man"]) } describe "#to_hash" do it "renders a block style list per default" do _(album.extend(representer).to_hash).must_equal("songs" => ["Jackhammer", "Terrible Man"]) end end describe "#from_hash" do it "parses a block style list" do _(album.extend(representer).from_hash("songs" => ["Off Key Melody", "Sinking"])).must_equal Album.new(["Off Key Melody", "Sinking"]) end end end representable-3.2.0/test/examples/0000755000004100000410000000000014240717435017203 5ustar www-datawww-datarepresentable-3.2.0/test/examples/example.rb0000644000004100000410000001530614240717435021170 0ustar www-datawww-datarequire 'bundler' Bundler.setup require 'representable/yaml' require 'ostruct' def reset_representer(*module_name) module_name.each do |mod| mod.module_eval do @representable_attrs = nil end end end class Song < OpenStruct end song = Song.new(:title => "Fallout", :track => 1) module SongRepresenter include Representable::JSON property :title property :track end puts song.extend(SongRepresenter).to_json rox = Song.new.extend(SongRepresenter).from_json(%{ {"title":"Roxanne"} }) puts rox.inspect module SongRepresenter include Representable::JSON self.representation_wrap= :hit property :title property :track end puts song.extend(SongRepresenter).to_json ######### collections reset_representer(SongRepresenter) module SongRepresenter include Representable::JSON property :title property :track collection :composers end song = Song.new(:title => "Fallout", :composers => ["Stewart Copeland", "Sting"]) puts song.extend(SongRepresenter).to_json ######### nesting types class Album < OpenStruct def name puts @table.inspect #@attributes @table[:name] end end module AlbumRepresenter include Representable::JSON property :name property :song, :extend => SongRepresenter, :class => Song end album = Album.new(:name => "The Police", :song => song) puts album.extend(AlbumRepresenter).to_json reset_representer(AlbumRepresenter) module AlbumRepresenter include Representable::JSON property :name collection :songs, :extend => SongRepresenter, :class => Song end album = Album.new(:name => "The Police", :songs => [song, Song.new(:title => "Synchronicity")]) puts album.extend(AlbumRepresenter).to_json album = Album.new.extend(AlbumRepresenter).from_json(%{{"name":"Offspring","songs":[{"title":"Genocide"},{"title":"Nitro","composers":["Offspring"]}]} }) puts album.inspect reset_representer(SongRepresenter) ######### using helpers (customizing the rendering/parsing) module AlbumRepresenter include Representable::JSON def name super.upcase end end puts Album.new(:name => "The Police"). extend(AlbumRepresenter).to_json reset_representer(SongRepresenter) ######### inheritance module SongRepresenter include Representable::JSON property :title property :track end module CoverSongRepresenter include Representable::JSON include SongRepresenter property :covered_by end song = Song.new(:title => "Truth Hits Everybody", :covered_by => "No Use For A Name") puts song.extend(CoverSongRepresenter).to_json ### XML require 'representable/xml' module SongRepresenter include Representable::XML property :title property :track collection :composers end song = Song.new(:title => "Fallout", :composers => ["Stewart Copeland", "Sting"]) puts song.extend(SongRepresenter).to_xml reset_representer(SongRepresenter) ### YAML require 'representable/yaml' module SongRepresenter include Representable::YAML property :title property :track collection :composers end puts song.extend(SongRepresenter).to_yaml SongRepresenter.module_eval do @representable_attrs = nil end ### YAML module SongRepresenter include Representable::YAML property :title property :track collection :composers, :style => :flow end puts song.extend(SongRepresenter).to_yaml SongRepresenter.module_eval do @representable_attrs = nil end # R/W support song = Song.new(:title => "You're Wrong", :track => 4) module SongRepresenter include Representable::Hash property :title, :readable => false property :track end puts song.extend(SongRepresenter).to_hash SongRepresenter.module_eval do @representable_attrs = nil end module SongRepresenter include Representable::Hash property :title, :writeable => false property :track end song = Song.new.extend(SongRepresenter) song.from_hash({:title => "Fallout", :track => 1}) puts song ######### custom methods in representer (using helpers) ######### conditions ######### :as ######### XML::Collection ######### polymorphic :extend and :class, instance context!, :instance class CoverSong < Song end songs = [Song.new(title: "Weirdo", track: 5), CoverSong.new(title: "Truth Hits Everybody", track: 6, copyright: "The Police")] album = Album.new(name: "Incognito", songs: songs) reset_representer(SongRepresenter, AlbumRepresenter) module SongRepresenter include Representable::Hash property :title property :track end module CoverSongRepresenter include Representable::Hash include SongRepresenter property :copyright end module AlbumRepresenter include Representable::Hash property :name collection :songs, :extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter } end puts album.extend(AlbumRepresenter).to_hash reset_representer(AlbumRepresenter) module AlbumRepresenter include Representable::Hash property :name collection :songs, :extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter }, :class => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong : Song } #=> {"title"=>"Weirdo", "track"=>5} end album = Album.new.extend(AlbumRepresenter).from_hash({"name"=>"Incognito", "songs"=>[{"title"=>"Weirdo", "track"=>5}, {"title"=>"Truth Hits Everybody", "track"=>6, "copyright"=>"The Police"}]}) puts album.inspect reset_representer(AlbumRepresenter) module AlbumRepresenter include Representable::Hash property :name collection :songs, :extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter }, :instance => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong.new : Song.new(original: true) } end album = Album.new.extend(AlbumRepresenter).from_hash({"name"=>"Incognito", "songs"=>[{"title"=>"Weirdo", "track"=>5}, {"title"=>"Truth Hits Everybody", "track"=>6, "copyright"=>"The Police"}]}) puts album.inspect ######### #hash reset_representer(SongRepresenter) module SongRepresenter include Representable::JSON property :title hash :ratings end puts Song.new(title: "Bliss", ratings: {"Rolling Stone" => 4.9, "FryZine" => 4.5}).extend(SongRepresenter).to_json ######### JSON::Hash module FavoriteSongsRepresenter include Representable::JSON::Hash end puts( {"Nick" => "Hyper Music", "El" => "Blown In The Wind"}.extend(FavoriteSongsRepresenter).to_json) require 'representable/json/hash' module FavoriteSongsRepresenter include Representable::JSON::Hash values extend: SongRepresenter, class: Song end puts( {"Nick" => Song.new(title: "Hyper Music")}.extend(FavoriteSongsRepresenter).to_json) require 'representable/json/collection' module SongsRepresenter include Representable::JSON::Collection items extend: SongRepresenter, class: Song end puts [Song.new(title: "Hyper Music"), Song.new(title: "Screenager")].extend(SongsRepresenter).to_json representable-3.2.0/test/examples/object.rb0000644000004100000410000000106714240717435021002 0ustar www-datawww-datarequire "test_helper" require "ostruct" require "pp" source = OpenStruct.new(name: "30 Years Live", songs: [ OpenStruct.new(id: 1, title: "Dear Beloved"), OpenStruct.new(id: 2, title: "Fuck Armageddon")]) require "representable/object" class AlbumRepresenter < Representable::Decorator include Representable::Object property :name collection :songs, instance: ->(fragment, *) { Song.new } do property :title end end Album = Struct.new(:name, :songs) Song = Struct.new(:title) target = Album.new AlbumRepresenter.new(target).from_object(source)representable-3.2.0/test/class_test.rb0000644000004100000410000000621014240717435020055 0ustar www-datawww-datarequire 'test_helper' class ClassTest < BaseTest class RepresentingSong attr_reader :name def from_hash(doc, *) @name = doc["__name__"] self # DISCUSS: do we wanna be able to return whatever we want here? this is a trick to replace the actual object end end describe "class: ClassName, only" do representer! do property :song, :class => RepresentingSong # supposed this class exposes #from_hash itself. end it "creates fresh instance and doesn't extend" do song = representer.prepare(OpenStruct.new).from_hash({"song" => {"__name__" => "Captured"}}).song _(song).must_be_instance_of RepresentingSong _(song.name).must_equal "Captured" end end describe "class: lambda, only" do representer! do property :song, :class => lambda { |*| RepresentingSong } end it "creates fresh instance and doesn't extend" do song = representer.prepare(OpenStruct.new).from_hash({"song" => {"__name__" => "Captured"}}).song _(song).must_be_instance_of RepresentingSong _(song.name).must_equal "Captured" end end # this throws a DeserializationError now. describe "lambda { nil }" do representer! do property :title, :class => nil end it do assert_raises Representable::DeserializeError do OpenStruct.new.extend(representer).from_hash({"title" => {}}) end end end describe "lambda receiving fragment and args" do let(:klass) { Class.new do class << self attr_accessor :args end def from_hash(*) self.class.new end end } representer!(:inject => :klass) do _klass = klass property :song, :class => lambda { |options| _klass.args=([options[:fragment],options[:user_options]]); _klass } end it { _(representer.prepare(OpenStruct.new).from_hash({"song" => {"name" => "Captured"}}, user_options: {volume: true}).song.class.args). must_equal([{"name"=>"Captured"}, {:volume=>true}]) } end describe "collection: lambda receiving fragment and args" do let(:klass) { Class.new do class << self attr_accessor :args end def from_hash(*) self.class.new end end } representer!(:inject => :klass) do _klass = klass collection :songs, :class => lambda { |options| _klass.args=([options[:fragment],options[:index],options[:user_options]]); _klass } end it { _(representer.prepare(OpenStruct.new).from_hash({"songs" => [{"name" => "Captured"}]}, user_options: {volume: true}).songs.first.class.args). must_equal([{"name"=>"Captured"}, 0, {:volume=>true}]) } end describe "class: implementing #from_hash" do let(:parser) do Class.new do def from_hash(*) [1,2,3,4] end end end representer!(:inject => :parser) do property :song, :class => parser # supposed this class exposes #from_hash itself. end it "allows returning arbitrary objects in #from_hash" do _(representer.prepare(OpenStruct.new).from_hash({"song" => 1}).song).must_equal [1,2,3,4] end end end #TODO: test fragment, # `class: Song` only, no :extend. representable-3.2.0/test/as_test.rb0000644000004100000410000000400114240717435017347 0ustar www-datawww-datarequire 'test_helper' class AsTest < MiniTest::Spec for_formats( :hash => [Representable::Hash, {"title" => "Wie Es Geht"}, {"title" => "Revolution"}], # :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything/open_struct>"], # :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"], ) do |format, mod, input, output| let(:song) { representer.prepare(Song.new("Revolution")) } let(:format) { format } describe "as: with :symbol" do representer!(:module => mod) do property :name, :as => :title end it { render(song).must_equal_document output } it { _(parse(song, input).name).must_equal "Wie Es Geht" } end describe "as: with lambda" do representer!(:module => mod) do property :name, :as => lambda { |*| "#{self.class}" } end it { render(song).must_equal_document({"Song" => "Revolution"}) } it { _(parse(song, {"Song" => "Wie Es Geht"}).name).must_equal "Wie Es Geht" } end describe "lambda arguments" do representer! do property :name, :as => lambda { |options| options[:user_options].inspect } end it { render(song, user_options:{volume: 1}).must_equal_document({"{:volume=>1}" => "Revolution"}) } it { _(parse(song, {"{:volume=>1}" => "Wie Es Geht"}, user_options: {volume: 1}).name).must_equal "Wie Es Geht" } end end end # hash: to_hash(wrap: ) is representation_wrap class AsXmlTest < MiniTest::Spec Band = Struct.new(:name, :label) Album = Struct.new(:band) Label = Struct.new(:name) representer!(module: Representable::XML, decorator: true) do self.representation_wrap = :album property :band, as: :combo do self.representation_wrap = :band property :name end end it do skip _(representer.new(Album.new(Band.new("Offspring"))).to_xml).must_equal "" end end representable-3.2.0/test/schema_test.rb0000644000004100000410000000710114240717435020210 0ustar www-datawww-datarequire 'test_helper' # Include Inherit Module And Decorator Test class SchemaTest < MiniTest::Spec module Genre include Representable property :genre end module LinkFeature def self.included(base) base.extend(Link) end module Link def link end end end module Module include Representable::Hash feature LinkFeature property :title property :label do # extend: LabelModule property :name link # feature property :location do property :city link # feature. end end property :album, :extend => lambda { raise "don't manifest me!" } # this is not an inline decorator, don't manifest it. include Genre # Schema::Included::included is called! end class WithLocationStreetRepresenter < Representable::Decorator include Representable::Hash feature LinkFeature property :title property :label do # extend: LabelModule property :name link # feature property :location do property :city link # feature. end end end describe "3-level deep with features" do let(:label) { OpenStruct.new(:name => "Epitaph", :location => OpenStruct.new(:city => "Sanfran", :name => "DON'T SHOW ME!")) } # Module does correctly include features in inlines. it { _(band.extend(Module).to_hash).must_equal({"label"=>{"name"=>"Epitaph", "location"=>{"city"=>"Sanfran"}}, "genre"=>"Punkrock"}) } # Decorator does correctly include features in inlines. it { _(Decorator.new(band).to_hash).must_equal({"label"=>{"name"=>"Epitaph", "location"=>{"city"=>"Sanfran"}}, "genre"=>"Punkrock"}) } end class Decorator < Representable::Decorator feature Representable::Hash include Module end # puts Decorator.representable_attrs[:definitions].inspect let(:label) { OpenStruct.new(:name => "Fat Wreck", :city => "San Francisco", :employees => [OpenStruct.new(:name => "Mike")], :location => OpenStruct.new(:city => "Sanfran")) } let(:band) { OpenStruct.new(:genre => "Punkrock", :label => label) } # it { FlatlinersDecorator.new( OpenStruct.new(label: OpenStruct.new) ). # to_hash.must_equal({}) } it do _(Decorator.new(band).to_hash).must_equal({"genre"=>"Punkrock", "label"=>{"name"=>"Fat Wreck", "location"=>{"city"=>"Sanfran"}}}) end class InheritDecorator < Representable::Decorator include Representable::Hash include Module property :label, inherit: true do # decorator.rb:27:in `initialize': superclass must be a Class (Module given) property :city property :location, :inherit => true do property :city end end end it do _(InheritDecorator.new(band).to_hash).must_equal({"genre"=>"Punkrock", "label"=>{"name"=>"Fat Wreck", "city"=>"San Francisco", "location"=>{"city"=>"Sanfran"}}}) end class InheritFromDecorator < InheritDecorator property :label, inherit: true do collection :employees do property :name end end end it do _(InheritFromDecorator.new(band).to_hash).must_equal({"genre"=>"Punkrock", "label"=>{"name"=>"Fat Wreck", "city"=>"San Francisco", "employees"=>[{"name"=>"Mike"}], "location"=>{"city"=>"Sanfran"}}}) end end class ApplyTest < MiniTest::Spec class AlbumDecorator < Representable::Decorator include Representable::Hash property :title property :hit do property :title end collection :songs do property :title end property :band do # yepp, people do crazy stuff like that. property :label do property :name end end end endrepresentable-3.2.0/test/populator_test.rb0000644000004100000410000000621514240717435021002 0ustar www-datawww-datarequire "test_helper" class PopulatorTest < Minitest::Spec Song = Struct.new(:id) Artist = Struct.new(:name) Album = Struct.new(:songs, :artist) describe "populator: ->{}" do representer! do collection :songs, populator: ->(input, options) { options[:represented].songs << song = Song.new; song } do property :id end property :artist, populator: ->(input, options) { options[:represented].artist = Artist.new } do property :name end end let(:album) { Album.new([]) } it do album.extend(representer).from_hash("songs"=>[{"id"=>1}, {"id"=>2}], "artist"=>{"name"=>"Waste"}) _(album.inspect).must_equal "#, #], artist=#>" end end describe "populator: ->{}, " do end end class PopulatorFindOrInstantiateTest < Minitest::Spec Song = Struct.new(:id, :title, :uid) do def self.find_by(attributes={}) return new(1, "Resist Stan", "abcd") if attributes[:id]==1# we should return the same object here new end end Composer = Class.new Composer.class_eval do def song=(v) @song = v "Absolute nonsense" # this tests that the populator always returns the correct object. end attr_reader :song end describe "FindOrInstantiate with property" do representer! do property :song, populator: Representable::FindOrInstantiate, class: Song do property :id property :title end end let(:album) { Composer.new.extend(representer).extend(Representable::Debug) } it "finds by :id and creates new without :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 _(album.song.uid).must_equal "abcd" # not changed via populator, indicating this is a formerly "persisted" object. end it "creates new without :id" do album.from_hash({"song"=>{"title"=>"Lower"}}) _(album.song.title).must_equal "Lower" _(album.song.id).must_be_nil _(album.song.uid).must_be_nil end end describe "FindOrInstantiate with collection" do representer! do collection :songs, populator: Representable::FindOrInstantiate, class: Song do property :id property :title end end let(:album) { Struct.new(:songs).new([]).extend(representer) } it "finds by :id and creates new without :id" do 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[0].uid).must_equal "abcd" # not changed via populator, indicating this is a formerly "persisted" object. _(album.songs[1].title).must_equal "Suffer" _(album.songs[1].id).must_be_nil _(album.songs[1].uid).must_be_nil end # TODO: test with existing collection end end representable-3.2.0/test/hash_bindings_test.rb0000644000004100000410000000527414240717435021561 0ustar www-datawww-datarequire 'test_helper' class HashBindingTest < MiniTest::Spec module SongRepresenter include Representable::JSON property :name end class SongWithRepresenter < ::Song include Representable include SongRepresenter end describe "PropertyBinding" do describe "#read" do before do @property = Representable::Hash::Binding.new(Representable::Definition.new(:song)) end it "returns fragment if present" do assert_equal "Stick The Flag Up Your Goddamn Ass, You Sonofabitch", @property.read({"song" => "Stick The Flag Up Your Goddamn Ass, You Sonofabitch"}, "song") assert_equal "", @property.read({"song" => ""}, "song") assert_nil @property.read({"song" => nil}, "song") end it "returns FRAGMENT_NOT_FOUND if not in document" do assert_equal Representable::Binding::FragmentNotFound, @property.read({}, "song") end end end describe "CollectionBinding" do describe "with plain text items" do before do @property = Representable::Hash::Binding::Collection.new(Representable::Definition.new(:songs, :collection => true)) end it "extracts with #read" do assert_equal ["The Gargoyle", "Bronx"], @property.read({"songs" => ["The Gargoyle", "Bronx"]}, "songs") end it "inserts with #write" do doc = {} assert_equal(["The Gargoyle", "Bronx"], @property.write(doc, ["The Gargoyle", "Bronx"], "songs")) assert_equal({"songs"=>["The Gargoyle", "Bronx"]}, doc) end end end describe "HashBinding" do describe "with plain text items" do before do @property = Representable::Hash::Binding.new(Representable::Definition.new(:songs, :hash => true)) end it "extracts with #read" do assert_equal({"first" => "The Gargoyle", "second" => "Bronx"} , @property.read({"songs" => {"first" => "The Gargoyle", "second" => "Bronx"}}, "songs")) end it "inserts with #write" do doc = {} assert_equal({"first" => "The Gargoyle", "second" => "Bronx"}, @property.write(doc, {"first" => "The Gargoyle", "second" => "Bronx"}, "songs")) assert_equal({"songs"=>{"first" => "The Gargoyle", "second" => "Bronx"}}, doc) end end describe "with objects" do before do @property = Representable::Hash::Binding.new(Representable::Definition.new(:songs, :hash => true, :class => Song, :extend => SongRepresenter)) end it "doesn't change the represented hash in #write" do song = Song.new("Better Than That") hash = {"first" => song} @property.write({}, hash, "song") assert_equal({"first" => song}, hash) end end end end representable-3.2.0/test/decorator_scope_test.rb0000644000004100000410000000141714240717435022127 0ustar www-datawww-datarequire 'test_helper' # TODO: remove in 2.0. class DecoratorScopeTest < MiniTest::Spec representer! do property :title, :getter => lambda { |*| title_from_representer }, :decorator_scope => true end let(:representer_with_method) { Module.new do include Representable::Hash property :title, :decorator_scope => true def title; "Crystal Planet"; end end } it "executes lambdas in represented context" do _(Class.new do def title_from_representer "Sounds Of Silence" end end.new.extend(representer).to_hash).must_equal({"title"=>"Sounds Of Silence"}) end it "executes method in represented context" do _(Object.new.extend(representer_with_method).to_hash).must_equal({"title"=>"Crystal Planet"}) end endrepresentable-3.2.0/test/config/0000755000004100000410000000000014240717435016632 5ustar www-datawww-datarepresentable-3.2.0/test/config/inherit_test.rb0000644000004100000410000000711414240717435021663 0ustar www-datawww-datarequire 'test_helper' # tests defining representers in modules, decorators and classes and the inheritance when combined. class ConfigInheritTest < MiniTest::Spec def assert_cloned(child, parent, property) child_def = child.representable_attrs.get(property) parent_def = parent.representable_attrs.get(property) child_def.merge!(:alias => property) _(child_def[:alias]).wont_equal parent_def[:alias] _(child_def.object_id).wont_equal parent_def.object_id end # class Object # end module GenreModule include Representable::Hash property :genre end # in Decorator ------------------------------------------------ class Decorator < Representable::Decorator include Representable::Hash property :title property :artist do property :id end end it { _(Decorator.definitions.keys).must_equal ["title", "artist"] } # in inheriting Decorator class InheritingDecorator < Decorator property :location end it { _(InheritingDecorator.definitions.keys).must_equal ["title", "artist", "location"] } it { assert_cloned(InheritingDecorator, Decorator, "title") } it do _(InheritingDecorator.representable_attrs.get(:artist).representer_module.object_id).wont_equal Decorator.representable_attrs.get(:artist).representer_module.object_id end # in inheriting and including Decorator class InheritingAndIncludingDecorator < Decorator include GenreModule property :location end it { _(InheritingAndIncludingDecorator.definitions.keys).must_equal ["title", "artist", "genre", "location"] } it { assert_cloned(InheritingAndIncludingDecorator, GenreModule, :genre) } # in module --------------------------------------------------- module Module include Representable property :title end it { _(Module.definitions.keys).must_equal ["title"] } # in module including module module SubModule include Representable include Module property :location end it { _(SubModule.definitions.keys).must_equal ["title", "location"] } it { assert_cloned(SubModule, Module, :title) } # including preserves order module IncludingModule include Representable property :genre include Module property :location end it { _(IncludingModule.definitions.keys).must_equal ["genre", "title", "location"] } # included in class ------------------------------------------- class Class include Representable include IncludingModule end it { _(Class.definitions.keys).must_equal ["genre", "title", "location"] } it { assert_cloned(Class, IncludingModule, :title) } it { assert_cloned(Class, IncludingModule, :location) } it { assert_cloned(Class, IncludingModule, :genre) } # included in class with order class DefiningClass include Representable property :street_cred include IncludingModule end it { _(DefiningClass.definitions.keys).must_equal ["street_cred", "genre", "title", "location"] } # in class class RepresenterClass include Representable property :title end it { _(RepresenterClass.definitions.keys).must_equal ["title"] } # in inheriting class class InheritingClass < RepresenterClass include Representable property :location end it { _(InheritingClass.definitions.keys).must_equal ["title", "location"] } it { assert_cloned(InheritingClass, RepresenterClass, :title) } # in inheriting class and including class InheritingAndIncludingClass < RepresenterClass property :location include GenreModule end it { _(InheritingAndIncludingClass.definitions.keys).must_equal ["title", "location", "genre"] } endrepresentable-3.2.0/test/wrap_test.rb0000644000004100000410000001123714240717435017726 0ustar www-datawww-datarequire "test_helper" class WrapTest < MiniTest::Spec class HardcoreBand include Representable::Hash end class SoftcoreBand < HardcoreBand end let(:band) { HardcoreBand.new } it "returns false per default" do assert_nil SoftcoreBand.new.send(:representation_wrap) end it "infers a printable class name if set to true" do HardcoreBand.representation_wrap = true assert_equal "hardcore_band", band.send(:representation_wrap) end it "can be set explicitely" do HardcoreBand.representation_wrap = "breach" assert_equal "breach", band.send(:representation_wrap) end for_formats( :hash => [Representable::Hash, {"Blink182"=>{"genre"=>"Pop"}}, {"Blink182"=>{"genre"=>"Poppunk"}}], :json => [Representable::JSON, "{\"Blink182\":{\"genre\":\"Pop\"}}", "{\"Blink182\":{\"genre\":\"Poppunk\"}}"], :xml => [Representable::XML, "Pop", "Poppunk"], # :yaml => [Representable::YAML, "---\nBlink182:\n"], # TODO: fix YAML. ) do |format, mod, output, input| describe "[#{format}] dynamic wrap" do let(:band) { representer.prepare(Struct.new(:name, :genre).new("Blink", "Pop")) } let(:format) { format } representer!(:module => mod) do self.representation_wrap = lambda { |number:, **| "#{name}#{number}" } property :genre end it { render(band, {:number => 182}).must_equal_document(output) } it { _(parse(band, input, {:number => 182}).genre).must_equal "Poppunk" } # TODO: better test. also, xml parses _any_ wrap. end end end class HashDisableWrapTest < MiniTest::Spec Band = Struct.new(:name, :label) Album = Struct.new(:band) Label = Struct.new(:name) class BandDecorator < Representable::Decorator include Representable::Hash self.representation_wrap = :bands property :name property :label do # this should have a wrap! self.representation_wrap = :important property :name end end let(:band) { BandDecorator.prepare(Band.new("Social Distortion")) } # direct, local api. it do _(band.to_hash).must_equal({"bands" => {"name"=>"Social Distortion"}}) _(band.to_hash(wrap: false)).must_equal({"name"=>"Social Distortion"}) _(band.to_hash(wrap: :band)).must_equal(:band=>{"name"=>"Social Distortion"}) end it do _(band.from_hash({"bands" => {"name"=>"Social Distortion"}}).name).must_equal "Social Distortion" _(band.from_hash({"name"=>"Social Distortion"}, wrap: false).name).must_equal "Social Distortion" _(band.from_hash({band: {"name"=>"Social Distortion"}}, wrap: :band).name).must_equal "Social Distortion" end class AlbumDecorator < Representable::Decorator include Representable::Hash self.representation_wrap = :albums property :band, decorator: BandDecorator, wrap: false, class: Band end let(:album) { AlbumDecorator.prepare(Album.new(Band.new("Social Distortion", Label.new("Epitaph")))) } # band has wrap turned off per property definition, however, label still has wrap. it "renders" do _(album.to_hash).must_equal({"albums" => {"band" => {"name"=>"Social Distortion", "label"=>{"important"=>{"name"=>"Epitaph"}}}}}) end it "parses" do _(album.from_hash({"albums" => {"band" => {"name"=>"Rvivr"}}}).band.name).must_equal "Rvivr" end end class XMLDisableWrapTest < MiniTest::Spec Band = Struct.new(:name, :label) Album = Struct.new(:band) Label = Struct.new(:name) class BandDecorator < Representable::Decorator include Representable::XML self.representation_wrap = :bands # when nested, it uses this. property :name # property :label do # this should have a wrap! # self.representation_wrap = :important # property :name # end end let(:band) { BandDecorator.prepare(Band.new("Social Distortion")) } it do band.to_xml.must_equal_xml "Social Distortion" band.to_xml(wrap: "combo").must_equal_xml "Social Distortion" end class AlbumDecorator < Representable::Decorator include Representable::XML self.representation_wrap = :albums property :band, decorator: BandDecorator, wrap: "po", class: Band end let(:album) { AlbumDecorator.prepare(Album.new(Band.new("Social Distortion", Label.new("Epitaph")))) } # band has wrap turned of per property definition, however, label still has wrap. it "rendersxx" do skip _(album.to_xml).must_equal({"albums" => {"band" => {"name"=>"Social Distortion", "label"=>{"important"=>{"name"=>"Epitaph"}}}}}) end # it "parses" do # album.from_hash({"albums" => {"band" => {"name"=>"Rvivr"}}}).band.name.must_equal "Rvivr" # end end representable-3.2.0/test/serialize_deserialize_test.rb0000644000004100000410000000205314240717435023320 0ustar www-datawww-datarequire 'test_helper' class SerializeDeserializeTest < BaseTest subject { Struct.new(:song).new.extend(representer) } describe "deserialize" do representer! do property :song, :instance => lambda { |options| options[:input].to_s.upcase }, :prepare => lambda { |options| options[:input] }, :deserialize => lambda { |options| "#{options[:input]} #{options[:fragment]} #{options[:user_options].inspect}" } end it { _(subject.from_hash({"song" => Object}, user_options: {volume: 9}).song).must_equal "OBJECT Object {:volume=>9}" } end describe "serialize" do representer! do property :song, :representable => true, :prepare => lambda { |options| options[:fragment] }, :serialize => lambda { |options| "#{options[:input]} #{options[:user_options].inspect}" } end before { subject.song = "Arrested In Shanghai" } it { _(subject.to_hash(user_options: {volume: 9})).must_equal({"song"=>"Arrested In Shanghai {:volume=>9}"}) } end endrepresentable-3.2.0/test/user_options_test.rb0000644000004100000410000000133114240717435021500 0ustar www-datawww-datarequire "test_helper" class UserOptionsTest < Minitest::Spec Song = Struct.new(:title) representer! do property :title, if: ->(options) { options[:user_options][:visible] } end it { _(Song.new("Run With It").extend(representer).to_hash).must_equal({}) } it { _(Song.new("Run With It").extend(representer).to_hash(user_options: {visible: true})).must_equal({"title"=>"Run With It"}) } it { _(Song.new("Run With It").extend(representer).from_hash("title"=>"Business Conduct").title).must_equal "Run With It" } it { _(Song.new("Run With It").extend(representer).from_hash({"title"=>"Business Conduct"}, user_options: {visible: true}).title).must_equal "Business Conduct" } end # Representable.deprecations=falserepresentable-3.2.0/test/is_representable_test.rb0000644000004100000410000000374714240717435022312 0ustar www-datawww-datarequire 'test_helper' class IsRepresentableTest < BaseTest describe "representable: false, extend:" do representer!(:inject => :song_representer) do property :song, :representable => false, :extend => song_representer end it "does extend but doesn't call #to_hash" do _(Struct.new(:song).new(song = Object.new).extend(representer). to_hash).must_equal("song" => song) _(song).must_be_kind_of Representable::Hash end end describe "representable: true, no extend:" do representer!(:inject => :song_representer) do property :song, :representable => true end it "doesn't extend but calls #to_hash" do song = Object.new song.instance_eval do def to_hash(*) 1 end end _(Struct.new(:song).new(song).extend(representer). to_hash).must_equal("song" => 1) _(song).wont_be_kind_of Representable::Hash end end # TODO: TEST implement for from_hash. describe "representable: false, with class:" do representer!(:inject => :song_representer) do property :song, :representable => false, :class => OpenStruct, :extend => song_representer end it "does extend but doesn't call #from_hash" do hit = Struct.new(:song).new.extend(representer). from_hash("song" => 1) _(hit.song).must_equal OpenStruct.new _(hit.song).must_be_kind_of Representable::Hash end end describe "representable: true, without extend: but class:" do SongReader = Class.new do def from_hash(*) "Piano?" end end representer!(:inject => :song_representer) do property :song, :representable => true, :class => SongReader end it "doesn't extend but calls #from_hash" do hit = Struct.new(:song).new.extend(representer). from_hash("song" => "Sonata No.2") _(hit.song).must_equal "Piano?" _(hit.song).wont_be_kind_of Representable::Hash end end endrepresentable-3.2.0/test/representable_test.rb0000644000004100000410000003616414240717435021616 0ustar www-datawww-datarequire 'test_helper' class RepresentableTest < MiniTest::Spec class Band include Representable::Hash property :name attr_accessor :name end class PunkBand < Band property :street_cred attr_accessor :street_cred end module BandRepresentation include Representable property :name end module PunkBandRepresentation include Representable include BandRepresentation property :street_cred end describe "#representable_attrs" do describe "in module" do it "allows including the concrete representer module later" do vd = class VD attr_accessor :name, :street_cred include Representable::JSON include PunkBandRepresentation end.new vd.name = "Vention Dention" vd.street_cred = 1 assert_json "{\"name\":\"Vention Dention\",\"street_cred\":1}", vd.to_json end #it "allows including the concrete representer module only" do # require 'representable/json' # module RockBandRepresentation # include Representable::JSON # property :name # end # vd = class VH # include RockBandRepresentation # end.new # vd.name = "Van Halen" # assert_equal "{\"name\":\"Van Halen\"}", vd.to_json #end end end describe "inheritance" do class CoverSong < OpenStruct end module SongRepresenter include Representable::Hash property :name end module CoverSongRepresenter include Representable::Hash include SongRepresenter property :by end it "merges properties from all ancestors" do props = {"name"=>"The Brews", "by"=>"Nofx"} assert_equal(props, CoverSong.new(props).extend(CoverSongRepresenter).to_hash) end it "allows mixing in multiple representers" do class Bodyjar include Representable::XML include Representable::JSON include PunkBandRepresentation self.representation_wrap = "band" attr_accessor :name, :street_cred end band = Bodyjar.new band.name = "Bodyjar" assert_json "{\"band\":{\"name\":\"Bodyjar\"}}", band.to_json assert_xml_equal "Bodyjar", band.to_xml end it "allows extending with different representers subsequentially" do module SongXmlRepresenter include Representable::XML property :name, :as => "name", :attribute => true end module SongJsonRepresenter include Representable::JSON property :name end @song = Song.new("Days Go By") assert_xml_equal "", @song.extend(SongXmlRepresenter).to_xml assert_json "{\"name\":\"Days Go By\"}", @song.extend(SongJsonRepresenter).to_json end # test if we call super in # ::inherited # ::included # ::extended module Representer include Representable # overrides ::inherited. end class BaseClass def self.inherited(subclass) super subclass.instance_eval { def other; end } end include Representable # overrides ::inherited. include Representer end class SubClass < BaseClass # triggers Representable::inherited, then OtherModule::inherited. end # test ::inherited. it do _(BaseClass.respond_to?(:other)).must_equal false _(SubClass.respond_to?(:other)).must_equal true end module DifferentIncluded def included(includer) includer.instance_eval { def different; end } end end module CombinedIncluded extend DifferentIncluded # defines ::included. include Representable # overrides ::included. end class IncludingClass include Representable include CombinedIncluded end # test ::included. it do IncludingClass.respond_to?(:representable_attrs) # from Representable IncludingClass.respond_to?(:different) end end describe "#property" do it "doesn't modify options hash" do options = {} representer.property(:title, options) _(options).must_equal({}) end representer! {} it "returns the Definition instance" do _(representer.property(:name)).must_be_kind_of Representable::Definition end end describe "#collection" do class RockBand < Band collection :albums end it "creates correct Definition" do assert_equal "albums", RockBand.representable_attrs.get(:albums).name assert RockBand.representable_attrs.get(:albums).array? end end describe "#hash" do it "also responds to the original method" do assert_kind_of Integer, BandRepresentation.hash end end class Hometown attr_accessor :name end module HometownRepresentable include Representable::JSON property :name end # DISCUSS: i don't like the JSON requirement here, what about some generic test module? class PopBand include Representable::JSON property :name property :groupies property :hometown, class: Hometown, extend: HometownRepresentable attr_accessor :name, :groupies, :hometown end describe "#update_properties_from" do before do @band = PopBand.new end it "copies values from document to object" do @band.from_hash({"name"=>"No One's Choice", "groupies"=>2}) assert_equal "No One's Choice", @band.name assert_equal 2, @band.groupies end it "ignores non-writeable properties" do @band = Class.new(Band) { property :name; collection :founders, :writeable => false; attr_accessor :founders }.new @band.from_hash("name" => "Iron Maiden", "groupies" => 2, "founders" => ["Steve Harris"]) assert_equal "Iron Maiden", @band.name assert_nil @band.founders end it "always returns the represented" do assert_equal @band, @band.from_hash({"name"=>"Nofx"}) end it "includes false attributes" do @band.from_hash({"groupies"=>false}) refute @band.groupies end it "ignores properties not present in the incoming document" do @band.instance_eval do def name=(*); raise "I should never be called!"; end end @band.from_hash({}) end # FIXME: do we need this test with XML _and_ JSON? it "ignores (no-default) properties not present in the incoming document" do { Representable::Hash => [:from_hash, {}], Representable::XML => [:from_xml, xml(%{}).to_s] }.each do |format, config| nested_repr = Module.new do # this module is never applied. # FIXME: can we make that a simpler test? include format property :created_at end repr = Module.new do include format property :name, :class => Object, :extend => nested_repr end @band = Band.new.extend(repr) @band.send(config.first, config.last) assert_nil @band.name, "Failed in #{format}" end end describe "passing options" do module TrackRepresenter include Representable::Hash end representer! do property :track, class: OpenStruct do property :nr property :length, class: OpenStruct do def to_hash(options) {seconds: options[:user_options][:nr]} end def from_hash(hash, options) super.tap do self.seconds = options[:user_options][:nr] end end end def to_hash(options) super.merge({"nr" => options[:user_options][:nr]}) end def from_hash(data, options) super.tap do self.nr = options[:user_options][:nr] end end end end it "#to_hash propagates to nested objects" do _(OpenStruct.new(track: OpenStruct.new(nr: 1, length: OpenStruct.new(seconds: nil))).extend(representer).extend(Representable::Debug). to_hash(user_options: {nr: 9})).must_equal({"track"=>{"nr"=>9, "length"=>{seconds: 9}}}) end it "#from_hash propagates to nested objects" do song = OpenStruct.new.extend(representer).from_hash({"track"=>{"nr" => "replace me", "length"=>{"seconds"=>"replacing"}}}, user_options: {nr: 9}) _(song.track.nr).must_equal 9 _(song.track.length.seconds).must_equal 9 end end end describe "#create_representation_with" do before do @band = PopBand.new @band.name = "No One's Choice" @band.groupies = 2 end it "compiles document from properties in object" do assert_equal({"name"=>"No One's Choice", "groupies"=>2}, @band.to_hash) end it "ignores non-readable properties" do @band = Class.new(Band) { property :name; collection :founder_ids, :readable => false; attr_accessor :founder_ids }.new @band.name = "Iron Maiden" @band.founder_ids = [1,2,3] hash = @band.to_hash assert_equal({"name" => "Iron Maiden"}, hash) end it "does not write nil attributes" do @band.groupies = nil assert_equal({"name"=>"No One's Choice"}, @band.to_hash) end it "writes false attributes" do @band.groupies = false assert_equal({"name"=>"No One's Choice","groupies"=>false}, @band.to_hash) end end describe ":extend and :class" do module UpcaseRepresenter include Representable def to_hash(*); upcase; end def from_hash(hsh, *args); replace hsh.upcase; end # DISCUSS: from_hash must return self. end module DowncaseRepresenter include Representable def to_hash(*); downcase; end def from_hash(hsh, *args); replace hsh.downcase; end end class UpcaseString < String; end describe "lambda blocks" do representer! do property :name, :extend => lambda { |name, *| compute_representer(name) } end it "executes lambda in represented instance context" do _(Song.new("Carnage").instance_eval do def compute_representer(name) UpcaseRepresenter end self end.extend(representer).to_hash).must_equal({"name" => "CARNAGE"}) end end describe ":instance" do obj = String.new("Fate") mod = Module.new { include Representable; def from_hash(*); self; end } representer! do property :name, :extend => mod, :instance => lambda { |*| obj } end it "uses object from :instance but still extends it" do song = Song.new.extend(representer).from_hash("name" => "Eric's Had A Bad Day") _(song.name).must_equal obj _(song.name).must_be_kind_of mod end end describe "property with :extend" do representer! do property :name, :extend => lambda { |options| options[:input].is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String end it "uses lambda when rendering" do assert_equal({"name" => "you make me thick"}, Song.new("You Make Me Thick").extend(representer).to_hash ) assert_equal({"name" => "STEPSTRANGER"}, Song.new(UpcaseString.new "Stepstranger").extend(representer).to_hash ) end it "uses lambda when parsing" do _(Song.new.extend(representer).from_hash({"name" => "You Make Me Thick"}).name).must_equal "you make me thick" _(Song.new.extend(representer).from_hash({"name" => "Stepstranger"}).name).must_equal "stepstranger" # DISCUSS: we compare "".is_a?(UpcaseString) end describe "with :class lambda" do representer! do property :name, :extend => lambda { |options| options[:input].is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => lambda { |options| options[:fragment] == "Still Failing?" ? String : UpcaseString } end it "creates instance from :class lambda when parsing" do song = OpenStruct.new.extend(representer).from_hash({"name" => "Quitters Never Win"}) _(song.name).must_be_kind_of UpcaseString _(song.name).must_equal "QUITTERS NEVER WIN" song = OpenStruct.new.extend(representer).from_hash({"name" => "Still Failing?"}) _(song.name).must_be_kind_of String _(song.name).must_equal "still failing?" end end end describe "collection with :extend" do representer! do collection :songs, :extend => lambda { |options| options[:input].is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String end it "uses lambda for each item when rendering" do _(Album.new([UpcaseString.new("Dean Martin"), "Charlie Still Smirks"]).extend(representer).to_hash).must_equal("songs"=>["DEAN MARTIN", "charlie still smirks"]) end it "uses lambda for each item when parsing" do album = Album.new.extend(representer).from_hash("songs"=>["DEAN MARTIN", "charlie still smirks"]) _(album.songs).must_equal ["dean martin", "charlie still smirks"] # DISCUSS: we compare "".is_a?(UpcaseString) end describe "with :class lambda" do representer! do collection :songs, :extend => lambda { |options| options[:input].is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => lambda { |options| options[:input] == "Still Failing?" ? String : UpcaseString } end it "creates instance from :class lambda for each item when parsing" do album = Album.new.extend(representer).from_hash("songs"=>["Still Failing?", "charlie still smirks"]) _(album.songs).must_equal ["still failing?", "CHARLIE STILL SMIRKS"] end end end describe ":decorator" do let(:extend_rpr) { Module.new { include Representable::Hash; collection :songs, :extend => SongRepresenter } } let(:decorator_rpr) { Module.new { include Representable::Hash; collection :songs, :decorator => SongRepresenter } } let(:songs) { [Song.new("Bloody Mary")] } it "is aliased to :extend" do _(Album.new(songs).extend(extend_rpr).to_hash).must_equal Album.new(songs).extend(decorator_rpr).to_hash end end # TODO: Move to global place since it's used twice. class SongRepresentation < Representable::Decorator include Representable::JSON property :name end class AlbumRepresentation < Representable::Decorator include Representable::JSON collection :songs, :class => Song, :extend => SongRepresentation end describe "::prepare" do let(:song) { Song.new("Still Friends In The End") } let(:album) { Album.new([song]) } describe "module including Representable" do it "uses :extend strategy" do album_rpr = Module.new { include Representable::Hash; collection :songs, :class => Song, :extend => SongRepresenter} _(album_rpr.prepare(album).to_hash).must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]}) _(album).must_respond_to :to_hash end end describe "Decorator subclass" do it "uses :decorate strategy" do _(AlbumRepresentation.prepare(album).to_hash).must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]}) _(album).wont_respond_to :to_hash end end end end end representable-3.2.0/test/nested_test.rb0000644000004100000410000000712614240717435020241 0ustar www-datawww-datarequire 'test_helper' class NestedTest < MiniTest::Spec Album = Struct.new(:label, :owner, :amount) for_formats( :hash => [Representable::Hash, {"label" => {"label"=>"Epitaph", "owner"=>"Brett Gurewitz", "releases"=>{"amount"=>19}}}], # :xml => [Representable::XML, ""], :yaml => [Representable::YAML, "---\nlabel:\n label: Epitaph\n owner: Brett Gurewitz\n releases:\n amount: 19\n"] ) do |format, mod, output, input| [false, true].each do |is_decorator| describe "::nested with (inline representer|decorator): #{is_decorator}" do let(:format) { format } representer!(:module => mod, :decorator => is_decorator) do nested :label do property :label property :owner # self.representation_wrap = nil if format == :xml nested :releases do property :amount end end # self.representation_wrap = :album if format == :xml end let(:album) { Album.new("Epitaph", "Brett Gurewitz", 19) } let(:decorator) { representer.prepare(album) } it "renders nested Album-properties in separate section" do render(decorator).must_equal_document output # do not use extend on the nested object. # FIXME: make this a proper test with two describes instead of this pseudo-meta stuff. if is_decorator==true _(album).wont_be_kind_of(Representable::Hash) end end it "parses nested properties to Album instance" do album = parse(representer.prepare(Album.new), output) _(album.label).must_equal "Epitaph" _(album.owner).must_equal "Brett Gurewitz" end end end describe "Decorator ::nested with extend:" do let(:format) { format } representer!(:name => :label_rpr) do include mod property :label property :owner nested :releases do # DISCUSS: do we need to test this? property :amount end end representer!(:module => mod, :decorator => true, :inject => :label_rpr) do nested :label, :extend => label_rpr self.representation_wrap = :album if format == :xml end let(:album) { representer.prepare(Album.new("Epitaph", "Brett Gurewitz", 19)) } # TODO: shared example with above. it "renders nested Album-properties in separate section" do render(album).must_equal_document output end it "parses nested properties to Album instance" do album = parse(representer.prepare(Album.new), output) _(album.label).must_equal "Epitaph" _(album.owner).must_equal "Brett Gurewitz" _(album.amount).must_equal 19 end end end describe "::nested without block but with inherit:" do representer!(:name => :parent) do include Representable::Hash nested :label do property :owner end end representer!(:module => Representable::Hash, :inject => :parent) do include parent nested :label, :inherit => true, :as => "Label" end let(:album) { representer.prepare(Album.new("Epitaph", "Brett Gurewitz", 19)) } it "renders nested Album-properties in separate section" do _(representer.prepare(album).to_hash).must_equal({"Label"=>{"owner"=>"Brett Gurewitz"}}) end # it "parses nested properties to Album instance" do # album = parse(representer.prepare(Album.new), output) # album.label.must_equal "Epitaph" # album.owner.must_equal "Brett Gurewitz" # album.amount.must_equal 19 # end end endrepresentable-3.2.0/test/option_test.rb0000644000004100000410000000156314240717435020266 0ustar www-datawww-data# frozen_string_literal: true require 'test_helper' class OptionTest < MiniTest::Spec class Callable include Uber::Callable def call(*); "callable" end end class MyRepresenter < Representable::Decorator include Representable::JSON property :static, getter: "static" property :symbol, getter: :symbol property :proc, getter: ->(*) { "proc" } property :callable, getter: Callable.new end Album = Struct.new(:static) do def symbol(*); "symbol" end end let(:album_representer) { MyRepresenter.new(Album.new) } describe ::Representable::Option do it "supports all types of callables (method, proc, static etc)" do _(album_representer.to_hash).must_equal({ "static" => "static", "symbol" => "symbol", "proc" => "proc", "callable" => "callable", }) end end end representable-3.2.0/test/inline_test.rb0000644000004100000410000002101614240717435020227 0ustar www-datawww-datarequire 'test_helper' class InlineTest < MiniTest::Spec let(:song) { Song.new("Alive") } let(:request) { representer.prepare(OpenStruct.new(:song => song)) } { :hash => [Representable::Hash, {"song"=>{"name"=>"Alive"}}, {"song"=>{"name"=>"You've Taken Everything"}}], :json => [Representable::JSON, "{\"song\":{\"name\":\"Alive\"}}", "{\"song\":{\"name\":\"You've Taken Everything\"}}"], :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything/open_struct>"], :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"], }.each do |format, cfg| mod, output, input = cfg describe "[#{format}] with :class" do representer!(:module => mod) do property :song, :class => Song do property :name end end let(:format) { format } it { render(request).must_equal_document output } it { _(parse(request, input).song.name).must_equal "You've Taken Everything"} end end { :hash => [Representable::Hash, {"songs"=>[{"name"=>"Alive"}]}, {"songs"=>[{"name"=>"You've Taken Everything"}]}], :json => [Representable::JSON, "{\"songs\":[{\"name\":\"Alive\"}]}", "{\"songs\":[{\"name\":\"You've Taken Everything\"}]}"], :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything", { :as => :song }], :yaml => [Representable::YAML, "---\nsongs:\n- name: Alive\n", "---\nsongs:\n- name: You've Taken Everything\n"], }.each do |format, cfg| mod, output, input, collection_options = cfg collection_options ||= {} describe "[#{format}] collection with :class" do let(:request) { representer.prepare(OpenStruct.new(:songs => [song])) } representer!(:module => mod) do collection :songs, collection_options.merge(:class => Song) do property :name end end let(:format) { format } # FIXME: why do we have to define this? it { render(request).must_equal_document output } it { _(parse(request, input).songs.first.name).must_equal "You've Taken Everything"} end end describe "without :class" do representer! do property :song do property :name end end it { _(request.to_hash).must_equal({"song"=>{"name"=>"Alive"}}) } end for_formats( :hash => [Representable::Hash, {}], # :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything/open_struct>"], # :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"], ) do |format, mod, input| describe "parsing [#{format}] where nested property missing" do representer!(:module => mod) do property :song do property :name end end it "doesn't change represented object" do _(request.send("from_#{format}", input).song).must_equal song end end end describe "inheriting from outer representer" do let(:request) { Struct.new(:song, :requester).new(song, "Josephine") } [false, true].each do |is_decorator| # test for module and decorator. representer!(:decorator => is_decorator) do property :requester property :song, :class => Song do property :name end end let(:decorator) { representer.prepare(request) } it { _(decorator.to_hash).must_equal({"requester"=>"Josephine", "song"=>{"name"=>"Alive"}}) } it { _(decorator.from_hash({"song"=>{"name"=>"You've Taken Everything"}}).song.name).must_equal "You've Taken Everything"} end end describe "object pollution" do representer!(:decorator => true) do property :song do property :name end end it "uses an inline decorator and doesn't alter represented" do representer.prepare(Struct.new(:song).new(song)).to_hash _(song).wont_be_kind_of Representable end end # TODO: should be in extend:/decorator: test. # FIXME: this tests :getter{represented}+:extend - represented gets extended twice and the inline decorator overrides original config. # for_formats( # :hash => [Representable::Hash, {"album" => {"artist" => {"label"=>"Epitaph"}}}], # # :xml => [Representable::XML, ""], # #:yaml => [Representable::YAML, "---\nlabel:\n label: Epitaph\n owner: Brett Gurewitz\n"] # ) do |format, mod, output, input| # module ArtistRepresenter # include Representable::JSON # property :label # end # describe ":getter with inline representer" do # let(:format) { format } # representer!(:module => mod) do # self.representation_wrap = :album # property :artist, :getter => lambda { |args| represented }, :extend => ArtistRepresenter # end # let(:album) { OpenStruct.new(:label => "Epitaph").extend(representer) } # it "renders nested Album-properties in separate section" do # render(album).must_equal_document output # end # end # end for_formats({ :hash => [Representable::Hash, {"album" => {"artist" => {"label"=>"Epitaph"}}}], # :xml => [Representable::XML, ""], #:yaml => [Representable::YAML, "---\nlabel:\n label: Epitaph\n owner: Brett Gurewitz\n"] }) do |format, mod, output, input| class ArtistDecorator < Representable::Decorator include Representable::JSON property :label end describe ":getter with :decorator" do let(:format) { format } representer!(:module => mod) do self.representation_wrap = "album" property :artist, :getter => lambda { |args| represented }, :decorator => ArtistDecorator end let(:album) { OpenStruct.new(:label => "Epitaph").extend(representer) } it "renders nested Album-properties in separate section" do render(album).must_equal_document output end end end # test helper methods within inline representer for_formats({ :hash => [Representable::Hash, {"song"=>{"name"=>"ALIVE"}}], :xml => [Representable::XML, "\n \n ALIVE\n \n"], :yaml => [Representable::YAML, "---\nsong:\n name: ALIVE\n"], }) do |format, mod, output| describe "helper method within inline representer [#{format}]" do let(:format) { format } representer!(:module => mod, :decorator => true) do self.representation_wrap = :request if format == :xml property :requester property :song do property :name, :exec_context => :decorator define_method :name do represented.name.upcase end self.representation_wrap = :song if format == :xml end end let(:request) { representer.prepare(OpenStruct.new(:song => Song.new("Alive"))) } it do render(request).must_equal_document output end end end describe "include module in inline representers" do representer! do extension = Module.new do include Representable::Hash property :title end property :song do include extension property :artist end end it do _(OpenStruct.new(:song => OpenStruct.new(:title => "The Fever And The Sound", :artist => "Strung Out")).extend(representer). to_hash). must_equal({"song"=>{"artist"=>"Strung Out", "title"=>"The Fever And The Sound"}}) end end # define method in inline representer describe "define method in inline representer" do Mod = Module.new do include Representable::Hash def song "Object.new" end property :song do property :duration def duration "6:53" end end end it { _(Object.new.extend(Mod).to_hash).must_equal("song"=>{"duration"=>"6:53"}) } end # define method inline with Decorator describe "define method inline with Decorator" do dec = Class.new(Representable::Decorator) do include Representable::Hash def song "Object.new" end property :song, :exec_context => :decorator do property :duration, :exec_context => :decorator def duration "6:53" end end end it { _(dec.new(Object.new).to_hash).must_equal("song"=>{"duration"=>"6:53"}) } end end representable-3.2.0/test/xml_namespace_test.rb0000644000004100000410000001315414240717435021571 0ustar www-datawww-datarequire "test_helper" # # # 0836217462 # Being a Dog Is a Full-Time Job # # Charles M Schulz # 1922-11-26 # 2000-02-12 # # # Peppermint Patty # 1966-08-22 # bold, brash and tomboyish # # # Snoopy # 1950-10-04 # extroverted beagle # # # Schroeder # 1951-05-30 # brought classical music to the Peanuts strip # # # # Lucy # 1952-03-03 # bossy, crabby and selfish # # # # Theoretically, every property (scalar) needs/should have a namespace. # property :name, namespace: "http://eric.van-der-vlist.com/ns/person" # # This is implicit if contained: # class Person < Decorator # namespace: "http://eric.van-der-vlist.com/ns/person" # property :name #, namespace: "http://eric.van-der-vlist.com/ns/person" # end # class Library # namespace "http://eric.van-der-vlist.com/ns/library" # namespace_def lib: "http://eric.van-der-vlist.com/ns/library" # namespace_def person: "http://eric.van-der-vlist.com/ns/person" # # namespace_def person: Person # property :person, decorator: Person # end class NamespaceXMLTest < Minitest::Spec module Model Library = Struct.new(:book) Book = Struct.new(:id, :isbn) end let (:book) { Model::Book.new(1, 666) } #:simple-class class Library < Representable::Decorator feature Representable::XML feature Representable::XML::Namespace namespace "http://eric.van-der-vlist.com/ns/library" property :book do namespace "http://eric.van-der-vlist.com/ns/library" property :id, attribute: true property :isbn end end #:simple-class end # default namespace for library it "what" do Library.new(Model::Library.new(book)).to_xml.must_xml( #:simple-xml %{ 666 } #:simple-xml end ) end end class Namespace2XMLTest < Minitest::Spec module Model Library = Struct.new(:book) Book = Struct.new(:id, :isbn, :author, :character) Character = Struct.new(:name, :born, :qualification) end let (:book) { Model::Book.new(1, 666, Model::Character.new("Fowler"), [Model::Character.new("Frau Java", 1991, "typed")]) } #:map-class class Library < Representable::Decorator feature Representable::XML feature Representable::XML::Namespace namespace "http://eric.van-der-vlist.com/ns/library" namespace_def lib: "http://eric.van-der-vlist.com/ns/library" namespace_def hr: "http://eric.van-der-vlist.com/ns/person" property :book, class: Model::Book do namespace "http://eric.van-der-vlist.com/ns/library" property :id, attribute: true property :isbn property :author, class: Model::Character do namespace "http://eric.van-der-vlist.com/ns/person" property :name property :born end collection :character, class: Model::Character do namespace "http://eric.van-der-vlist.com/ns/library" property :qualification property :name, namespace: "http://eric.van-der-vlist.com/ns/person" property :born, namespace: "http://eric.van-der-vlist.com/ns/person" end end end #:map-class end it "renders" do Library.new(Model::Library.new(book)).to_xml.must_xml( #:map-xml %{ 666 Fowler typed Frau Java 1991 } #:map-xml end ) end it "parses" do lib = Model::Library.new #:parse-call Library.new(lib).from_xml(%{ 666 Fowler typed Frau Java Mr. Ruby Dr. Elixir 1991 } #:parse-call end ) _(lib.book.inspect).must_equal %{#, character=[#]>} #:parse-res lib.book.character[0].name #=> "Frau Java" #:parse-res end end end representable-3.2.0/test/object_test.rb0000644000004100000410000000327314240717435020224 0ustar www-datawww-datarequire "test_helper" require "representable/object" class ObjectTest < MiniTest::Spec Song = Struct.new(:title, :album) Album = Struct.new(:name, :songs) representer!(module: Representable::Object) do property :title property :album, instance: lambda { |options| options[:fragment].name.upcase!; options[:fragment] } do property :name collection :songs, instance: lambda { |options| options[:fragment].title.upcase!; options[:fragment] } do property :title end end # TODO: collection end let(:source) { Song.new("The King Is Dead", Album.new("Ruiner", [Song.new("In Vino Veritas II")])) } let(:target) { Song.new } it do representer.prepare(target).from_object(source) _(target.title).must_equal "The King Is Dead" _(target.album.name).must_equal "RUINER" _(target.album.songs[0].title).must_equal "IN VINO VERITAS II" end # ignore nested object when nil it do representer.prepare(Song.new("The King Is Dead")).from_object(Song.new) _(target.title).must_be_nil # scalar property gets overridden when nil. _(target.album).must_be_nil # nested property stays nil. end # to_object describe "#to_object" do representer!(module: Representable::Object) do property :title property :album, render_filter: lambda { |input, options|input.name = "Live";input } do property :name collection :songs, render_filter: lambda { |input, options|input[0].title = 1;input } do property :title end end end it do representer.prepare(source).to_object _(source.album.name).must_equal "Live" _(source.album.songs[0].title).must_equal 1 end end endrepresentable-3.2.0/test/xml_test.rb0000644000004100000410000003751714240717435017566 0ustar www-datawww-datarequire 'test_helper' class XmlPublicMethodsTest < Minitest::Spec #--- # from_hash class BandRepresenter < Representable::Decorator include Representable::XML property :id property :name end let(:data) { %{1Rancid} } it { _(BandRepresenter.new(Band.new).from_xml(data)[:id, :name]).must_equal ["1", "Rancid"] } it { _(BandRepresenter.new(Band.new).parse(data)[:id, :name]).must_equal ["1", "Rancid"] } #--- # to_hash let(:band) { Band.new("1", "Rancid") } it { BandRepresenter.new(band).to_xml.must_equal_xml data } it { BandRepresenter.new(band).render.must_equal_xml data } end class XmlTest < MiniTest::Spec class Band include Representable::XML property :name attr_accessor :name def initialize(name=nil) name and self.name = name end end XML = Representable::XML Def = Representable::Definition describe "Xml module" do before do @Band = Class.new do include Representable::XML self.representation_wrap = :band property :name property :label attr_accessor :name, :label end @band = @Band.new end describe "#from_xml" do before do @band = @Band.new @xml = %{Nofx} end it "parses XML and assigns properties" do @band.from_xml(@xml) assert_equal ["Nofx", "NOFX"], [@band.name, @band.label] end end describe "#from_xml with remove_namespaces! and xmlns present" do before do @Band.remove_namespaces! @band = @Band.new @xml = %{Nofx} end it "parses with xmlns present" do @band.from_xml(@xml) assert_equal ["Nofx", "NOFX"], [@band.name, @band.label] end end describe "#from_node" do before do @band = @Band.new @xml = Nokogiri::XML(%{Nofx}).root end it "receives Nokogiri node and assigns properties" do @band.from_node(@xml) assert_equal ["Nofx", "NOFX"], [@band.name, @band.label] end end describe "#to_xml" do it "delegates to #to_node and returns string" do assert_xml_equal "Rise Against", Band.new("Rise Against").to_xml end end describe "#to_node" do it "returns Nokogiri node" do node = Band.new("Rise Against").to_node assert_kind_of Nokogiri::XML::Element, node end it "wraps with infered class name per default" do node = Band.new("Rise Against").to_node assert_xml_equal "Rise Against", node.to_s end it "respects #representation_wrap=" do klass = Class.new(Band) do include Representable property :name end klass.representation_wrap = :group assert_xml_equal "Rise Against", klass.new("Rise Against").to_node.to_s end end describe "XML::Binding#build_for" do it "returns AttributeBinding" do assert_kind_of XML::Binding::Attribute, XML::Binding.build_for(Def.new(:band, :as => "band", :attribute => true)) end it "returns Binding" do assert_kind_of XML::Binding, XML::Binding.build_for(Def.new(:band, :class => Hash)) assert_kind_of XML::Binding, XML::Binding.build_for(Def.new(:band, :as => :content)) end it "returns CollectionBinding" do assert_kind_of XML::Binding::Collection, XML::Binding.build_for(Def.new(:band, :collection => :true)) end it "returns HashBinding" do assert_kind_of XML::Binding::Hash, XML::Binding.build_for(Def.new(:band, :hash => :true)) end end describe "DCI" do module SongRepresenter include Representable::XML property :name end module AlbumRepresenter include Representable::XML property :best_song, :class => Song, :extend => SongRepresenter collection :songs, :class => Song, :as => :song, :extend => SongRepresenter end it "allows adding the representer by using #extend" do module BandRepresenter include Representable::XML property :name end civ = Object.new civ.instance_eval do def name; "CIV"; end def name=(v) @name = v end end civ.extend(BandRepresenter) assert_xml_equal "CIV", civ.to_xml end it "extends contained models when serializing" do @album = Album.new( [Song.new("I Hate My Brain"), mr=Song.new("Mr. Charisma")], mr) @album.extend(AlbumRepresenter) @album.to_xml.must_equal_xml " Mr. Charisma I Hate My Brain Mr. Charisma " end it "extends contained models when deserializing" do @album = Album.new @album.extend(AlbumRepresenter) @album.from_xml("Mr. CharismaI Hate My BrainMr. Charisma") assert_equal "Mr. Charisma", @album.best_song.name end end end end class AttributesTest < MiniTest::Spec describe ":as => rel, :attribute => true" do class Link include Representable::XML property :href, :as => "href", :attribute => true property :title, :as => "title", :attribute => true attr_accessor :href, :title end it "#from_xml creates correct accessors" do link = Link.new.from_xml(%{ }) assert_equal "http://apotomo.de", link.href assert_equal "Home, sweet home", link.title end it "#to_xml serializes correctly" do link = Link.new link.href = "http://apotomo.de/" assert_xml_equal %{}, link.to_xml end end end class CDataBand class CData < Representable::XML::Binding def serialize_node(parent, value) parent << Nokogiri::XML::CDATA.new(parent, represented.name) end end include Representable::XML property :name, :binding => lambda { |*args| CData.new(*args) }#getter: lambda { |opt| Nokogiri::XML::CDATA.new(opt[:doc], name) } attr_accessor :name def initialize(name=nil) name and self.name = name end end class TypedPropertyTest < MiniTest::Spec class Band include Representable::XML property :name attr_accessor :name def initialize(name=nil) name and self.name = name end end module AlbumRepresenter include Representable::XML property :band, :class => Band end class Album attr_accessor :band def initialize(band=nil) @band = band end end # TODO:property :group, :class => Band # :class # where to mixin DCI? describe ":class => Item" do it "#from_xml creates one Item instance" do album = Album.new.extend(AlbumRepresenter).from_xml(%{ Bad Religion }) assert_equal "Bad Religion", album.band.name end describe "#to_xml" do it "doesn't escape xml from Band#to_xml" do band = Band.new("Bad Religion") album = Album.new(band).extend(AlbumRepresenter) assert_xml_equal %{ Bad Religion }, album.to_xml end it "doesn't escape and wrap string from Band#to_node" do band = Band.new("Bad Religion") band.instance_eval do def to_node(*) "Baaaad Religion" end end assert_xml_equal %{Baaaad Religion}, Album.new(band).extend(AlbumRepresenter).to_xml end end describe "#to_xml with CDATA" do it "wraps Band name in CDATA#to_xml" do band = CDataBand.new("Bad Religion") album = Album.new(band).extend(AlbumRepresenter) assert_xml_equal %{ }, album.to_xml end end end end # TODO: add parsing tests. class XMLPropertyTest < Minitest::Spec Band = Struct.new(:name, :genre) Manager = Struct.new(:managed) #--- #- :as with scalar class BandRepresenter < Representable::Decorator include Representable::XML property :name, as: :theyCallUs property :genre, attribute: true end it { BandRepresenter.new(Band.new("Mute")).to_xml.must_equal_xml %{Mute} } class ManagerRepresenter < Representable::Decorator include Representable::XML property :managed, as: :band, decorator: BandRepresenter end #- :as with nested property it { ManagerRepresenter.new(Manager.new(Band.new("Mute", "Punkrock"))).to_xml.must_equal_xml %{Mute} } end class XMLCollectionTest < MiniTest::Spec Band = Struct.new(:name) Compilation = Struct.new(:bands) class BandRepresenter < Representable::Decorator include Representable::XML property :name end #--- #- :as, :decorator, :class describe ":class => Band, :as => :band, :collection => true" do class CompilationRepresenter < Representable::Decorator include Representable::XML collection :bands, class: Band, as: :group, decorator: BandRepresenter end describe "#from_xml" do it "pushes collection items to array" do cd = CompilationRepresenter.new(Compilation.new).from_xml(%{ Diesel Boy Cobra Skulls }) assert_equal ["Cobra Skulls", "Diesel Boy"], cd.bands.map(&:name).sort end end it "responds to #to_xml" do cd = Compilation.new([Band.new("Diesel Boy"), Band.new("Bad Religion")]) CompilationRepresenter.new(cd).to_xml.must_equal_xml %{ Diesel Boy Bad Religion } end end describe ":as" do let(:xml_doc) { Module.new do include Representable::XML collection :songs, :as => :song end } it "collects untyped items" do album = Album.new.extend(xml_doc).from_xml(%{ Two Kevins Wright and Rong Laundry Basket }) assert_equal ["Laundry Basket", "Two Kevins", "Wright and Rong"].sort, album.songs.sort end end describe ":wrap" do let(:album) { Album.new.extend(xml_doc) } let(:xml_doc) { Module.new do include Representable::XML collection :songs, :as => :song, :wrap => :songs end } describe "#from_xml" do it "finds items in wrapped collection" do album.from_xml(%{ Two Kevins Wright and Rong Laundry Basket }) assert_equal ["Laundry Basket", "Two Kevins", "Wright and Rong"].sort, album.songs.sort end end describe "#to_xml" do it "wraps items" do album.songs = ["Laundry Basket", "Two Kevins", "Wright and Rong"] assert_xml_equal %{ Laundry Basket Two Kevins Wright and Rong }, album.to_xml end end end require 'representable/xml/hash' class LonelyRepresenterTest < MiniTest::Spec # TODO: where is the XML::Hash test? module SongRepresenter include Representable::XML property :name self.representation_wrap = :song end let(:decorator) { rpr = representer; Class.new(Representable::Decorator) { include Representable::XML; include rpr } } describe "XML::Collection" do describe "with contained objects" do representer!(:module => Representable::XML::Collection) do items :class => Song, :extend => SongRepresenter self.representation_wrap= :songs end let(:songs) { [Song.new("Days Go By"), Song.new("Can't Take Them All")] } let(:xml_doc) { "Days Go ByCan't Take Them All" } it "renders array" do songs.extend(representer).to_xml.must_equal_xml xml_doc end it "renders array with decorator" do decorator.new(songs).to_xml.must_equal_xml xml_doc end it "parses array" do _([].extend(representer).from_xml(xml_doc)).must_equal songs end it "parses array with decorator" do _(decorator.new([]).from_xml(xml_doc)).must_equal songs end end end describe "XML::AttributeHash" do # TODO: move to HashTest. representer!(:module => Representable::XML::AttributeHash) do self.representation_wrap= :songs end let(:songs) { {"one" => "Graveyards", "two" => "Can't Take Them All"} } let(:xml_doc) { "" } describe "#to_xml" do it "renders hash" do songs.extend(representer).to_xml.must_equal_xml xml_doc end it "respects :exclude" do assert_xml_equal "", songs.extend(representer).to_xml(:exclude => [:one]) end it "respects :include" do assert_xml_equal "", songs.extend(representer).to_xml(:include => [:two]) end it "renders hash with decorator" do decorator.new(songs).to_xml.must_equal_xml xml_doc end end describe "#from_json" do it "returns hash" do _({}.extend(representer).from_xml(xml_doc)).must_equal songs end it "respects :exclude" do assert_equal({"two" => "Can't Take Them All"}, {}.extend(representer).from_xml(xml_doc, :exclude => [:one])) end it "respects :include" do assert_equal({"one" => "Graveyards"}, {}.extend(representer).from_xml(xml_doc, :include => [:one])) end it "parses hash with decorator" do _(decorator.new({}).from_xml(xml_doc)).must_equal songs end end end end end class XmlHashTest < MiniTest::Spec # scalar, no object describe "plain text" do representer!(module: Representable::XML) do hash :songs end let(:doc) { "The GargoyleBronx" } # to_xml it { OpenStruct.new(songs: {"first" => "The Gargoyle", "second" => "Bronx"}).extend(representer).to_xml.must_equal_xml(doc) } # FIXME: this NEVER worked! # it { OpenStruct.new.extend(representer).from_xml(doc).songs.must_equal({"first" => "The Gargoyle", "second" => "Bronx"}) } end describe "with objects" do representer!(module: Representable::XML) do hash :songs, class: OpenStruct do property :title end end let(:doc) { " The Gargoyle Bronx " } # to_xml it { OpenStruct.new(songs: {"first" => OpenStruct.new(title: "The Gargoyle"), "second" => OpenStruct.new(title: "Bronx")}).extend(representer).to_xml.must_equal_xml(doc) } # FIXME: this NEVER worked! # it { OpenStruct.new.extend(representer).from_xml(doc).songs.must_equal({"first" => "The Gargoyle", "second" => "Bronx"}) } end end representable-3.2.0/test/cached_test.rb0000644000004100000410000001274214240717435020166 0ustar www-datawww-datarequire "test_helper" class Profiler def self.profile(&block) case RUBY_ENGINE when "ruby" require 'ruby-prof' output = StringIO.new profile_result = RubyProf.profile(&block) printer = RubyProf::FlatPrinter.new(profile_result) printer.print(output) output.string when "jruby" require 'jruby/profiler' output_stream = java.io.ByteArrayOutputStream.new print_stream = java.io.PrintStream.new(output_stream) profile_result = JRuby::Profiler.profile(&block) printer = JRuby::Profiler::FlatProfilePrinter.new(profile_result) printer.printProfile(print_stream) output_stream.toString end end end class CachedTest < MiniTest::Spec # TODO: also test with feature(Cached) module Model Song = Struct.new(:title, :composer) Album = Struct.new(:name, :songs, :artist) Artist = Struct.new(:name, :hidden_taste) end class SongRepresenter < Representable::Decorator include Representable::Hash feature Representable::Cached property :title, render_filter: lambda { |input, options| "#{input}:#{options[:options][:user_options]}" } property :composer, class: Model::Artist do property :name end end class AlbumRepresenter < Representable::Decorator include Representable::Hash include Representable::Cached property :name collection :songs, decorator: SongRepresenter, class: Model::Song end describe "serialization" do let(:album_hash) { {"name"=>"Louder And Even More Dangerous", "songs"=>[{"title"=>"Southbound:{:volume=>10}"}, {"title"=>"Jailbreak:{:volume=>10}"}]} } let(:song) { Model::Song.new("Jailbreak") } let(:song2) { Model::Song.new("Southbound") } let(:album) { Model::Album.new("Live And Dangerous", [song, song2, Model::Song.new("Emerald")]) } let(:representer) { AlbumRepresenter.new(album) } it do # album2 = Model::Album.new("Louder And Even More Dangerous", [song2, song]) # makes sure options are passed correctly. _(representer.to_hash(user_options: {volume: 9})).must_equal({"name"=>"Live And Dangerous", "songs"=>[{"title"=>"Jailbreak:{:volume=>9}"}, {"title"=>"Southbound:{:volume=>9}"}, {"title"=>"Emerald:{:volume=>9}"}]}) # called in Deserializer/Serializer # representer becomes reusable as it is stateless. # representer.update!(album2) # makes sure options are passed correctly. # representer.to_hash(volume:10).must_equal(album_hash) end # profiling it do representer.to_hash data = Profiler.profile { representer.to_hash } # 3 songs get decorated. _(data).must_match(/3\s*Representable::Function::Decorate#call/m) # These weird Regexp bellow are a quick workaround to accomodate # the different profiler result formats. # - "3 #prepare" -> At MRI Ruby # - "3 Representable::Decorator.prepare" -> At JRuby # 3 nested decorator is instantiated for 3 Songs, though. _(data).must_match(/3\s*(?[\#.]prepare/m) # no Binding is instantiated at runtime. _(data).wont_match "Representable::Binding#initialize" # 2 mappers for Album, Song # data.must_match "2 Representable::Mapper::Methods#initialize" # title, songs, 3x title, composer _(data).must_match(/8\s*Representable::Binding[#\.]render_pipeline/m) _(data).wont_match "render_functions" _(data).wont_match "Representable::Binding::Factories#render_functions" end end describe "deserialization" do let(:album_hash) { { "name"=>"Louder And Even More Dangerous", "songs"=>[ {"title"=>"Southbound", "composer"=>{"name"=>"Lynott"}}, {"title"=>"Jailbreak", "composer"=>{"name"=>"Phil Lynott"}}, {"title"=>"Emerald"} ] } } it do album = Model::Album.new AlbumRepresenter.new(album).from_hash(album_hash) _(album.songs.size).must_equal 3 _(album.name).must_equal "Louder And Even More Dangerous" _(album.songs[0].title).must_equal "Southbound" _(album.songs[0].composer.name).must_equal "Lynott" _(album.songs[1].title).must_equal "Jailbreak" _(album.songs[1].composer.name).must_equal "Phil Lynott" _(album.songs[2].title).must_equal "Emerald" _(album.songs[2].composer).must_be_nil # TODO: test options. end it "xxx" do representer = AlbumRepresenter.new(Model::Album.new) representer.from_hash(album_hash) data = Profiler.profile { representer.from_hash(album_hash) } # only 2 nested decorators are instantiated, Song, and Artist. # Didn't like the regexp? # MRI and JRuby has different output formats. See note above. _(data).must_match(/5\s*(?[#\.]prepare/) # a total of 5 properties in the object graph. _(data).wont_match "Representable::Binding#initialize" _(data).wont_match "parse_functions" # no pipeline creation. _(data).must_match(/10\s*Representable::Binding[#\.]parse_pipeline/) # three mappers for Album, Song, composer # data.must_match "3 Representable::Mapper::Methods#initialize" # # 6 deserializers as the songs collection uses 2. # data.must_match "6 Representable::Deserializer#initialize" # # one populater for every property. # data.must_match "5 Representable::Populator#initialize" # printer.print(STDOUT) end end endrepresentable-3.2.0/test/inherit_test.rb0000644000004100000410000001235314240717435020417 0ustar www-datawww-datarequire 'test_helper' class InheritTest < MiniTest::Spec module SongRepresenter # it's important to have a global module so we can test if stuff gets overridden in the original module. include Representable::Hash property :name, :as => :title do property :string, :as => :str end property :track, :as => :no end let(:song) { Song.new(Struct.new(:string).new("Roxanne"), 1) } describe ":inherit plain property" do representer! do include SongRepresenter property :track, :inherit => true, :getter => lambda { |*| "n/a" } end it { _(SongRepresenter.prepare(song).to_hash).must_equal({"title"=>{"str"=>"Roxanne"}, "no"=>1}) } it { _(representer.prepare(song).to_hash).must_equal({"title"=>{"str"=>"Roxanne"}, "no"=>"n/a"}) } # as: inherited. end describe ":inherit with empty inline representer" do representer! do include SongRepresenter property :name, :inherit => true do # inherit as: title # that doesn't make sense. end end it { _(SongRepresenter.prepare(Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash).must_equal({"title"=>{"str"=>"Believe It"}, "no"=>1}) } # the block doesn't override the inline representer. it { _(representer.prepare( Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash).must_equal({"title"=>{"str"=>"Believe It"}, "no"=>1}) } end describe ":inherit with overriding inline representer" do representer! do include SongRepresenter # passing block property :name, :inherit => true do # inherit as: title property :string, :as => :s property :length end end it { _(representer.prepare( Song.new(Struct.new(:string, :length).new("Believe It", 10), 1)).to_hash).must_equal({"title"=>{"s"=>"Believe It","length"=>10}, "no"=>1}) } end describe ":inherit with empty inline and options" do representer! do include SongRepresenter property :name, inherit: true, as: :name do # inherit module, only. # that doesn't make sense. but it should simply inherit the old nested properties. end end it { _(SongRepresenter.prepare(Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash).must_equal({"title"=>{"str"=>"Believe It"}, "no"=>1}) } it { _(representer.prepare( Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash).must_equal({"name"=>{"str"=>"Believe It"}, "no"=>1}) } end describe ":inherit with inline without block but options" do representer! do include SongRepresenter property :name, :inherit => true, :as => :name # FIXME: add :getter or something else dynamic since this is double-wrapped. end it { _(SongRepresenter.prepare(Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash).must_equal({"title"=>{"str"=>"Believe It"}, "no"=>1}) } it { _(representer.prepare( Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash).must_equal({"name"=>{"str"=>"Believe It"}, "no"=>1}) } end # no :inherit describe "overwriting without :inherit" do representer! do include SongRepresenter property :track, :representable => true end it "replaces inherited property" do _(representer.representable_attrs.size).must_equal 2 definition = representer.representable_attrs.get(:track) # TODO: find a better way to assert Definition identity. # definition.keys.size.must_equal 2 _(definition[:representable]). must_equal true _(definition.name).must_equal "track" # was "no". end end # decorator describe ":inherit with decorator" do representer!(:decorator => true) do property :hit do property :title, exec_context: :decorator def title "Cheap Transistor Radio" end end end let(:inheriting) { class InheritingDecorator < representer include Representable::Debug property :hit, :inherit => true do include Representable::Debug property :length end self end } it { _(representer.new(OpenStruct.new(hit: OpenStruct.new(title: "I WILL BE OVERRIDDEN", :length => "2:59"))).to_hash).must_equal( {"hit"=>{"title"=>"Cheap Transistor Radio"}}) } # inheriting decorator inherits inline representer class (InlineRepresenter#title). # inheriting decorator adds :length. it { _(inheriting.new(OpenStruct.new(:hit => OpenStruct.new(:title => "Hole In Your Soul", :length => "2:59"))).to_hash).must_equal( {"hit"=>{"title"=>"Cheap Transistor Radio", "length"=>"2:59"}}) } end # :inherit when property doesn't exist, yet. describe ":inherit without inheritable property" do representer! do property :name, :inherit => true end it { _(representer.prepare(Song.new("The Beginning")).to_hash).must_equal({"name"=>"The Beginning"})} end end # class InheritancingTest < MiniTest::Spec # class SongDecorator < Representable::Decorator # include Representable::Hash # property :album do # # does have Hash. # property :title # end # end # class JsonSongDecorator < SongDecorator # include Representable::XML # end # it do # puts JsonSongDecorator.new(OpenStruct.new(:album => OpenStruct.new(:title => "Erotic Cakes", :tracks => nil))).to_xml # end # end representable-3.2.0/test/generic_test.rb0000644000004100000410000000726714240717435020401 0ustar www-datawww-datarequire 'test_helper' class GenericTest < MiniTest::Spec # TODO: rename/restructure to CollectionTest. let(:new_album) { OpenStruct.new.extend(representer) } let(:album) { OpenStruct.new(:songs => ["Fuck Armageddon"]).extend(representer) } let(:song) { OpenStruct.new(:title => "Resist Stance") } let(:song_representer) { Module.new do include Representable::Hash; property :title end } describe "::collection" do representer! do collection :songs end it "doesn't initialize property" do new_album.from_hash({}) _(new_album.songs).must_be_nil end it "leaves properties untouched" do album.from_hash({}) # TODO: test property. _(album.songs).must_equal ["Fuck Armageddon"] # when the collection is not present in the incoming hash, this propery stays untouched. end # when collection is nil, it doesn't get rendered: for_formats( :hash => [Representable::Hash, {}], :xml => [Representable::XML, ""], :yaml => [Representable::YAML, "--- {}\n"], # FIXME: this doesn't look right. ) do |format, mod, output, input| describe "nil collections" do let(:format) { format } representer!(:module => mod) do collection :songs self.representation_wrap = :album if format == :xml end let(:album) { Album.new.extend(representer) } it "doesn't render collection in #{format}" do render(album).must_equal_document output end end end # when collection is set but empty, render the empty collection. for_formats( :hash => [Representable::Hash, {"songs" => []}], #:xml => [Representable::XML, ""], :yaml => [Representable::YAML, "---\nsongs: []\n"], ) do |format, mod, output, input| describe "empty collections" do let(:format) { format } representer!(:module => mod) do collection :songs self.representation_wrap = :album if format == :xml end let(:album) { OpenStruct.new(:songs => []).extend(representer) } it "renders empty collection in #{format}" do render(album).must_equal_document output end end end # when collection is [], suppress rendering when render_empty: false. for_formats( :hash => [Representable::Hash, {}], #:xml => [Representable::XML, ""], :yaml => [Representable::YAML, "--- {}\n"], ) do |format, mod, output, input| describe "render_empty [#{format}]" do let(:format) { format } representer!(:module => mod) do collection :songs, :render_empty => false self.representation_wrap = :album if format == :xml end let(:album) { OpenStruct.new(:songs => []).extend(representer) } it { render(album).must_equal_document output } end end end # wrap_test for_formats( :hash => [Representable::Hash, {}], # :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything/open_struct>"], # :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"], ) do |format, mod, input| describe "parsing [#{format}] with wrap where wrap is missing" do representer!(:module => mod) do self.representation_wrap = :song property :title end it "doesn't change represented object" do _(song.extend(representer).send("from_#{format}", input).title).must_equal "Resist Stance" end end end endrepresentable-3.2.0/test/yaml_test.rb0000644000004100000410000001033014240717435017710 0ustar www-datawww-datarequire 'test_helper' class YamlPublicMethodsTest < Minitest::Spec #--- # from_yaml class BandRepresenter < Representable::Decorator include Representable::YAML property :id property :name end let(:data) { %{--- id: 1 name: Rancid } } it { _(BandRepresenter.new(Band.new).from_yaml(data)[:id, :name]).must_equal [1, "Rancid"] } it { _(BandRepresenter.new(Band.new).parse(data)[:id, :name]).must_equal [1, "Rancid"] } #--- # to_yaml let(:band) { Band.new("1", "Rancid") } it { _(BandRepresenter.new(band).to_yaml).must_equal data } it { _(BandRepresenter.new(band).render).must_equal data } end class YamlTest < MiniTest::Spec def self.yaml_representer(&block) Module.new do include Representable::YAML instance_exec(&block) end end def yaml_representer(&block) self.class.yaml_representer(&block) end describe "property" do let(:yaml) { yaml_representer do property :best_song end } let(:album) { Album.new.tap do |album| album.best_song = "Liar" end } describe "#to_yaml" do it "renders plain property" do _(album.extend(yaml).to_yaml).must_equal( "--- best_song: Liar ") end it "always renders values into strings" do _(Album.new.tap { |a| a.best_song = 8675309 }.extend(yaml).to_yaml).must_equal( "--- best_song: 8675309 " ) end end describe "#from_yaml" do it "parses plain property" do _(album.extend(yaml).from_yaml( "--- best_song: This Song Is Recycled ").best_song).must_equal "This Song Is Recycled" end end describe "with :class and :extend" do yaml_song = yaml_representer do property :name end let(:yaml_album) { Module.new do include Representable::YAML property :best_song, :extend => yaml_song, :class => Song end } let(:album) { Album.new.tap do |album| album.best_song = Song.new("Liar") end } describe "#to_yaml" do it "renders embedded typed property" do _(album.extend(yaml_album).to_yaml).must_equal "--- best_song: name: Liar " end end describe "#from_yaml" do it "parses embedded typed property" do _(album.extend(yaml_album).from_yaml("--- best_song: name: Go With Me ")).must_equal Album.new(nil,Song.new("Go With Me")) end end end end describe "collection" do let(:yaml) { yaml_representer do collection :songs end } let(:album) { Album.new.tap do |album| album.songs = ["Jackhammer", "Terrible Man"] end } describe "#to_yaml" do it "renders a block style list per default" do _(album.extend(yaml).to_yaml).must_equal "--- songs: - Jackhammer - Terrible Man " end it "renders a flow style list when :style => :flow set" do yaml = yaml_representer { collection :songs, :style => :flow } _(album.extend(yaml).to_yaml).must_equal "--- songs: [Jackhammer, Terrible Man] " end end describe "#from_yaml" do it "parses a block style list" do _(album.extend(yaml).from_yaml("--- songs: - Off Key Melody - Sinking")).must_equal Album.new(["Off Key Melody", "Sinking"]) end it "parses a flow style list" do _(album.extend(yaml).from_yaml("--- songs: [Off Key Melody, Sinking]")).must_equal Album.new(["Off Key Melody", "Sinking"]) end end describe "with :class and :extend" do let(:yaml_album) { Module.new do include Representable::YAML collection :songs, :class => Song do property :name property :track end end } let(:album) { Album.new([Song.new("Liar", 1), Song.new("What I Know", 2)]) } describe "#to_yaml" do it "renders collection of typed property" do _(album.extend(yaml_album).to_yaml).must_equal "--- songs: - name: Liar track: 1 - name: What I Know track: 2 " end end describe "#from_yaml" do it "parses collection of typed property" do _(album.extend(yaml_album).from_yaml("--- songs: - name: One Shot Deal track: 4 - name: Three Way Dance track: 5")).must_equal Album.new([Song.new("One Shot Deal", 4), Song.new("Three Way Dance", 5)]) end end end end end representable-3.2.0/test/represent_test.rb0000644000004100000410000000537714240717435020774 0ustar www-datawww-datarequire 'test_helper' class RepresentTest < MiniTest::Spec let(:songs) { [song, Song.new("Can't Take Them All")] } let(:song) { Song.new("Days Go By") } for_formats( :hash => [Representable::Hash, out=[{"name" => "Days Go By"}, {"name"=>"Can't Take Them All"}], out], # :json => [Representable::JSON, out="[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]", out], # :xml => [Representable::XML, out="", out] ) do |format, mod, output, input| # Representer.represents detects collection. describe "Module#to_/from_#{format}" do let(:format) { format } let(:representer) { Module.new do include mod property :name collection_representer :class => Song # TODOOOOOOOOOOOO: test without Song and fix THIS FUCKINGNoMethodError: undefined method `name=' for {"name"=>"Days Go By"}:Hash ERROR!!!!!!!!!!!!!!! end } it { render(representer.represent(songs)).must_equal_document output } it { _(parse(representer.represent([]), input)).must_equal songs } end # Decorator.represents detects collection. describe "Decorator#to_/from_#{format}" do let(:format) { format } let(:representer) { Class.new(Representable::Decorator) do include mod property :name collection_representer :class => Song end } it { render(representer.represent(songs)).must_equal_document output } it("ficken") { _(parse(representer.represent([]), input)).must_equal songs } end end for_formats( :hash => [Representable::Hash, out={"name" => "Days Go By"}, out], :json => [Representable::JSON, out="{\"name\":\"Days Go By\"}", out], # :xml => [Representable::XML, out="", out] ) do |format, mod, output, input| # Representer.represents detects singular. describe "Module#to_/from_#{format}" do let(:format) { format } let(:representer) { Module.new do include mod property :name collection_representer :class => Song end } it { render(representer.represent(song)).must_equal_document output } it { _(parse(representer.represent(Song.new), input)).must_equal song } end # Decorator.represents detects singular. describe "Decorator#to_/from_#{format}" do let(:format) { format } let(:representer) { Class.new(Representable::Decorator) do include mod property :name collection_representer :class => Song end } it { render(representer.represent(song)).must_equal_document output } it { _(parse(representer.represent(Song.new), input)).must_equal song } end end endrepresentable-3.2.0/test/filter_test.rb0000644000004100000410000000376314240717435020247 0ustar www-datawww-datarequire 'test_helper' class FilterPipelineTest < MiniTest::Spec let(:block1) { lambda { |input, options| "1: #{input}" } } let(:block2) { lambda { |input, options| "2: #{input}" } } subject { Representable::Pipeline[block1, block2] } it { _(subject.call("Horowitz", {})).must_equal "2: 1: Horowitz" } end class FilterTest < MiniTest::Spec representer! do property :title property :track, :parse_filter => lambda { |input, options| "#{input.downcase},#{options[:doc]}" }, :render_filter => lambda { |val, options| "#{val.upcase},#{options[:doc]},#{options[:options][:user_options]}" } end # gets doc and options. it { song = OpenStruct.new.extend(representer).from_hash("title" => "VULCAN EARS", "track" => "Nine") _(song.title).must_equal "VULCAN EARS" _(song.track).must_equal "nine,{\"title\"=>\"VULCAN EARS\", \"track\"=>\"Nine\"}" } it { _(OpenStruct.new("title" => "vulcan ears", "track" => "Nine").extend(representer).to_hash).must_equal( {"title"=>"vulcan ears", "track"=>"NINE,{\"title\"=>\"vulcan ears\"},{}"}) } describe "#parse_filter" do representer! do property :track, :parse_filter => [ lambda { |input, options| "#{input}-1" }, lambda { |input, options| "#{input}-2" }], :render_filter => [ lambda { |val, options| "#{val}-1" }, lambda { |val, options| "#{val}-2" }] end # order matters. it { _(OpenStruct.new.extend(representer).from_hash("track" => "Nine").track).must_equal "Nine-1-2" } it { _(OpenStruct.new("track" => "Nine").extend(representer).to_hash).must_equal({"track"=>"Nine-1-2"}) } end end # class RenderFilterTest < MiniTest::Spec # representer! do # property :track, :render_filter => [lambda { |val, options| "#{val}-1" } ] # property :track, :render_filter => [lambda { |val, options| "#{val}-2" } ], :inherit => true # end # it { OpenStruct.new("track" => "Nine").extend(representer).to_hash.must_equal({"track"=>"Nine-1-2"}) } # endrepresentable-3.2.0/test/test_helper_test.rb0000644000004100000410000000155014240717435021270 0ustar www-datawww-datarequire 'test_helper' class TestHelperTest < MiniTest::Spec describe "#assert_json" do it "tests for equality" do assert_json "{\"songs\":{\"one\":\"65\",\"two\":\"Emo Boy\"}}", "{\"songs\":{\"one\":\"65\",\"two\":\"Emo Boy\"}}" end it "allows different key orders" do assert_json "{\"songs\":{\"one\":\"65\",\"two\":\"Emo Boy\"}}", "{\"songs\":{\"two\":\"Emo Boy\",\"one\":\"65\"}}" end it "complains when expected hash is subset" do assert_raises MiniTest::Assertion do assert_json "{\"songs\":{\"one\":\"65\"}}", "{\"songs\":{\"two\":\"Emo Boy\",\"one\":\"65\"}}" end end it "complains when source hash is subset" do assert_raises MiniTest::Assertion do assert_json "{\"songs\":{\"two\":\"Emo Boy\",\"one\":\"65\"}}", "{\"songs\":{\"one\":\"65\"}}" end end end end representable-3.2.0/test/coercion_test.rb0000644000004100000410000000427514240717435020562 0ustar www-datawww-data# frozen_string_literal: true require 'test_helper' require 'representable/coercion' class CoercionTest < MiniTest::Spec representer! do include Representable::Coercion property :title # no coercion. property :length, type: Representable::Coercion::Types::Params::Float property :band, class: OpenStruct do property :founded, type: Representable::Coercion::Types::Params::Integer end collection :songs, class: OpenStruct do property :ok, type: Representable::Coercion::Types::Params::Bool end end let(:album) do OpenStruct.new(title: 'Dire Straits', length: 41.34, band: OpenStruct.new(founded: '1977'), songs: [OpenStruct.new(ok: 1), OpenStruct.new(ok: 0)]) end it do _(album.extend(representer).to_hash).must_equal({ 'title' => 'Dire Straits', 'length' => 41.34, 'band' => { 'founded' => 1977 }, 'songs' => [{ 'ok' => true }, { 'ok' => false }] }) end it { album = OpenStruct.new album.extend(representer) album.from_hash({ 'title' => 'Dire Straits', 'length' => '41.34', 'band' => { 'founded' => '1977' }, 'songs' => [{ 'ok' => 1 }, { 'ok' => 0 }] }) # it _(album.length).must_equal 41.34 _(album.band.founded).must_equal 1977 _(album.songs[0].ok).must_equal true } describe 'with user :parse_filter and :render_filter' do representer! do include Representable::Coercion property :length, type: Representable::Coercion::Types::Params::Float, parse_filter: ->(input, _options) { "#{input}.1" }, # happens BEFORE coercer. render_filter: ->(fragment, *) { "#{fragment}.1" } end # user's :parse_filter(s) are run before coercion. it { _(OpenStruct.new.extend(representer).from_hash('length' => '1').length).must_equal 1.1 } # user's :render_filter(s) are run before coercion. it { _(OpenStruct.new(length: 1).extend(representer).to_hash).must_equal({ 'length' => 1.1 }) } end end representable-3.2.0/test/pipeline_test.rb0000644000004100000410000002143514240717435020563 0ustar www-datawww-datarequire "test_helper" class PipelineTest < MiniTest::Spec Song = Struct.new(:title, :artist) Artist = Struct.new(:name) Album = Struct.new(:ratings, :artists) R = Representable P = R::Pipeline Getter = ->(input, options) { "Yo" } StopOnNil = ->(input, options) { input } SkipRender = ->(input, *) { input == "Yo" ? input : P::Stop } Prepare = ->(input, options) { "Prepare(#{input})" } Deserialize = ->(input, options) { "Deserialize(#{input}, #{options[:fragment]})" } SkipParse = ->(input, options) { input } CreateObject = ->(input, options) { OpenStruct.new } Setter = ->(input, options) { "Setter(#{input})" } AssignFragment = ->(input, options) { options[:fragment] = input } it "linear" do _(P[SkipParse, Setter].("doc", {fragment: 1})).must_equal "Setter(doc)" # parse style. _(P[AssignFragment, SkipParse, CreateObject, Prepare].("Bla", {})).must_equal "Prepare(#)" # render style. _(P[Getter, StopOnNil, SkipRender, Prepare, Setter].(nil, {})). must_equal "Setter(Prepare(Yo))" # pipeline = Representable::Pipeline[SkipParse , SetResult, ModifyResult] # pipeline.(fragment: "yo!").must_equal "modified object from yo!" end Stopping = ->(input, options) { return P::Stop if options[:fragment] == "stop!"; input } it "stopping" do pipeline = Representable::Pipeline[SkipParse, Stopping, Prepare] _(pipeline.(nil, fragment: "oy!")).must_equal "Prepare()" _(pipeline.(nil, fragment: "stop!")).must_equal Representable::Pipeline::Stop end describe "Collect" do Reverse = ->(input, options) { input.reverse } Add = ->(input, options) { "#{input}+" } let(:pipeline) { R::Collect[Reverse, Add] } it { _(pipeline.(["yo!", "oy!"], {})).must_equal ["!oy+", "!yo+"] } describe "Pipeline with Collect" do let(:pipeline) { P[Reverse, R::Collect[Reverse, Add]] } it { _(pipeline.(["yo!", "oy!"], {})).must_equal ["!yo+", "!oy+"] } end end ######### scalar property let(:title) { dfn = R::Definition.new(:title) R::Hash::Binding.new(dfn) } it "rendering scalar property" do doc = {} _(P[ R::GetValue, R::StopOnSkipable, R::AssignName, R::WriteFragment ].(nil, {represented: Song.new("Lime Green"), binding: title, doc: doc})).must_equal "Lime Green" _(doc).must_equal({"title"=>"Lime Green"}) end it "parsing scalar property" do _(P[ R::AssignName, R::ReadFragment, R::StopOnNotFound, R::OverwriteOnNil, # R::SkipParse, R::SetValue, ].extend(P::Debug).(doc={"title"=>"Eruption"}, {represented: song=Song.new("Lime Green"), binding: title, doc: doc})).must_equal "Eruption" _(song.title).must_equal "Eruption" end module ArtistRepresenter include Representable::Hash property :name end let(:artist) { dfn = R::Definition.new(:artist, extend: ArtistRepresenter, class: Artist) R::Hash::Binding.new(dfn) } let(:song_model) { Song.new("Lime Green", Artist.new("Diesel Boy")) } it "rendering typed property" do doc = {} _(P[ R::GetValue, R::StopOnSkipable, R::StopOnNil, R::Decorate, R::Serialize, R::AssignName, R::WriteFragment ].extend(P::Debug).(nil, {represented: song_model, binding: artist, doc: doc, options: {}})).must_equal({"name" => "Diesel Boy"}) _(doc).must_equal({"artist"=>{"name"=>"Diesel Boy"}}) end it "parsing typed property" do _(P[ R::AssignName, R::ReadFragment, R::StopOnNotFound, R::OverwriteOnNil, R::AssignFragment, R::CreateObject::Class, R::Decorate, R::Deserialize, R::SetValue, ].extend(P::Debug).(doc={"artist"=>{"name"=>"Doobie Brothers"}}, {represented: song_model, binding: artist, doc: doc, options: {}})).must_equal model=Artist.new("Doobie Brothers") _(song_model.artist).must_equal model end ######### collection :ratings let(:ratings) { dfn = R::Definition.new(:ratings, collection: true, skip_render: ->(*) { false }) R::Hash::Binding::Collection.new(dfn) } it "render scalar collection" do doc = {} _(P[ R::GetValue, R::StopOnSkipable, R::Collect[ R::SkipRender, ], R::AssignName, R::WriteFragment ].extend(P::Debug).(nil, {represented: Album.new([1,2,3]), binding: ratings, doc: doc, options: {}})).must_equal([1,2,3]) _(doc).must_equal({"ratings"=>[1,2,3]}) end ######### collection :songs, extend: SongRepresenter let(:artists) { dfn = R::Definition.new(:artists, collection: true, extend: ArtistRepresenter, class: Artist) R::Hash::Binding::Collection.new(dfn) } it "render typed collection" do doc = {} _(P[ R::GetValue, R::StopOnSkipable, R::Collect[ R::Decorate, R::Serialize, ], R::AssignName, R::WriteFragment ].extend(P::Debug).(nil, {represented: Album.new(nil, [Artist.new("Diesel Boy"), Artist.new("Van Halen")]), binding: artists, doc: doc, options: {}})).must_equal([{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]) _(doc).must_equal({"artists"=>[{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]}) end let(:album_model) { Album.new(nil, [Artist.new("Diesel Boy"), Artist.new("Van Halen")]) } it "parse typed collection" do doc = {"artists"=>[{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]} _(P[ R::AssignName, R::ReadFragment, R::StopOnNotFound, R::OverwriteOnNil, # R::SkipParse, R::Collect[ R::AssignFragment, R::CreateObject::Class, R::Decorate, R::Deserialize, ], R::SetValue, ].extend(P::Debug).(doc, {represented: album_model, binding: artists, doc: doc, options: {}})).must_equal([Artist.new("Diesel Boy"), Artist.new("Van Halen")]) _(album_model.artists).must_equal([Artist.new("Diesel Boy"), Artist.new("Van Halen")]) end # TODO: test with arrays, too, not "only" Pipeline instances. describe "#Insert Pipeline[], Function, replace: OldFunction" do let(:pipeline) { P[R::GetValue, R::StopOnSkipable, R::StopOnNil] } it "returns Pipeline instance when passing in Pipeline instance" do _(P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable)).must_be_instance_of(R::Pipeline) end it "replaces if exists" do # pipeline.insert!(R::Default, replace: R::StopOnSkipable) _(P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable)).must_equal P[R::GetValue, R::Default, R::StopOnNil] _(pipeline).must_equal P[R::GetValue, R::StopOnSkipable, R::StopOnNil] end it "replaces Function instance" do pipeline = P[R::Prepare, R::StopOnSkipable, R::StopOnNil] _(P::Insert.(pipeline, R::Default, replace: R::Prepare)).must_equal P[R::Default, R::StopOnSkipable, R::StopOnNil] _(pipeline).must_equal P[R::Prepare, R::StopOnSkipable, R::StopOnNil] end it "does not replace when not existing" do P::Insert.(pipeline, R::Default, replace: R::Prepare) _(pipeline).must_equal P[R::GetValue, R::StopOnSkipable, R::StopOnNil] end it "applies on nested Collect" do pipeline = P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil] _(P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).extend(P::Debug).inspect).must_equal "Pipeline[GetValue, Collect[GetValue, Default], StopOnNil]" _(pipeline).must_equal P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil] _(P::Insert.(pipeline, R::Default, replace: R::StopOnNil).extend(P::Debug).inspect).must_equal "Pipeline[GetValue, Collect[GetValue, StopOnSkipable], Default]" end it "applies on nested Collect with Function::CreateObject" do pipeline = P[R::GetValue, R::Collect[R::GetValue, R::CreateObject], R::StopOnNil] _(P::Insert.(pipeline, R::Default, replace: R::CreateObject).extend(P::Debug).inspect).must_equal "Pipeline[GetValue, Collect[GetValue, Default], StopOnNil]" _(pipeline).must_equal P[R::GetValue, R::Collect[R::GetValue, R::CreateObject], R::StopOnNil] end end describe "Insert.(delete: true)" do let(:pipeline) { P[R::GetValue, R::StopOnNil] } it do _(P::Insert.(pipeline, R::GetValue, delete: true).extend(P::Debug).inspect).must_equal "Pipeline[StopOnNil]" _(pipeline.extend(P::Debug).inspect).must_equal "Pipeline[GetValue, StopOnNil]" end end describe "Insert.(delete: true) with Collect" do let(:pipeline) { P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil] } it do _(P::Insert.(pipeline, R::GetValue, delete: true).extend(P::Debug).inspect).must_equal "Pipeline[Collect[StopOnSkipable], StopOnNil]" _(pipeline.extend(P::Debug).inspect).must_equal "Pipeline[GetValue, Collect[GetValue, StopOnSkipable], StopOnNil]" end end endrepresentable-3.2.0/test/test_helper.rb0000644000004100000410000000671714240717435020243 0ustar www-datawww-datarequire 'pry-byebug' require 'representable' require 'minitest/autorun' require 'test_xml/mini_test' require "representable/debug" require 'minitest/assertions' module MiniTest::Assertions def assert_equal_xml(text, subject) assert_equal (text.gsub("\n", "").gsub(/(\s\s+)/, "")), subject.gsub("\n", "").gsub(/(\s\s+)/, "") end end String.infect_an_assertion :assert_equal_xml, :must_xml # TODO: delete all that in 4.0: class Album attr_accessor :songs, :best_song def initialize(songs=nil, best_song=nil) @songs = songs @best_song = best_song end def ==(other) songs == other.songs and best_song == other.best_song end end class Song attr_accessor :name, :track # never change this, track rendered with Rails#to_json. def initialize(name=nil, track=nil) @name = name @track = track end def ==(other) name == other.name and track == other.track end end module XmlHelper def xml(document) Nokogiri::XML(document).root end end module AssertJson module Assertions def assert_json(expected, actual, msg=nil) msg = message(msg, "") { diff expected, actual } assert_equal(expected.split("").sort, actual.split("").sort, msg) end end end MiniTest::Spec.class_eval do include AssertJson::Assertions include XmlHelper def self.for_formats(formats) formats.each do |format, cfg| mod, output, input = cfg yield format, mod, output, input end end def render(object, *args) AssertableDocument.new(object.send("to_#{format}", *args), format) end def parse(object, input, *args) object.send("from_#{format}", input, *args) end class AssertableDocument attr_reader :document def initialize(document, format) @document, @format = document, format end def must_equal_document(*args) return document.must_equal_xml(*args) if @format == :xml document.must_equal(*args) end end def self.representer!(options={}, &block) fmt = options # we need that so the 2nd call to ::let(within a ::describe) remembers the right format. name = options[:name] || :representer format = options[:module] || Representable::Hash let(name) do mod = options[:decorator] ? Class.new(Representable::Decorator) : Module.new inject_representer(mod, fmt) mod.module_eval do include format instance_exec(&block) end mod end undef :inject_representer if method_defined? :inject_representer def inject_representer(mod, options) return unless options[:inject] injected_name = options[:inject] injected = send(injected_name) # song_representer mod.singleton_class.instance_eval do define_method(injected_name) { injected } end end end module TestMethods def representer_for(modules=[Representable::Hash], &block) Module.new do extend TestMethods include(*modules) module_exec(&block) end end alias_method :representer!, :representer_for end include TestMethods end class BaseTest < MiniTest::Spec let(:new_album) { OpenStruct.new.extend(representer) } let(:album) { OpenStruct.new(:songs => ["Fuck Armageddon"]).extend(representer) } let(:song) { OpenStruct.new(:title => "Resist Stance") } let(:song_representer) { Module.new do include Representable::Hash; property :title end } end Band = Struct.new(:id, :name) do def [](*attrs) attrs.collect { |attr| send(attr) } end end representable-3.2.0/test/heritage_test.rb0000644000004100000410000000227014240717435020542 0ustar www-datawww-datarequire "test_helper" class HeritageTest < Minitest::Spec module Hello def hello "Hello!" end end module Ciao def ciao "Ciao!" end end class A < Representable::Decorator include Representable::Hash feature Hello property :id do end end class B < A feature Ciao # does NOT extend id, of course. property :id, inherit: true do end end class C < A property :id do end # overwrite old :id. end it "B must inherit Hello! feature from A" do _(B.representable_attrs.get(:id)[:extend].(nil).new(nil).hello).must_equal "Hello!" end it "B must have Ciao from module (feauture) Ciao" do _(B.representable_attrs.get(:id)[:extend].(nil).new(nil).ciao).must_equal "Ciao!" end it "C must inherit Hello! feature from A" do _(C.representable_attrs.get(:id)[:extend].(nil).new(nil).hello).must_equal "Hello!" end module M include Representable feature Hello end module N include Representable include M feature Ciao end let(:obj_extending_N) { Object.new.extend(N) } it "obj should inherit from N, and N from M" do _(obj_extending_N.hello).must_equal "Hello!" end end representable-3.2.0/test/defaults_options_test.rb0000644000004100000410000000515214240717435022336 0ustar www-datawww-datarequire 'test_helper' class DefaultsOptionsTest < BaseTest let(:format) { :hash } let(:song) { Struct.new(:title, :author_name, :song_volume, :description).new("Revolution", "Some author", 20, nil) } let(:prepared) { representer.prepare song } describe "hash options combined with dynamic options" do representer! do defaults render_nil: true do |name| { as: name.to_s.upcase } end property :title property :author_name property :description property :song_volume end it { render(prepared).must_equal_document({"TITLE" => "Revolution", "AUTHOR_NAME" => "Some author", "DESCRIPTION" => nil, "SONG_VOLUME" => 20}) } end describe "with only dynamic property options" do representer! do defaults do |name| { as: name.to_s.upcase } end property :title property :author_name property :description property :song_volume end it { render(prepared).must_equal_document({"TITLE" => "Revolution", "AUTHOR_NAME" => "Some author", "SONG_VOLUME" => 20}) } end describe "with only hashes" do representer! do defaults render_nil: true property :title property :author_name property :description property :song_volume end it { render(prepared).must_equal_document({"title" => "Revolution", "author_name" => "Some author", "description" => nil, "song_volume" => 20}) } end describe "direct defaults hash" do representer! do defaults render_nil: true property :title property :author_name property :description property :song_volume end it { render(prepared).must_equal_document({"title" => "Revolution", "author_name" => "Some author", "description" => nil, "song_volume" => 20}) } end describe "direct defaults hash with dynamic options" do representer! do defaults render_nil: true do |name| { as: name.to_s.upcase } end property :title property :author_name property :description property :song_volume end it { render(prepared).must_equal_document({"TITLE" => "Revolution", "AUTHOR_NAME" => "Some author", "DESCRIPTION" => nil, "SONG_VOLUME" => 20}) } end describe "prioritizes specific options" do representer! do defaults render_nil: true do |name| { as: name.to_s.upcase } end property :title property :author_name property :description, render_nil: false property :song_volume, as: :volume end it { render(prepared).must_equal_document({"TITLE" => "Revolution", "AUTHOR_NAME" => "Some author", "volume" => 20}) } end end representable-3.2.0/test/stringify_hash_test.rb0000644000004100000410000000226314240717435021775 0ustar www-datawww-datarequire 'test_helper' class StringifyHashTest < MiniTest::Spec describe "#from_hash" do representer!(:name => :song_representer) do include Representable::Hash include Representable::Hash::AllowSymbols property :title end representer!(:inject => :song_representer) do include Representable::Hash::AllowSymbols property :song, :extend => song_representer, :class => OpenStruct end it "parses symbols, too" do _(OpenStruct.new.extend(representer).from_hash({:song => {:title => "Der Optimist"}}).song.title).must_equal "Der Optimist" end it "still parses strings" do _(OpenStruct.new.extend(representer).from_hash({"song" => {"title" => "Der Optimist"}}).song.title).must_equal "Der Optimist" end describe "with :wrap" do representer!(:inject => :song_representer) do include Representable::Hash::AllowSymbols self.representation_wrap = :album property :song, :extend => song_representer, :class => OpenStruct end it "parses symbols, too" do _(OpenStruct.new.extend(representer).from_hash({:album => {:song => {:title => "Der Optimist"}}}).song.title).must_equal "Der Optimist" end end end endrepresentable-3.2.0/test/realistic_benchmark.rb0000644000004100000410000000475514240717435021716 0ustar www-datawww-datarequire 'test_helper' require 'benchmark' SONG_PROPERTIES = 50.times.collect do |i| "song_property_#{i}" end module SongRepresenter include Representable::JSON SONG_PROPERTIES.each { |p| property p } end class NestedProperty < Representable::Decorator include Representable::JSON SONG_PROPERTIES.each { |p| property p } end class SongDecorator < Representable::Decorator include Representable::JSON SONG_PROPERTIES.each { |p| property p, extend: NestedProperty } end class AlbumRepresenter < Representable::Decorator include Representable::JSON # collection :songs, extend: SongRepresenter collection :songs, extend: SongDecorator end Song = Struct.new(*SONG_PROPERTIES.map(&:to_sym)) Album = Struct.new(:songs) def random_song Song.new(*SONG_PROPERTIES.collect { |p| Song.new(*SONG_PROPERTIES) }) end times = [] 3.times.each do album = Album.new(100.times.collect { random_song }) times << Benchmark.measure do puts "================ next!" AlbumRepresenter.new(album).to_json end end puts times.join("") album = Album.new(100.times.collect { random_song }) require 'ruby-prof' RubyProf.start AlbumRepresenter.new(album).to_hash res = RubyProf.stop printer = RubyProf::FlatPrinter.new(res) printer.print(array = []) array[0..60].each { |a| puts a } # 100 songs, 100 attrs # 0.050000 0.000000 0.050000 ( 0.093157) ## 100 songs, 1000 attrs # 0.470000 0.010000 0.480000 ( 0.483708) ### without binding cache: # 2.790000 0.030000 2.820000 ( 2.820190) ### with extend: on Song, with binding cache> # 2.490000 0.030000 2.520000 ( 2.517433) 2.4-3.0 ### without skip? # 2.030000 0.020000 2.050000 ( 2.050796) 2.1-2.3 ### without :writer # 2.270000 0.010000 2.280000 ( 2.284530 1.9-2.2 ### without :render_filter # 2.020000 0.000000 2.020000 ( 2.030234) 1.5-2.0 ###without default_for and skipable? # 1.730000 0.010000 1.740000 ( 1.735597 1.4-1.7 ### without :serialize # 1.780000 0.010000 1.790000 ( 1.786791) 1.4-1.7 ### using decorator # 1.400000 0.030000 1.430000 ( 1.434206) 1.4-1.6 ### with prepare AFTER representable? # 1.330000 0.010000 1.340000 ( 1.335900) 1.1-1.3 # representable 2.0 # 3.000000 0.020000 3.020000 ( 3.013031) 2.7-3.0 # no method missing # 2.280000 0.030000 2.310000 ( 2.313522) 2.2-2.5 # no def_delegator in Definition # 2.130000 0.010000 2.140000 ( 2.136115) 1.7-2.1 representable-3.2.0/test/parse_pipeline_test.rb0000644000004100000410000000404514240717435021753 0ustar www-datawww-datarequire "test_helper" class ParsePipelineTest < MiniTest::Spec Album = Struct.new(:id, :artist, :songs) Artist = Struct.new(:email) Song = Struct.new(:title) describe "transforming nil to [] when parsing" do representer!(decorator: true) do collection :songs, parse_pipeline: ->(*) { Representable::Pipeline.insert( parse_functions, # original function list from Binding#parse_functions. ->(input, options) { input.nil? ? [] : input }, # your new function (can be any callable object).. replace: Representable::OverwriteOnNil # ..that replaces the original function. ) }, class: Song do property :title end end it do representer.new(album = Album.new).from_hash("songs"=>nil) _(album.songs).must_equal [] end it do representer.new(album = Album.new).from_hash("songs"=>[{"title" => "Business Conduct"}]) _(album.songs).must_equal [Song.new("Business Conduct")] end end # tests [Collect[Instance, Prepare, Deserialize], Setter] class Representer < Representable::Decorator include Representable::Hash # property :artist, populator: Uber::Options::Value.new(ArtistPopulator.new), pass_options:true do # property :email # end # DISCUSS: rename to populator_pipeline ? collection :songs, parse_pipeline: ->(*) { [Collect[Instance, Prepare, Deserialize], Setter] }, instance: :instance!, exec_context: :decorator, pass_options: true do property :title end def instance!(*options) Song.new end def songs=(array) represented.songs=array end end it do skip "TODO: implement :parse_pipeline and :render_pipeline, and before/after/replace semantics" album = Album.new Representer.new(album).from_hash({"artist"=>{"email"=>"yo"}, "songs"=>[{"title"=>"Affliction"}, {"title"=>"Dream Beater"}]}) _(album.songs).must_equal([Song.new("Affliction"), Song.new("Dream Beater")]) end endrepresentable-3.2.0/README.md0000644000004100000410000001151514240717435015670 0ustar www-datawww-data# Representable Representable maps Ruby objects to documents and back. ![Build Status](https://github.com/trailblazer/representable/actions/workflows/ci.yml/badge.svg?branch=master) [![Gem Version](https://badge.fury.io/rb/representable.svg)](http://badge.fury.io/rb/representable) In other words: Take an object and decorate it with a representer module. This will allow you to render a JSON, XML or YAML document from that object. But that's only half of it! You can also use representers to parse a document and create or populate an object. Representable is helpful for all kind of mappings, rendering and parsing workflows. However, it is mostly useful in API code. Are you planning to write a real REST API with representable? Then check out the [Roar](https://github.com/trailblazer/roar) gem first, save work and time and make the world a better place instead. ## Full Documentation Representable comes with a rich set of options and semantics for parsing and rendering documents. Its [full documentation](https://trailblazer.to/2.1/docs/representable.html) can be found on the Trailblazer site. ## Example What if we're writing an API for music - songs, albums, bands. ```ruby class Song < OpenStruct end song = Song.new(title: "Fallout", track: 1) ``` ## Defining Representations Representations are defined using representer classes, called _decorator, or modules. In these examples, let's use decorators ```ruby class SongRepresenter < Representable::Decorator include Representable::JSON property :title property :track end ``` In the representer the #property method allows declaring represented attributes of the object. All the representer requires for rendering are readers on the represented object, e.g. `#title` and `#track`. When parsing, it will call setters - in our example, that'd be `#title=` and `#track=`. ## Rendering Mixing in the representer into the object adds a rendering method. ```ruby SongRepresenter.new(song).to_json #=> {"title":"Fallout","track":1} ``` ## Parsing It also adds support for parsing. ```ruby song = SongRepresenter.new(song).from_json(%{ {"title":"Roxanne"} }) #=> # ``` Note that parsing hashes per default does [require string keys](https://trailblazer.to/2.1/docs/representable.html#representable-api-symbol-keys) and does _not_ pick up symbol keys. ## Collections Let's add a list of composers to the song representation. ```ruby class SongRepresenter < Representable::Decorator include Representable::JSON property :title property :track collection :composers end ``` Surprisingly, `#collection` lets us define lists of objects to represent. ```ruby Song.new(title: "Fallout", composers: ["Stewart Copeland", "Sting"]). extend(SongRepresenter).to_json #=> {"title":"Fallout","composers":["Stewart Copeland","Sting"]} ``` And again, this works both ways - in addition to the title it extracts the composers from the document, too. ## Nesting Representers can also manage compositions. Why not use an album that contains a list of songs? ```ruby class Album < OpenStruct end album = Album.new(name: "The Police", songs: [song, Song.new(title: "Synchronicity")]) ``` Here comes the representer that defines the composition. ```ruby class AlbumRepresenter < Representable::Decorator include Representable::JSON property :name collection :songs, decorator: SongRepresenter, class: Song end ``` ## Inline Representers If you don't want to maintain two separate modules when nesting representations you can define the `SongRepresenter` inline. ```ruby class AlbumRepresenter < Representable::Decorator include Representable::JSON property :name collection :songs, class: Song do property :title property :track collection :composers end end ``` ## More Representable has many more features and can literally parse and render any kind of document to an arbitrary Ruby object graph. Please check the [official documentation for more](https://trailblazer.to/2.1/docs/representable.html#representable-api). ## Installation The representable gem runs with all Ruby versions >= 2.4.0. t ```ruby gem 'representable' ``` ### Dependencies Representable does a great job with JSON, it also features support for XML, YAML and pure ruby hashes. But Representable did not bundle dependencies for JSON and XML. If you want to use JSON, add the following to your Gemfile: ```ruby gem 'multi_json' ``` If you want to use XML, add the following to your Gemfile: ```ruby gem 'nokogiri' ``` ## Copyright Representable started as a heavily simplified fork of the ROXML gem. Big thanks to Ben Woosley for his extremely inspiring work. * Copyright (c) 2011-2020 Nick Sutterer * ROXML is Copyright (c) 2004-2009 Ben Woosley, Zak Mandhro and Anders Engstrom. Representable is released under the [MIT License](https://www.opensource.org/licenses/MIT). representable-3.2.0/TODO-4.0.md0000644000004100000410000000322114240717435015772 0ustar www-datawww-data# Decorator XML::Binding::Collection.to_xml(represented) bindings.each bin.to_xml # hat vorteil: [].each{ Collection.to_xml(item) } * make all properties "Object-like", even arrays of strings etc. This saves us from having `extend ObjectBinding if typed?` and we could just call to_hash/from_hash on all attributes. performance issues here? otherwise: implement! # how to? class CH wrap :character prpoerty :a class proerty :author, dec: CH # how to? * override specific bindings and their logic? e.g. `Namespace#read` * Extend nested representers, e.g. the namespace prefix, when it gets plugged into composition * Easier polymorphic representer # XML * ditch annoying nokogiri in favor of https://github.com/YorickPeterse/oga # Parsing * Let bindings have any "xpath" * Allow to parse "wildcard" sections where you have no idea about the property names (and attribute names, eg. with links) # Options * There should be an easier way to pass a set of options to all nested #to_node decorators. ```ruby representable_attrs.keys.each do |property| options[property.to_sym] = { show_definition: false, namespaces: options[:namespaces] } end ``` * Allow passing options to Binding#serialize. serialize(.., options{:exclude => ..}) # wrap, as AsWithNamespace( Binding ) BUT NOT FOR AsWithNamespace( Binding::Attribute ) => selectively wrap bindings at compile- and runtime * Cleanup the manifest part in Decorator. * all property objects should be extended/wrapped so we don't need the switch. # Deprecations * deprecate instance: { nil } which is superseded by parse_strategy: :sync from_hash, property :band, class: vergessen representable-3.2.0/.gitignore0000644000004100000410000000013314240717435016373 0ustar www-datawww-dataspec/**/*.xml rdoc build doc Rake.config pkg .project .bundle Gemfile.lock *.swp *.swo bin representable-3.2.0/LICENSE0000644000004100000410000000210214240717435015406 0ustar www-datawww-dataCopyright (c) 2011 - 2021 Nick Sutterer and the roar contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. representable-3.2.0/Rakefile0000644000004100000410000000041614240717435016054 0ustar www-datawww-datarequire "bundler/gem_tasks" require 'bundler/setup' require 'rake/testtask' desc 'Test the representable gem.' task :default => :test Rake::TestTask.new(:test) do |test| test.libs << 'test' test.test_files = FileList['test/**/*_test.rb'] test.verbose = true end representable-3.2.0/TODO0000644000004100000410000000250314240717435015076 0ustar www-datawww-data* Pass key/index as first block arg to :class and :extend class: |key, hsh| document `XML::AttributeHash` etc * cleanup ReadableWriteable * deprecate Representable::*::ClassMethods (::from_hash and friends) * Song < OpenStruct in test_helper * have representable-options (:include, :exclude) and user-options * make all properties "Object-like", even arrays of strings etc. This saves us from having `extend ObjectBinding if typed?` and we could just call to_hash/from_hash on all attributes. performance issues here? otherwise: implement! def compile_fragment(doc) module ReaderWriter def compile_fragment(doc) do whatever super end => do that for all "features" (what parts would that be?: getter/setter, reader/writer, readable/writeable )? * make lambda options optional (arity == 0) * pass args to methods when arity matches * DISCUSS if Decorator.new.representable_attrs != Decorator.representable_attrs ? (what about performance?) * REMOVE :from, make :a(lia)s authorative. * does :instance not work with :decorator ? * make it easy to override Binding#options via #to_hash(whatever: {hit: {decorator: HitDecorator}}) * DISCUSS: should inline representers be created at runtime, so we don't need ::representer_engine? * deprecate `Decorator::Coercion`. * cleanup XML so it matches the current #serialize standard. representable-3.2.0/lib/0000755000004100000410000000000014240717435015154 5ustar www-datawww-datarepresentable-3.2.0/lib/representable.rb0000644000004100000410000000661014240717435020337 0ustar www-datawww-datarequire "uber/delegates" require "uber/callable" require "declarative/schema" require "representable/option" require "representable/config" require "representable/definition" require "representable/declarative" require "representable/deserializer" require "representable/serializer" require "representable/binding" require "representable/pipeline" require "representable/insert" # Pipeline::Insert require "representable/cached" require "representable/for_collection" require "representable/represent" module Representable autoload :Binding, 'representable/binding' autoload :HashMethods, 'representable/hash_methods' autoload :Decorator, 'representable/decorator' autoload :Hash, 'representable/hash' autoload :JSON, 'representable/json' autoload :Object, 'representable/object' autoload :YAML, 'representable/yaml' autoload :XML, 'representable/xml' attr_writer :representable_attrs def self.included(base) base.class_eval do extend Declarative # make Representable horizontally and vertically inheritable. extend ModuleExtensions, ::Declarative::Heritage::Inherited, ::Declarative::Heritage::Included extend ClassMethods extend ForCollection extend Represent end end private # Reads values from +doc+ and sets properties accordingly. def update_properties_from(doc, options, format) propagated_options = normalize_options(**options) representable_map!(doc, propagated_options, format, :uncompile_fragment) represented end # Compiles the document going through all properties. def create_representation_with(doc, options, format) propagated_options = normalize_options(**options) representable_map!(doc, propagated_options, format, :compile_fragment) doc end def representable_map(options, format) Binding::Map.new(representable_bindings_for(format, options)) end def representable_map!(doc, options, format, method) options = {doc: doc, options: options, represented: represented, decorator: self} representable_map(options, format).(method, options) # .(:uncompile_fragment, options) end def representable_bindings_for(format, options) representable_attrs.collect {|definition| format.build(definition) } end def normalize_options(user_options: {}, **options) { user_options: user_options }.merge(options) end # Prepares options for a particular nested representer. # This is used in Serializer and Deserializer. OptionsForNested = ->(options, binding) do child_options = {user_options: options[:user_options], } # wrap: child_options[:wrap] = binding[:wrap] unless binding[:wrap].nil? # nested params: child_options.merge!(options[binding.name.to_sym]) if options[binding.name.to_sym] child_options end def representable_attrs @representable_attrs ||= self.class.definitions end def representation_wrap(options = {}) representable_attrs.wrap_for(represented, options) end def represented self end module ModuleExtensions # Copies the representable_attrs reference to the extended object. # Note that changing attrs in the instance will affect the class configuration. def extended(object) super object.representable_attrs=(representable_attrs) # yes, we want a hard overwrite here and no inheritance. end end module ClassMethods def prepare(represented) represented.extend(self) end end end representable-3.2.0/lib/representable/0000755000004100000410000000000014240717435020007 5ustar www-datawww-datarepresentable-3.2.0/lib/representable/version.rb0000644000004100000410000000005514240717435022021 0ustar www-datawww-datamodule Representable VERSION = "3.2.0" end representable-3.2.0/lib/representable/xml/0000755000004100000410000000000014240717435020607 5ustar www-datawww-datarepresentable-3.2.0/lib/representable/xml/binding.rb0000644000004100000410000001065314240717435022553 0ustar www-datawww-datarequire 'representable/binding' module Representable module XML module_function def Node(document, name, attributes={}) node = Nokogiri::XML::Node.new(name.to_s, document) # Java::OrgW3cDom::DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces. attributes.each { |k, v| node[k] = v } # TODO: benchmark. node end class Binding < Representable::Binding def self.build_for(definition) return Collection.new(definition) if definition.array? return Hash.new(definition) if definition.hash? and not definition[:use_attributes] # FIXME: hate this. return AttributeHash.new(definition) if definition.hash? and definition[:use_attributes] return Attribute.new(definition) if definition[:attribute] return Content.new(definition) if definition[:content] new(definition) end def write(parent, fragments, as) wrap_node = parent if wrap = self[:wrap] parent << wrap_node = XML::Node(parent.document, wrap) end wrap_node << serialize_for(fragments, parent, as) end def read(node, as) nodes = find_nodes(node, as) return FragmentNotFound if nodes.size == 0 # TODO: write dedicated test! deserialize_from(nodes) end # Creates wrapped node for the property. def serialize_for(value, parent, as) node = XML::Node(parent.document, as) # node doesn't have attr="" attributes!!! serialize_node(node, value, as) end def serialize_node(node, value, as) if typed? value.name = as if as != self[:name] return value end node.content = value node end def deserialize_from(nodes) content_for(nodes.first) end # DISCUSS: why is this public? def serialize_method :to_node end def deserialize_method :from_node end private def find_nodes(doc, as) selector = as selector = "#{self[:wrap]}/#{as}" if self[:wrap] doc.xpath(selector) # nodes end def content_for(node) # TODO: move this into a ScalarDecorator. return node if typed? node.content end class Collection < self include Representable::Binding::Collection def serialize_for(value, parent, as) # return NodeSet so << works. set_for(parent, value.collect { |item| super(item, parent, as) }) end def deserialize_from(nodes) content_nodes = nodes.collect do |item| # TODO: move this to Node? content_for(item) end content_nodes end private def set_for(parent, nodes) Nokogiri::XML::NodeSet.new(parent.document, nodes) end end class Hash < Collection def serialize_for(value, parent, as) set_for(parent, value.collect do |k, v| node = XML::Node(parent.document, k) serialize_node(node, v, as) end) end def deserialize_from(nodes) hash = {} nodes.children.each do |node| hash[node.name] = content_for node end hash end end class AttributeHash < Collection # DISCUSS: use AttributeBinding here? def write(parent, value, as) # DISCUSS: is it correct overriding #write here? value.collect do |k, v| parent[k] = v.to_s end parent end # FIXME: this is not tested! def deserialize_from(node) HashDeserializer.new(self).deserialize(node) end end # Represents a tag attribute. Currently this only works on the top-level tag. class Attribute < self def read(node, as) node[as] end def serialize_for(value, parent, as) parent[as] = value.to_s end def write(parent, value, as) serialize_for(value, parent, as) end end # Represents tag content. class Content < self def read(node, as) node.content end def serialize_for(value, parent) parent.content = value.to_s end def write(parent, value, as) serialize_for(value, parent) end end end # Binding end end representable-3.2.0/lib/representable/xml/namespace.rb0000644000004100000410000001043114240717435023067 0ustar www-datawww-datamodule Representable::XML # Experimental! # Best explanation so far: http://books.xmlschemata.org/relaxng/relax-CHP-11-SECT-1.html # # Note: This module doesn't work with JRuby because Nokogiri uses a completely # different implementation in Java which has other requirements that we couldn't fulfil. # Please wait for Representable 4 where we replace Nokogiri with Oga. module Namespace def self.included(includer) includer.extend(DSL) end module DSL def namespace(namespace) representable_attrs.options[:local_namespace] = namespace representable_attrs.options[:namespace_mappings] ||= {} representable_attrs.options[:namespace_mappings][namespace] = nil # this might get overwritten via #namespace_def later. end def namespace_def(mapping) namespace_defs.merge!(mapping.invert) end # :private: def namespace_defs representable_attrs.options[:namespace_mappings] ||= {} end def property(name, options={}) uri = representable_attrs.options[:local_namespace] # per default, a property belongs to the local namespace. options[:namespace] ||= uri # don't override if already set. # a nested representer is automatically assigned "its" local namespace. It's like saying # property :author, namespace: "http://ns/author" do ... end super.tap do |dfn| if dfn.typed? # FIXME: ouch, this should be doable with property's API to hook into the creation process. dfn.merge!( namespace: dfn.representer_module.representable_attrs.options[:local_namespace] ) update_namespace_defs!(namespace_defs) end end end # :private: # super ugly hack # recursively injects the namespace_defs into all representers of this tree. will be done better in 4.0. def update_namespace_defs!(namespace_defs) representable_attrs.each do |dfn| dfn.merge!(namespace_defs: namespace_defs) # this only helps with scalars if dfn.typed? representer = Class.new(dfn.representer_module) # don't pollute classes. representer.update_namespace_defs!(namespace_defs) dfn.merge!(extend: representer) end end end end module AsWithNamespace def write(doc, fragment, as) super(doc, fragment, prefixed(self, as)) end # FIXME: this is shit, the NestedOptions is executed too late here! def read(node, as) super(node, prefixed(self, as)) end private def prefixed(dfn, as) uri = dfn[:namespace] # this is generic behavior and per property prefix = dfn[:namespace_defs][uri] as = Namespace::Namespaced(prefix, as) end end # FIXME: some "bug" in Representable's XML doesn't consider the container tag, so we could theoretically pick the # wrong namespaced tag here :O def from_node(node, options={}) super end def to_node(options={}) local_uri = representable_attrs.options[:local_namespace] # every decorator MUST have a local namespace. prefix = self.class.namespace_defs[local_uri] root_tag = [prefix, representation_wrap(options)].compact.join(":") options = { wrap: root_tag }.merge(options) # TODO: there should be an easier way to pass a set of options to all nested #to_node decorators. representable_attrs.keys.each do |property| options[property.to_sym] = { show_definition: false, namespaces: options[:namespaces] } end super(options).tap do |node| add_namespace_definitions!(node, self.class.namespace_defs) unless options[:show_definition] == false end end # "Physically" add `xmlns` attributes to `node`. def add_namespace_definitions!(node, namespaces) namespaces.each do |uri, prefix| prefix = prefix.nil? ? nil : prefix.to_s node.add_namespace_definition(prefix, uri) end end def self.Namespaced(prefix, name) [ prefix, name ].compact.join(":") end # FIXME: this is a PoC, we need a better API to inject code. def representable_map(options, format) super.tap do |map| map.each { |bin| bin.extend(AsWithNamespace) unless bin.is_a?(Binding::Attribute) } end end end end representable-3.2.0/lib/representable/xml/hash.rb0000644000004100000410000000200314240717435022052 0ustar www-datawww-datarequire 'representable/xml' require 'representable/hash_methods' module Representable::XML module AttributeHash include Representable::XML include Representable::HashMethods def self.included(base) base.class_eval do include Representable extend ClassMethods property(:_self, hash: true, use_attributes: true) end end module ClassMethods def values(options) hash :_self, options.merge!(:use_attributes => true) end end def create_representation_with(doc, options, format) bin = representable_bindings_for(format, options).first bin.write(doc, super, options) end end module Hash include Representable::XML include HashMethods def self.included(base) base.class_eval do include Representable extend ClassMethods property(:_self, {:hash => true}) end end module ClassMethods def values(options) hash :_self, options end end end end representable-3.2.0/lib/representable/xml/collection.rb0000644000004100000410000000112514240717435023266 0ustar www-datawww-datamodule Representable::XML module Collection def self.included(base) base.send :include, Representable::XML base.send :include, Representable::Hash::Collection base.send :include, Methods end module Methods def create_representation_with(doc, options, format) bin = representable_map(options, format).first bin.write(doc, super, bin.name) end def update_properties_from(doc, *args) super(doc.search("./*"), *args) # pass the list of collection items to Hash::Collection#update_properties_from. end end end end representable-3.2.0/lib/representable/decorator.rb0000644000004100000410000000225214240717435022317 0ustar www-datawww-datarequire "uber/inheritable_attr" require "representable" module Representable class Decorator attr_reader :represented alias_method :decorated, :represented # TODO: when moving all class methods into ClassMethods, i get a segfault. def self.prepare(represented) new(represented) end def self.default_nested_class #FIXME. SHOULD we move that into NestedBuilder? Representable::Decorator end # extend ::Declarative::Heritage::Inherited # DISCUSS: currently, that is handled via Representable::inherited. # This is called from inheritable_attr when inheriting a decorator class to a subclass. # Explicitly subclassing the Decorator makes sure representable_attrs is a clean version. def self.clone Class.new(self) end include Representable # include after class methods so Decorator::prepare can't be overwritten by Representable::prepare. include Cached extend Uber::InheritableAttr inheritable_attr :map self.map = Binding::Map.new def initialize(represented) @represented = represented end def self.nested_builder ::Declarative::Schema::DSL::NestedBuilder end end end representable-3.2.0/lib/representable/declarative.rb0000644000004100000410000000345514240717435022626 0ustar www-datawww-datarequire "declarative/schema" module Representable autoload :Decorator, "representation/decorator" autoload :Definition, "representation/definition" module Declarative def representation_wrap=(name) heritage.record(:representation_wrap=, name) definitions.wrap = name end def collection(name, options={}, &block) property(name, options.merge(collection: true), &block) end def hash(name=nil, options={}, &block) return super() unless name # allow Object.hash. options[:hash] = true property(name, options, &block) end # Allows you to nest a block of properties in a separate section while still mapping # them to the original object. def nested(name, options={}, &block) options = options.merge( getter: ->(_opts) { self }, setter: ->(opts) { }, instance: ->(_opts) { self }, ) if block options[:_nested_builder] = Decorator.nested_builder options[:_base] = Decorator.default_nested_class end property(name, options, &block) end include ::Declarative::Schema::DSL # ::property include ::Declarative::Schema::Feature include ::Declarative::Heritage::DSL def default_nested_class Module.new # FIXME: make that unnecessary in Declarative end NestedBuilder = ->(options) do Module.new do include Representable # FIXME: do we really need this? feature(*options[:_features]) include(*options[:_base]) # base when :inherit, or in decorator. module_eval(&options[:_block]) end end def nested_builder NestedBuilder end def definitions @definitions ||= Config.new(Representable::Definition) end alias_method :representable_attrs, :definitions end end representable-3.2.0/lib/representable/populator.rb0000644000004100000410000000253214240717435022363 0ustar www-datawww-datamodule Representable class Populator FindOrInstantiate = ->(input, options) { binding = options[:binding] object_class = binding[:class].(input, options) object = object_class.find_by(id: input["id"]) || object_class.new if options[:binding].array? # represented.songs[i] = model options[:represented].send(binding.getter)[options[:index]] = object else # represented.song = model options[:represented].send(binding.setter, object) end object } # pipeline: [StopOnExcluded, AssignName, ReadFragment, StopOnNotFound, OverwriteOnNil, AssignFragment, #, #, Deserialize, Set] def self.apply!(options) return unless populator = options[:populator] options[:parse_pipeline] = ->(_input, _opts) do pipeline = Pipeline[*parse_functions] # TODO: AssignFragment pipeline = Pipeline::Insert.(pipeline, SetValue, delete: true) # remove the setter function. pipeline = Pipeline::Insert.(pipeline, populator, replace: CreateObject::Populator) # let the actual populator do the job. # puts pipeline.extend(Representable::Pipeline::Debug).inspect pipeline end end end FindOrInstantiate = Populator::FindOrInstantiate end representable-3.2.0/lib/representable/binding.rb0000644000004100000410000000717314240717435021756 0ustar www-datawww-data# frozen_string_literal: true require 'uber/delegates' module Representable # The Binding provides methods to read/write the fragment for one property. # # Actually parsing the fragment from the document happens in Binding#read, everything after that is generic. class Binding class Map < Array def call(method, options) each do |bin| options[:binding] = bin # this is so much faster than options.merge(). bin.send(method, options) end end # TODO: Merge with Definitions. def <<(binding) # can be slow. this is compile time code. (existing = find { |bin| bin.name == binding.name }) ? self[index(existing)] = binding : super(binding) end end class FragmentNotFound end def self.build(definition) build_for(definition) end def initialize(definition) @definition = definition @name = @definition.name @getter = @definition.getter @setter = @definition.setter setup_exec_context! end attr_reader :name, :getter, :setter extend Uber::Delegates delegates :@definition, :has_default?, :representable?, :array?, :typed? # Single entry points for rendering and parsing a property are #compile_fragment # and #uncompile_fragment in Mapper. module Deprecatable # Retrieve value and write fragment to the doc. def compile_fragment(options) render_pipeline(nil, options).call(nil, options) end # Parse value from doc and update the model property. def uncompile_fragment(options) parse_pipeline(options[:doc], options).call(options[:doc], options) end end include Deprecatable module EvaluateOption def evaluate_option(name, input, options) proc = self[name] # puts "@@@@@ #{self.inspect}, #{name}...... #{self[name]}" proc.call(exec_context: send(:exec_context, options), keyword_arguments: options.merge(user_options: options[:options][:user_options], input: input)) # from Uber::Options::Value. # NOTE: this can also be the Proc object if it's not wrapped by Uber:::Value. end end include EvaluateOption def [](name) @definition[name] end def skipable_empty_value?(value) value.nil? and !(self[:render_nil]) end def default_for(value) return self[:default] if skipable_empty_value?(value) value end attr_accessor :cached_representer require 'representable/pipeline_factories' include Factories private def setup_exec_context! @exec_context = ->(options) { options[:represented] } unless self[:exec_context] @exec_context = ->(_options) { self } if self[:exec_context] == :binding @exec_context = ->(options) { options[:decorator] } if self[:exec_context] == :decorator end def exec_context(options) @exec_context.call(options) end def parse_pipeline(input, options) @parse_pipeline ||= pipeline_for(:parse_pipeline, input, options) { Pipeline[*parse_functions] } end def render_pipeline(input, options) @render_pipeline ||= pipeline_for(:render_pipeline, input, options) { Pipeline[*render_functions] } end # generics for collection bindings. module Collection def skipable_empty_value?(value) # TODO: this can be optimized, again. return true if value.nil? && !(self[:render_nil]) # FIXME: test this without the "and" true if (self[:render_empty] == false) && value && value.empty? # TODO: change in 2.0, don't render emtpy. end end end class DeserializeError < RuntimeError end end representable-3.2.0/lib/representable/json/0000755000004100000410000000000014240717435020760 5ustar www-datawww-datarepresentable-3.2.0/lib/representable/json/hash.rb0000644000004100000410000000077214240717435022236 0ustar www-datawww-datarequire 'representable/json' require 'representable/hash_methods' module Representable::JSON # "Lonely Hash" support. module Hash def self.included(base) base.class_eval do include Representable extend ClassMethods include Representable::JSON include Representable::HashMethods property(:_self, hash: true) end end module ClassMethods def values(options, &block) hash(:_self, options, &block) end end end end representable-3.2.0/lib/representable/json/collection.rb0000644000004100000410000000037614240717435023446 0ustar www-datawww-datarequire 'representable/json' require 'representable/hash/collection' module Representable::JSON module Collection include Representable::JSON def self.included(base) base.send :include, Representable::Hash::Collection end end end representable-3.2.0/lib/representable/for_collection.rb0000644000004100000410000000165114240717435023340 0ustar www-datawww-datamodule Representable # Gives us Representer::for_collection and its configuration directive # ::collection_representer. module ForCollection def for_collection # this is done at run-time, not a big fan of this. however, it saves us from inheritance/self problems. @collection_representer ||= collection_representer!({}) # DON'T make it inheritable as it would inherit the wrong singular. end private def collection_representer!(options) singular = self # what happens here is basically # Module.new { include Representable::JSON::Collection; ... } nested_builder.( _base: default_nested_class, _features: [singular.collection_representer_class], _block: ->(*) { items options.merge(:extend => singular) } ) end def collection_representer(options={}) @collection_representer = collection_representer!(options) end end end representable-3.2.0/lib/representable/xml.rb0000644000004100000410000000340314240717435021134 0ustar www-datawww-datagem 'nokogiri', '> 1.10.8' require 'nokogiri' require 'representable' module Representable module XML autoload :Binding, 'representable/xml/binding' autoload :Collection, 'representable/xml/collection' autoload :Namespace, 'representable/xml/namespace' def self.included(base) base.class_eval do include Representable extend ClassMethods self.representation_wrap = true # let representable compute it. register_feature Representable::XML end end module ClassMethods def remove_namespaces! representable_attrs.options[:remove_namespaces] = true end def format_engine Representable::XML end def collection_representer_class Collection end end def from_xml(doc, *args) node = parse_xml(doc, *args) from_node(node, *args) end def from_node(node, options={}) update_properties_from(node, options, Binding) end # Returns a Nokogiri::XML object representing this object. def to_node(options={}) options[:doc] = Nokogiri::XML::Document.new # DISCUSS: why do we need a fresh Document here? root_tag = options[:wrap] || representation_wrap(options) create_representation_with(Node(options[:doc], root_tag.to_s), options, Binding) end def to_xml(*args) to_node(*args).to_s end alias_method :render, :to_xml alias_method :parse, :from_xml private def remove_namespaces? # TODO: make local Config easily extendable so you get Config#remove_ns? etc. representable_attrs.options[:remove_namespaces] end def parse_xml(doc, *args) node = Nokogiri::XML(doc) node.remove_namespaces! if remove_namespaces? node.root end end end representable-3.2.0/lib/representable/coercion.rb0000644000004100000410000000136214240717435022137 0ustar www-datawww-datagem 'dry-types', '>= 1.0.0' require "dry-types" module Representable module Coercion module Types include Dry::Types() end class Coercer def initialize(type) @type = type end def call(input, _options) @type.call(input) end end def self.included(base) base.class_eval do extend ClassMethods register_feature Coercion end end module ClassMethods def property(name, options={}, &block) super.tap do |definition| return definition unless type = options[:type] definition.merge!(render_filter: coercer = Coercer.new(type)) definition.merge!(parse_filter: coercer) end end end end end representable-3.2.0/lib/representable/hash.rb0000644000004100000410000000331414240717435021260 0ustar www-datawww-datarequire 'representable' require 'representable/hash/binding' module Representable # The generic representer. Brings #to_hash and #from_hash to your object. # If you plan to write your own representer for a new media type, try to use this module (e.g., check how JSON reuses Hash's internal # architecture). module Hash autoload :Collection, 'representable/hash/collection' autoload :AllowSymbols, 'representable/hash/allow_symbols' def self.included(base) base.class_eval do include Representable # either in Hero or HeroRepresentation. extend ClassMethods register_feature Representable::Hash end end module ClassMethods def format_engine Representable::Hash end def collection_representer_class Collection end end def from_hash(data, options={}, binding_builder=Binding) data = filter_wrap(data, options) update_properties_from(data, options, binding_builder) end def to_hash(options={}, binding_builder=Binding) hash = create_representation_with({}, options, binding_builder) return hash if options[:wrap] == false return hash unless (wrap = options[:wrap] || representation_wrap(options)) {wrap => hash} end alias_method :render, :to_hash alias_method :parse, :from_hash private def filter_wrap(data, options) return data if options[:wrap] == false return data unless (wrap = options[:wrap] || representation_wrap(options)) filter_wrap_for(data, wrap) end def filter_wrap_for(data, wrap) data[wrap.to_s] || {} # DISCUSS: don't initialize this more than once. # TODO: this should be done with #read. end end end representable-3.2.0/lib/representable/represent.rb0000644000004100000410000000030514240717435022341 0ustar www-datawww-datamodule Representable::Represent def represent(represented, array_class=Array) return for_collection.prepare(represented) if represented.is_a?(array_class) prepare(represented) end end representable-3.2.0/lib/representable/cached.rb0000644000004100000410000000073614240717435021551 0ustar www-datawww-datamodule Representable # Using this module only makes sense with Decorator representers. module Cached module BuildDefinition def build_definition(*) super.tap do |definition| binding_builder = format_engine::Binding map << binding_builder.build(definition) end end end def self.included(includer) includer.extend(BuildDefinition) end def representable_map(*) self.class.map end end end representable-3.2.0/lib/representable/yaml/0000755000004100000410000000000014240717435020751 5ustar www-datawww-datarepresentable-3.2.0/lib/representable/yaml/binding.rb0000644000004100000410000000213514240717435022711 0ustar www-datawww-datarequire 'representable/hash/binding' module Representable module YAML class Binding < Representable::Hash::Binding def self.build_for(definition) return Collection.new(definition) if definition.array? new(definition) end def write(map, fragment, as) map.children << Psych::Nodes::Scalar.new(as) map.children << node_for(fragment) # FIXME: should be serialize. end # private def node_for(fragment) write_scalar(fragment) end def write_scalar(value) return value if typed? Psych::Nodes::Scalar.new(value.to_s) end def serialize_method :to_ast end def deserialize_method :from_hash end class Collection < self include Representable::Binding::Collection def node_for(fragments) Psych::Nodes::Sequence.new.tap do |seq| seq.style = Psych::Nodes::Sequence::FLOW if self[:style] == :flow fragments.each { |frag| seq.children << write_scalar(frag) } end end end end end end representable-3.2.0/lib/representable/pipeline.rb0000644000004100000410000000274514240717435022151 0ustar www-datawww-datamodule Representable # Allows to implement a pipeline of filters where a value gets passed in and the result gets # passed to the next callable object. class Pipeline < Array Stop = Class.new # options is mutable. def call(input, options) inject(input) do |memo, block| res = evaluate(block, memo, options) return(Stop) if Stop == res res end end private def evaluate(block, input, options) block.call(input, options) end module Macros # TODO: explicit test. # Macro to quickly modify an array of functions via Pipeline::Insert and return a # Pipeline instance. def insert(functions, new_function, options) Pipeline.new(Pipeline::Insert.(functions, new_function, options)) end end extend Macros end # Pipeline # Collect applies a pipeline to each element of input. class Collect < Pipeline # when stop, the element is skipped. (should that be Skip then?) def call(input, options) arr = [] input.each_with_index do |item_fragment, i| result = super(item_fragment, options.merge(index: i)) # DISCUSS: NO :fragment set. Pipeline::Stop == result ? next : arr << result end arr end class Hash < Pipeline def call(input, options) {}.tap do |hsh| input.each { |key, item_fragment| hsh[key] = super(item_fragment, options) }# DISCUSS: NO :fragment set. end end end end end representable-3.2.0/lib/representable/pipeline_factories.rb0000644000004100000410000000630614240717435024205 0ustar www-datawww-data# NOTE: this might become a separate class, that's why it's in a separate file. module Representable module Binding::Factories def pipeline_for(name, input, options) return yield unless (proc = @definition[name]) # proc.(self, options) instance_exec(input, options, &proc) end # i decided not to use polymorphism here for the sake of clarity. def collect_for(item_functions) return [Collect[*item_functions]] if array? return [Collect::Hash[*item_functions]] if self[:hash] item_functions end def parse_functions [*default_parse_init_functions, *collect_for(default_parse_fragment_functions), *default_post_functions] end # DISCUSS: StopOnNil, before collect def render_functions [*default_render_init_functions, *collect_for(default_render_fragment_functions), WriteFragment] end def default_render_fragment_functions functions = [] functions << SkipRender if self[:skip_render] if typed? # TODO: allow prepare regardless of :extend, which makes it independent of typed? if self[:prepare] functions << Prepare end # functions << (self[:prepare] ? Prepare : Decorate) end functions << Decorate if self[:extend] and !self[:prepare] if representable? functions << (self[:serialize] ? Serializer : Serialize) end functions end def default_render_init_functions functions = [] functions << Stop if self[:readable]==false functions << StopOnExcluded functions << If if self[:if] functions << (self[:getter] ? Getter : GetValue) functions << Writer if self[:writer] functions << RenderFilter if self[:render_filter].any? functions << RenderDefault if has_default? functions << StopOnSkipable functions << (self[:as] ? AssignAs : AssignName) end def default_parse_init_functions functions = [] functions << Stop if self[:writeable]==false functions << StopOnExcluded functions << If if self[:if] functions << (self[:as] ? AssignAs : AssignName) functions << (self[:reader] ? Reader : ReadFragment) functions << (has_default? ? Default : StopOnNotFound) functions << OverwriteOnNil # include StopOnNil if you don't want to erase things. end def default_parse_fragment_functions functions = [AssignFragment] functions << SkipParse if self[:skip_parse] if self[:class] or self[:extend] or self[:instance] or self[:populator] if self[:populator] functions << CreateObject::Populator elsif self[:parse_strategy] functions << CreateObject::Instance # TODO: remove in 2.5. else functions << (self[:class] ? CreateObject::Class : CreateObject::Instance) end functions << Prepare if self[:prepare] functions << Decorate if self[:extend] if representable? functions << (self[:deserialize] ? Deserializer : Deserialize) end end functions end def default_post_functions funcs = [] funcs << ParseFilter if self[:parse_filter].any? funcs << (self[:setter] ? Setter : SetValue) end end end representable-3.2.0/lib/representable/serializer.rb0000644000004100000410000000352014240717435022505 0ustar www-datawww-datamodule Representable Getter = ->(input, options) do options[:binding].evaluate_option(:getter, input, options) end GetValue = ->(_input, options) { options[:binding].send(:exec_context, options).public_send(options[:binding].getter) } Writer = ->(input, options) do options[:binding].evaluate_option(:writer, input, options) Pipeline::Stop end # TODO: evaluate this, if we need this. RenderDefault = ->(input, options) do binding = options[:binding] binding.skipable_empty_value?(input) ? binding[:default] : input end StopOnSkipable = ->(input, options) do options[:binding].send(:skipable_empty_value?, input) ? Pipeline::Stop : input end RenderFilter = ->(input, options) do options[:binding][:render_filter].(input, options) end SkipRender = ->(input, options) do options[:binding].evaluate_option(:skip_render, input, options) ? Pipeline::Stop : input end Serializer = ->(input, options) do return if input.nil? # DISCUSS: how can we prevent that? options[:binding].evaluate_option(:serialize, input, options) end Serialize = ->(input, options) do return if input.nil? # DISCUSS: how can we prevent that? binding, options = options[:binding], options[:options] # FIXME: rename to :local_options. options_for_nested = OptionsForNested.(options, binding) input.send(binding.serialize_method, options_for_nested) end WriteFragment = ->(input, options) { options[:binding].write(options[:doc], input, options[:as]) } As = ->(input, options) { options[:binding].evaluate_option(:as, input, options) } # Warning: don't rely on AssignAs/AssignName, i am not sure if i leave that as functions. AssignAs = ->(input, options) { options[:as] = As.(input, options); input } AssignName = ->(input, options) { options[:as] = options[:binding].name; input } end representable-3.2.0/lib/representable/option.rb0000644000004100000410000000075514240717435021653 0ustar www-datawww-datarequire "trailblazer/option" module Representable # Extend `Trailblazer::Option` to support static values as callables too. class Option < ::Trailblazer::Option def self.callable?(value) [Proc, Symbol, Uber::Callable].any?{ |kind| value.is_a?(kind) } end def self.build(value) return ->(*) { value } unless callable?(value) # Wrap static `value` into a proc. super end end def self.Option(value) ::Representable::Option.build(value) end end representable-3.2.0/lib/representable/json.rb0000644000004100000410000000204314240717435021304 0ustar www-datawww-datagem "multi_json", '>= 1.14.1' require "multi_json" require "representable" module Representable # Brings #to_json and #from_json to your object. module JSON autoload :Collection, "representable/json/collection" extend Hash::ClassMethods include Hash def self.included(base) base.class_eval do include Representable # either in Hero or HeroRepresentation. extend ClassMethods # DISCUSS: do that only for classes? register_feature Representable::JSON end end module ClassMethods def format_engine Representable::Hash end def collection_representer_class JSON::Collection end end # Parses the body as JSON and delegates to #from_hash. def from_json(data, *args) data = MultiJson.load(data) from_hash(data, *args) end # Returns a JSON string representing this object. def to_json(*args) MultiJson.dump to_hash(*args) end alias_method :render, :to_json alias_method :parse, :from_json end end representable-3.2.0/lib/representable/debug.rb0000644000004100000410000000471514240717435021431 0ustar www-datawww-datarequire 'logger' module Representable module Debug module_function def _representable_logger @logger ||= Logger.new(STDOUT) end module_function def representable_log(message) _representable_logger.debug { message } end def update_properties_from(doc, options, format) representable_log "[Deserialize]........." representable_log "[Deserialize] document #{doc.inspect}" super end def create_representation_with(doc, options, format) representable_log "[Serialize]........." representable_log "[Serialize]" super end def representable_map(*) super.tap do |arr| arr.collect { |bin| bin.extend(Binding) } end end module Binding def evaluate_option(name, *args, &block) Debug.representable_log "=====#{self[name]}" if name ==:prepare Debug.representable_log (evaled = self[name]) ? " #evaluate_option [#{name}]: eval!!!" : " #evaluate_option [#{name}]: skipping" value = super Debug.representable_log " #evaluate_option [#{name}]: --> #{value}" if evaled Debug.representable_log " #evaluate_option [#{name}]: -->= #{args.first}" if name == :setter value end def parse_pipeline(*) super.extend(Pipeline::Debug) end def render_pipeline(*) super.extend(Pipeline::Debug) end end end module Pipeline::Debug def call(input, options) Debug.representable_log "Pipeline#call: #{inspect}" Debug.representable_log " input: #{input.inspect}" super end def evaluate(block, memo, options) block.extend(Pipeline::Debug) if block.is_a?(Collect) Debug.representable_log " Pipeline : -> #{_inspect_function(block)} " super.tap do |res| Debug.representable_log " Pipeline : result: #{res.inspect}" end end def inspect functions = collect do |func| _inspect_function(func) end.join(", ") "#{self.class.to_s.split("::").last}[#{functions}]" end # prints SkipParse instead of . i know, i can make this better, but not now. def _inspect_function(func) return func.extend(Pipeline::Debug).inspect if func.is_a?(Collect) return func unless func.is_a?(Proc) File.readlines(func.source_location[0])[func.source_location[1]-1].match(/^\s+(\w+)/)[1] end end end representable-3.2.0/lib/representable/hash/0000755000004100000410000000000014240717435020732 5ustar www-datawww-datarepresentable-3.2.0/lib/representable/hash/binding.rb0000644000004100000410000000117514240717435022675 0ustar www-datawww-datarequire 'representable/binding' module Representable module Hash class Binding < Representable::Binding def self.build_for(definition) return Collection.new(definition) if definition.array? new(definition) end def read(hash, as) hash.has_key?(as) ? hash[as] : FragmentNotFound end def write(hash, fragment, as) hash[as] = fragment end def serialize_method :to_hash end def deserialize_method :from_hash end class Collection < self include Representable::Binding::Collection end end end end representable-3.2.0/lib/representable/hash/allow_symbols.rb0000644000004100000410000000076414240717435024154 0ustar www-datawww-data# frozen_string_literal: true module Representable module Hash module AllowSymbols private def filter_wrap_for(data, *args) super(Conversion.stringify_keys(data), *args) end def update_properties_from(data, *args) super(Conversion.stringify_keys(data), *args) end end module Conversion def self.stringify_keys(hash) hash.keys.collect do |key| [ key.to_s, hash[key] ] end.to_h end end end end representable-3.2.0/lib/representable/hash/collection.rb0000644000004100000410000000250314240717435023412 0ustar www-datawww-datarequire 'representable/hash' module Representable::Hash module Collection include Representable::Hash def self.included(base) base.class_eval do include Representable::Hash extend ClassMethods property(:_self, {:collection => true}) end end module ClassMethods def items(options={}, &block) collection(:_self, options.merge(:getter => lambda { |*| self }), &block) end end # TODO: revise lonely collection and build separate pipeline where we just use Serialize, etc. def create_representation_with(doc, options, format) options = normalize_options(**options) options[:_self] = options bin = representable_bindings_for(format, options).first Collect[*bin.default_render_fragment_functions]. (represented, {doc: doc, fragment: represented, options: options, binding: bin, represented: represented}) end def update_properties_from(doc, options, format) options = normalize_options(**options) options[:_self] = options bin = representable_bindings_for(format, options).first value = Collect[*bin.default_parse_fragment_functions]. (doc, fragment: doc, document: doc, options: options, binding: bin, represented: represented) represented.replace(value) end end end representable-3.2.0/lib/representable/yaml.rb0000644000004100000410000000176214240717435021304 0ustar www-datawww-datarequire 'psych' require 'representable' module Representable module YAML autoload :Binding, 'representable/yaml/binding' include Hash def self.included(base) base.class_eval do include Representable register_feature Representable::YAML extend ClassMethods end end module ClassMethods def format_engine Representable::YAML end end def from_yaml(doc, options={}) hash = Psych.load(doc) from_hash(hash, options, Binding) end # Returns a Nokogiri::XML object representing this object. def to_ast(options={}) Psych::Nodes::Mapping.new.tap do |map| create_representation_with(map, options, Binding) end end def to_yaml(*args) stream = Psych::Nodes::Stream.new stream.children << doc = Psych::Nodes::Document.new doc.children << to_ast(*args) stream.to_yaml end alias_method :render, :to_yaml alias_method :parse, :from_yaml end end representable-3.2.0/lib/representable/definition.rb0000644000004100000410000000632414240717435022471 0ustar www-datawww-datarequire 'declarative/definitions' module Representable autoload :Pipeline, "representable/pipeline" autoload :Populator, "representable/populator" autoload :Option, "representable/option" # Created at class compile time. Keeps configuration options for one property. class Definition < ::Declarative::Definitions::Definition def initialize(sym, options={}, &block) options[:extend] = options[:nested] if options[:nested] super # defaults: options[:parse_filter] = Pipeline[*options[:parse_filter]] options[:render_filter] = Pipeline[*options[:render_filter]] setup!(options, &block) end def name self[:name] end alias_method :getter, :name def merge!(options, &block) options = options.clone options[:parse_filter] = @options[:parse_filter].push(*options[:parse_filter]) options[:render_filter] = @options[:render_filter].push(*options[:render_filter]) setup!(options, &block) # FIXME: this doesn't yield :as etc. self end def delete!(name) @runtime_options.delete(name) @options.delete(name) self end def [](name) # return nil if name==:extend && self[:nested].nil? # return Uber::Options::Value.new(self[:nested]) if name==:extend @runtime_options[name] end def setter :"#{name}=" end def typed? # TODO: remove. self[:class] or self[:extend] or self[:instance] end def representable? return if self[:representable] == false self[:representable] or typed? end def array? self[:collection] end def hash? self[:hash] end def has_default? @options.has_key?(:default) end def representer_module @options[:extend] end def create_binding(*args) self[:binding].call(self, *args) end def inspect state = (instance_variables-[:@runtime_options, :@name]).collect { |ivar| "#{ivar}=#{instance_variable_get(ivar)}" } "##{name} #{state.join(" ")}>" end private def setup!(options, &block) handle_extend!(options) handle_as!(options) # DISCUSS: we could call more macros here (e.g. for :nested). Representable::Populator.apply!(options) yield options if block_given? @options.merge!(options) runtime_options!(@options) end # wrapping dynamic options in Value does save runtime, as this is used very frequently (and totally unnecessary to wrap an option # at runtime, its value never changes). def runtime_options!(options) @runtime_options = {} for name, value in options value = ::Representable::Option(value) if dynamic_options.include?(name) @runtime_options[name] = value end end def dynamic_options [:as, :getter, :setter, :class, :instance, :reader, :writer, :extend, :prepare, :if, :deserialize, :serialize, :skip_parse, :skip_render] end def handle_extend!(options) mod = options.delete(:extend) || options.delete(:decorator) and options[:extend] = mod end def handle_as!(options) options[:as] = options[:as].to_s if options[:as].is_a?(Symbol) # Allow symbols for as: end end end representable-3.2.0/lib/representable/object.rb0000644000004100000410000000121214240717435021576 0ustar www-datawww-datarequire 'representable' require 'representable/object/binding' module Representable module Object def self.included(base) base.class_eval do include Representable extend ClassMethods register_feature Representable::Object end end module ClassMethods def collection_representer_class Collection end end def from_object(data, options={}, binding_builder=Binding) update_properties_from(data, options, binding_builder) end def to_object(options={}, binding_builder=Binding) create_representation_with(nil, options, binding_builder) end end end representable-3.2.0/lib/representable/object/0000755000004100000410000000000014240717435021255 5ustar www-datawww-datarepresentable-3.2.0/lib/representable/object/binding.rb0000644000004100000410000000135614240717435023221 0ustar www-datawww-datarequire 'representable/binding' module Representable module Object class Binding < Representable::Binding def self.build_for(definition) # TODO: remove default arg. return Collection.new(definition) if definition.array? new(definition) end def read(hash, as) fragment = hash.send(as) # :getter? no, that's for parsing! return FragmentNotFound if fragment.nil? and typed? fragment end def write(hash, fragment, as) true end def deserialize_method :from_object end def serialize_method :to_object end class Collection < self include Representable::Binding::Collection end end end end representable-3.2.0/lib/representable/insert.rb0000644000004100000410000000200614240717435021636 0ustar www-datawww-datamodule Representable class Pipeline < Array # i hate that. module Function class Insert def call(arr, func, options) arr = arr.dup delete!(arr, func) if options[:delete] replace!(arr, options[:replace], func) if options[:replace] arr end private def replace!(arr, old_func, new_func) arr.each_with_index { |func, index| if func.is_a?(Collect) arr[index] = Collect[*Pipeline::Insert.(func, new_func, replace: old_func)] end arr[index] = new_func if func==old_func } end def delete!(arr, removed_func) arr.delete(removed_func) # TODO: make nice. arr.each_with_index { |func, index| if func.is_a?(Collect) arr[index] = Collect[*Pipeline::Insert.(func, removed_func, delete: true)] end } end end end Insert = Pipeline::Function::Insert.new end # Pipeline end representable-3.2.0/lib/representable/config.rb0000644000004100000410000000231314240717435021600 0ustar www-datawww-datarequire 'declarative/definitions' module Representable autoload :Option, 'representable/option' # Stores Definitions from ::property. It preserves the adding order (1.9+). # Same-named properties get overridden, just like in a Hash. # # Overwrite definition_class if you need a custom Definition object (helpful when using # representable in other gems). class Config < ::Declarative::Definitions def initialize(*) super @wrap = nil end def remove(name) delete(name.to_s) end def options # FIXME: this is not inherited. @options ||= {} end def wrap=(value) value = value.to_s if value.is_a?(Symbol) @wrap = ::Representable::Option(value) end # Computes the wrap string or returns false. def wrap_for(represented, options = {}, &block) return unless @wrap value = @wrap.(exec_context: represented, keyword_arguments: options.to_hash) return value if value != true infer_name_for(represented.class.name) end private def infer_name_for(name) name.to_s.split('::').last. gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). downcase end end end representable-3.2.0/lib/representable/hash_methods.rb0000644000004100000410000000230414240717435023001 0ustar www-datawww-datamodule Representable module HashMethods def create_representation_with(doc, options, format) hash = filter_keys_for!(represented, options) # FIXME: this modifies options and replicates logic from Representable. bin = representable_map(options, format).first Collect::Hash[*bin.default_render_fragment_functions].(hash, {doc: doc, options: options, binding: bin, represented: represented, decorator: self}) end def update_properties_from(doc, options, format) hash = filter_keys_for!(doc, options) bin = representable_map(options, format).first value = Collect::Hash[*bin.default_parse_fragment_functions].(hash, fragment: hash, document: doc, binding: bin, represented: represented, options: options, decorator: self) represented.replace(value) end private def filter_keys_for!(hash, options) excluding = options[:exclude] # TODO: use same filtering method as in normal representer in Representable#create_representation_with. return hash unless (props = (options.delete(:exclude) || options.delete(:include))) hash.select { |k, _v| excluding ? !props.include?(k.to_sym) : props.include?(k.to_sym) } end end end representable-3.2.0/lib/representable/deserializer.rb0000644000004100000410000000726314240717435023026 0ustar www-datawww-datamodule Representable # we don't use keyword args, because i didn't want to discriminate 1.9 users, yet. # this will soon get introduces and remove constructs like options[:binding][:default]. # Deprecation strategy: # binding.evaluate_option_with_deprecation(:reader, options, :doc) # => binding.evaluate_option(:reader, options) # always pass in options. AssignFragment = ->(input, options) { options[:fragment] = input } ReadFragment = ->(input, options) { options[:binding].read(input, options[:as]) } Reader = ->(input, options) { options[:binding].evaluate_option(:reader, input, options) } StopOnNotFound = ->(input, _options) do Binding::FragmentNotFound == input ? Pipeline::Stop : input end StopOnNil = ->(input, _options) do # DISCUSS: Not tested/used, yet. input.nil? ? Pipeline::Stop : input end OverwriteOnNil = ->(input, options) do input.nil? ? (SetValue.(input, options); Pipeline::Stop) : input end Default = ->(input, options) do Binding::FragmentNotFound == input ? options[:binding][:default] : input end SkipParse = ->(input, options) do options[:binding].evaluate_option(:skip_parse, input, options) ? Pipeline::Stop : input end module Function class Prepare def call(input, options) binding = options[:binding] binding.evaluate_option(:prepare, input, options) end end class Decorate def call(object, options) binding = options[:binding] return object unless object # object might be nil. mod = binding.evaluate_option(:extend, object, options) prepare_for(mod, object, binding) end def prepare_for(mod, object, binding) mod.prepare(object) end end end module CreateObject Instance = ->(input, options) { options[:binding].evaluate_option(:instance, input, options)|| raise( DeserializeError.new(":instance did not return class constant for `#{options[:binding].name}`.")) } Class = ->(input, options) do object_class = options[:binding].evaluate_option(:class, input, options) || raise( DeserializeError.new(":class did not return class constant for `#{options[:binding].name}`.")) object_class.new end # FIXME: no additional args passed here, yet. Populator = ->(*) { raise "Populator: implement me!" } end # CreateObject = Function::CreateObject.new Prepare = Function::Prepare.new Decorate = Function::Decorate.new Deserializer = ->(input, options) { options[:binding].evaluate_option(:deserialize, input, options) } Deserialize = ->(input, args) do binding, fragment, options = args[:binding], args[:fragment], args[:options] # user_options: child_options = OptionsForNested.(options, args[:binding]) input.send(binding.deserialize_method, fragment, child_options) end ParseFilter = ->(input, options) do options[:binding][:parse_filter].(input, options) end Setter = ->(input, options) { options[:binding].evaluate_option(:setter, input, options) } SetValue = ->(input, options) { options[:binding].send(:exec_context, options).send(options[:binding].setter, input) } Stop = ->(*) { Pipeline::Stop } If = ->(input, options) { options[:binding].evaluate_option(:if, nil, options) ? input : Pipeline::Stop } StopOnExcluded = ->(input, options) do return input unless options[:options] return input unless props = (options[:options][:exclude] || options[:options][:include]) res = props.include?(options[:binding].name.to_sym) # false with include: Stop. false with exclude: go! return input if options[:options][:include]&&res return input if options[:options][:exclude]&&!res Pipeline::Stop end end representable-3.2.0/Gemfile0000644000004100000410000000024714240717435015704 0ustar www-datawww-datasource "https://rubygems.org" gemspec group :test do gem "minitest-line" gem "multi_json", require: false gem "nokogiri", require: false end gem 'pry-byebug' representable-3.2.0/CHANGES.md0000644000004100000410000007235614240717435016015 0ustar www-datawww-data# 3.2.0 * Drop support for Virtus Coercion * Fix deprecation warning with Nokogiri 1.13.0 # 3.1.1 * Upgrade `trailblazer-option` to `0.1.1` which supports passing an empty `keyword_arguments`. # 3.1.0 * Remove circular require * Use Dry-types 1.0.0+ as coercion library * Renamed Coercion to VirtusCoercion to support old codebases * Replace `declarative-option` with [`trailblazer-option`](https://github.com/trailblazer/trailblazer-option) # 3.0.4 * Add proper XML namespace support. * [internal] Replace `XML::Binding#node_for` with function `XML::Node`. # 3.0.3 * Replace `Uber::Option` with the new [`Declarative::Option`](https://github.com/apotonick/declarative-option). This should result in a significant performance boost. # 3.0.2 * Initialize `Config@wrap` to avoid Ruby's warning. * Add `#render` and `#parse` alias methods to all format modules as a generic entry point. * In `GetValue`, use `public_send` now. # 3.0.1 * Loosen `uber` dependency. # 3.0.0 * Removed deprecations from 2.4. * Removed `:parse_strategy` in favor of `:populator`. * Removed `:binding` in favor of your own pipeline. # 2.4.1 * No need to use Uber::Callable in Pipeline as this object is always invoked via `#call`. # 2.4.0 Full migration guide here: http://trailblazer.to/gems/representable/upgrading-guide.html#to-24 * Breaking API change: `:parse_filter` and `:render_filter` have no deprecation as all the other, they receive one options. render_filter: val, doc, options * `Decorator` always needs a format engine included, e.g. `Representable::JSON` to build bindings at compile-time. * Removed `Representable::Apply`. This is now done via `Schema`. representer_class.representable_attrs is definitions * Removed `:use_decorator` option. Use a decorator instead. * Added `Representable.deprecations = false` to disable slow and weird deprecation code. * Removed `Binding#user_options`. This is now available via `->(options[:user_options])`. * Removed `Binding#as`. * Removed `Binding#parent_decorator`. ## Internal Changes * Removed `Binding@represented` (which was never public anyway). Use `Binding#represented`. * Changed signature: `Binding#get(represented:)`. In now needs a hash `{represented: ..}`. # 2.4.0.rc5 * Fix double definition of `Insert`. * Deprecate `:binding`. # 2.4.0.rc4 * The preferred way of passing user options is now `to_hash(user_options: {})`. * Supports nested options for nested representers. # 2.4.0.rc3 * `Set` is `SetValue`. `Get` is `GetValue`. * `CreateObject` no longer invokes `AssignFragment`. This is now part of the official parse pipeline. # 2.4.0.rc2 * Use Declarative's `::build_definition` interface instead of overwriting `::property`. # 2.3.0 * Remove dependency to Nokogiri and Multi_JSON. You have to add what you need to your `Gemfile`/`gemspec` now. * `to_*`/`from_*` with options do no longer change the hash but work on copies. * `to_*`/`from_*` now respect `wrap: false`. This will suppress the wrapping on the first level. * Introduce `property "name", wrap: false`. This allows reusing existing representers with `representation_wrap` set as nested representers but suppressing the wrapping. ```ruby class BandDecorator < Representable::Decorator include Representable::Hash self.representation_wrap = :bands # wrap set! property :name end class AlbumDecorator < Representable::Decorator include Representable::Hash self.representation_wrap = :albums # wrap set! property :band, decorator: BandDecorator, wrap: false # you can now set :wrap to false! end album.to_hash #=> {"albums" => {"band" => {"name"=>"Social Distortion"}}} ``` Thanks to @fighella for inspiring this feature when working on [roarify](https://github.com/fighella/roarify). * `from_hash` no longer crashes with "NoMethodError: undefined method 'has_key?' for nil:NilClass" when an incoming nested object's value is `nil`. This was a problem with documents like `{song: nil}` where `song` is a nested property. Thanks to @mhuggins and @moxley for helping here. # 2.2.3 * Introduce `Decorator::clone` to make sure cloning properly copies representable_attrs. in former versions, this would partially share definitions with subclasses when the decorator was an `inheritable_attr`. # 2.2.2 * Bug fix: In 2.2.1 I accidentially removed a `super` call in `Representable::inherited` which leads to wrong behavior when having Representable mixed into a class along with other classes overriding `inherited`. Thanks to @jrmhaig for an [excellent bug report](https://github.com/apotonick/representable/issues/139#issuecomment-105926608) making it really easy to find the problem. # 2.2.1 ## API change. * Options in `Definition` are now Cloneable. That means they will deep-clone when they contain values that are `Cloneable`. This allows clean cloning of deeply nested configuration hashes, e.g. for `deserializer: {instance: ->{}}` in combination with inheritance across representers. The former behavior was not to clone, which would allow sub-representers to bleed into the parent options, which is _wrong_. However, this fix shouldn't affect anyone but me. # 2.2.0 ## New Stuff * Introduce `Representable::Cached` that will keep the mapper, which in turn will keep the bindings, which in turn will keep their representer, in case they're nested. You have to include this feature manually and you can expect a 50% and more speed-up for rendering and parsing. Not to speak about the reduced memory footprint. ```ruby class SongDecorator < Representable::Decorator include Representable::JSON feature Representable::Cached # .. end ``` * Introduced `Decorator#update!` to re-use a decorator instance between requests. This will inject the represented object, only. ```ruby decorator = SongDecorator.new(song) decorator.to_json(..) decorator.update!(louder_song) decorator.to_json(..) ``` This is quite awesome. ## API change. * The `:extend` option only accepts one module. `extend: [Module, Module]` does no longer work and it actually didn't work in former versions of 2.x, anyway, it just included the first element of an array. * Remove `Binding#representer_module`. # 2.1.8 * API change: features are now included into inline representers in the order they were specified. This used to be the other way round and is, of course, wrong, in case a sub-feature wants to override an existing method introduced by an earlier feature. ```ruby class Album < Representable::Decorator include Representable::Hash feature Title feature Date property :songs # will include R::Hash, Title, then Date. ``` As this is an edge-casey change, I decided _not_ to minor-version bump. # 2.1.7 * Adding `Object#to_object`. This is even faster than using `#from_object` for simple transformations. # 2.1.6 * Introducing `Representable::Object` that allows mapping objects to objects. This is way faster than rendering a hash from the source and then writing the hash to the target object. * Fixed loading issues when requiring `representable/decorator`, only. # 2.1.5 * Using `inherit: true` now works even if the parent property hasn't been defined before. It will simply create a new property. This used to crash with `undefined method `merge!' for nil:NilClass`. ```ruby class SongRepresenter < Representable::Decorator property :title, inherit: true # this will create a brand-new :title property. end ``` # 2.1.4 * Allow lonely collection representers without configuration, with inline representer, only. This is for render-only collection representers and very handy. * Now runs with MagLev. # 2.1.3 * Like 2.1.2 (got yanked) because I thought it's buggy but it's not. What has changed is that `Serializer::Collection#serialize` no longer does `collection.collect` but `collection.each` since this allows filtering out unwanted elements. # 2.1.2 * Added `:skip_render` options. # 2.1.1 * Added `Definition#delete!` to remove options. * Added `Representable::apply` do iterate and change schemas. * Added `Config#remove` to remove properties. * Added `Representable::Debug` which just has to be included into your represented object. ```ruby song.extend(SongRepresenter).extend(Representable::Debug).from_json("..") song.extend(SongRepresenter).extend(Representable::Debug).to_json("..") ``` It can also be included statically into your representer or decorator. ```ruby class SongRepresenter < Representable::Decorator include Representable::JSON include Representable::Debug property :title end ``` It is great. # 2.1.0 ## Breaking Changes * None, unless you messed around with internals like `Binding`. ## Changes * Added `:skip_parse` to skip deserialization of fragments. * It's now `Binding#read_fragment -> Populator -> Deserializer`. Mostly, this got changed to allow better support for complex collection semantics when populating/deserializing as found in Object-HAL. * Likewise, it is `Binding#write_fragment -> Serializer`, clearly separating format-specific and generic logic. * Make `Definition#inspect` more readable by filtering out some instance variables like `@runtime_options`. * Remove `Binding#write_fragment_for`. This is `#render_fragment` now. * Almost 100% speedup for rendering and parsing by removing Ruby's delegation and `method_missing`. * Bindings are now in the following naming format: `Representable::Hash::Binding[::Collection]`. The file name is `representable/hash/binding`. * Move `Definition#skipable_empty_value?` and `Definition#default_for` to `Binding` as it is runtime-specific. # 2.0.4 * Fix implicit rendering of JSON and XML collections where json/collection wasn't loaded properly, resulting in the native JSON's `#to_json` to be called. * Fix `:find_or_instantiate` parse strategy which wouldn't instantiate but raise an error. Thanks to @d4rky-pl. # 2.0.3 * Fixed a bug where `Forwardable` wasn't available (because we didn't require it :). # 2.0.2 * Fixed a bug with `Config#[]` which returned a default value that shouldn't be there. # 2.0.1 * Made is simpler to define your own `Definition` class by passing it to `Config.new(Definition)` in `Representer::build_config`. # 2.0.0 ## Relevant * Removed class methods `::from_json`, `::from_hash`, `::from_yaml` and `::from_xml`. Please build the instance yourself and use something along `Song.new.from_json`. * Inline representers in `Decorator` do *no longer inherit from `self`*. When defining an inline representer, they are always derived from `Representable::Decorator`. The base class can be changed by overriding `Decorator::default_inline_class` in the decorator class that defines an inline representer block. If you need to inherit common methods to all inline decorators, include the module using `::feature`: `Representer.feature(BetterProperty)`. * You can now define methods in inline representers! The block is now `module_eval`ed and not `instance_exec`ed anymore. Same goes for Decorators, note that you need to `exec_context: :decorator`, though. Here, the block is `class_eval`ed. * Removed behaviour for `instance: lambda { |*| nil }` which used to return `binding.get`. Simply do it yourself: `instance: lambda { |fragment, options| options.binding.get }` if you need this behaviour. If you use `:instance` and it returns `nil` it throws a `DeserializeError` now, which is way more understandable than `NoMethodError: undefined method 'title=' for {"title"=>"Perpetual"}:Hash`. * Remove behaviour for `class: lambda { nil }` which used to return the fragment. This now throws a `DeserializeError`. Do it yourself with `class: lambda { |fragment,*| fragment }`. * Coercion now happens inside `:render_filter` and `:parse_filter` (new!) and doesn't block `:getter` and `:setter` anymore. Also, we require virtus >=1.0 now. * `::representation_wrap=` in now properly inherited. * Including modules with representable `property .., inherit: true` into a `Decorator` crashed. This works fine now. ## New Stuff * Added `::for_collection` to automatically generate a collection representer for singular one. Thanks to @timoschilling for inspiring this years ago. * Added `::represent` that will detect collections and render the singular/collection with the respective representer. * Added `Callable` options. * Added `:parse_filter` and `:render_filter`. ## Internals * Added `Representable::feature` to include a module and register it to be included into inline representers. * New signature: `inline_representer(base, features, name, options, &block)`. * Removed `::representer_engine`, the module to include is just another `register_feature` now. * `Config` no longer is a Hash, it's API is limited to a few methods like `#<<`, `#[]` etc. It still supports the `Enumberable` interface. * Moved `Representable::ClassMethods::Declarations` to `Representable::Declarative`. * Moved `Representable::ClassMethods` to `Representable::Declarative`. * Fixed: Inline decorators now work with `inherit: true`. * Remove `:extend` in combination with inline representer. The `:extend` option is no longer considered. Include the module directly into the inline block. * Deprecated class methods `::from_json` and friends. Use the instance version on an instance. * Use uber 0.0.7 so we can use `Uber::Callable`. * Removed `Decorator::Coercion`. * Removed `Definition#skipable_nil_value?`. # 1.8.5 * Binding now uses `#method_missing` instead of SimpleDelegator for a significant performance boost of many 100%s. Thanks to @0x4a616d6573 for figuring this. # 1.8.4 * Make `representable/json` work without having to require `representable/hash`. Thanks to @acuppy!!! # 1.8.3 * Fix `JSON::Collection` and `JSON::Hash` (lonely arrays and hashes), they can now use inline representers. Thanks to @jandudulski for reporting. * Added `:render_empty` option to suppress rendering of empty collections. This will default to true in 2.0. * Remove Virtus deprecations. * Add support for Rubinius. * `Definition#default` is public now, please don't use it anyway, it's a private concept. # 1.8.1 * Add `:serialize` and `:deserialize` options for overriding those steps. # 1.8.0 ## Major Breakage * `:if` receives block arguments just like any other dynamic options. Refer to **Dynamic Options**. * Remove defaults for collections. This fixes a major design flaw - when parsing a document a collection would be reset to `[]` even if it is not present in the parsed document. * The number of arguments per block might have changed. Generally, if you're not interested in block arguments, use `Proc.new` or `lambda { |*| }`. See **Dynamic Options**. ## Dynamic Options * The following options are dynamic now and can either be a static value, a lambda or an instance method symbol: `:as`, `:getter`, `:setter`, `:class`, `:instance`, `:reader`, `:writer`, `:extend`, `:prepare`, `:if`. Please refer to the README to see their signatures. * `representation_wrap` is dynamic, too, allowing you to change the wrap per instance. ## Cool New Stuff * When unsure about the number of arguments passed into an option lambda, use `:pass_options`. This passes all general options in a dedicated `Options` object that responds to `binding`, `decorator`, `represented` and `user_options`. It's always the last argument for the block. * Added `parse_strategy: :find_or_instantiate`. More to come. * Added `parse_strategy: lambda { |fragment, i, options| }` to implement your own deserialization. * Use `representable: false` to prevent calling `to_*/from_*` on a represented object even if the property is `typed?` (`:extend`, `:class` or `:instance` set). * Introduced `:use_decorator` option to force an inline representer to be implemented with a Decorator even in a module. This fixes a bug since we used the `:decorate` option in earlier versions, which was already used for something else. * Autoload `Representable::Hash*` and `Representable::Decorator`. * Added `Representable::Hash::AllowSymbols` to convert symbol keys to strings in `from_hash`. ## Deprecations * `decorator_scope: true` is deprecated, use `exec_context: :decorator` instead. * Using `:extend` in combination with an inline representer is deprecated. Include the module in the block. * `instance: lambda { true }` is deprecated. Use `parse_strategy: :sync`. * Removed `Config#wrap`. Only way to retrieve the evaluated wrap is `Config#wrap_for`. * `class: lambda { nil }` is deprecated. To return the fragment from parsing, use `instance: lambda { |fragment, *args| fragment }` instead. ## Definition * Make `Definition < Hash`, all options can/should now be accessed with `Definition#[]`. * Make `Definition::new` and `#merge!` the only entry points so that a `Definition` becomes an almost *immutual* object. If you happened to modify a definition using `options[..]=` this will break now. Use `definition.merge!(..)` to change it after creation. * Deprecated `#options` as the definition itself is a hash (e.g. `definition[:default]`). * Removed `#sought_type`, `#default`, `#attribute`, `#content`. * `#from` is replaced by `#as` and hardcore deprecated. * `#name` and `#as` are _always_ strings. * A Definition is considered typed as soon as [`:extend`|`:class`|`:instance`] is set. In earlier versions, `property :song, class: Song` was considered typed, whereas `property :song, class: lambda { Song }` was static. h2. 1.7.7 * Parsing an empty hash with a representer having a wrap does no longer throw an exception. * `::nested` now works in modules, too! Nests are implemented as decorator representer, not as modules, so they don't pollute the represented object. * Introduce `:inherit` to allow inheriting+overriding properties and inline representers (and properties in inline representers - it starts getting crazy!!!). h2. 1.7.6 * Add `::nested` to nest blocks in the document whilst still using the same represented object. Use with `Decorator` only. * Fixing a bug (thanks @rsutphin) where inline decorators would inherit the properties from the outer decorator. h2. 1.7.5 * propagate all options for ::property to ::inline_representer. h2. 1.7.3 * Fix segfaulting with XML by passing the document to nested objects. Reported by @timoschilling and fixed by @canadaduane. h2. 1.7.2 * `Representable#update_properties_from` is private now. * Added the `:content` option in XML to map top-level node's content to a property. h2. 1.7.1 * Introduce `Config#options` hash to store per-representer configuration. * The XML representer can now automatically remove namespaces when parsing. Use `XML::remove_namespaces!` in your representer. This is a work-around until namespaces are properly implemented in representable. h2. 1.7.0 * The actual serialization and deserialization (that is, calling `to_hash` etc on the object) now happens in dedicated classes: `ObjectDeserializer` and friends. If you used to override stuff in `Binding`, I'm sorry. * A new option `parse_strategy: :sync`. Instead of creating a new object using the `:class` option when parsing, it uses the original object found in the represented instance. This works for property and collections. * `Config` is now a hash. You may find a particular definition by using `Config#[]`. * Properties are now overridden: when calling `property(:title)` multiple times with the same name, this will override the former `Definition`. While this slightly changes the API, it allows overriding properties cleanly in sub-representers and saves you from manually finding and fiddling with the definitions. h2. 1.6.1 * Using `instance: lambda { nil }` will now treat the property as a representable object without trying to extend it. It simply calls `to_*/from_*` on the property. * You can use an inline representer and still have a `:extend` which will be automatically included in the inline representer. This is handy if you want to "extend" a base representer with an inline block. Thanks to @pixelvitamina for requesting that. * Allow inline representers with `collection`. Thanks to @rsutphin! h2. 1.6.0 * You can define inline representers now if you don't wanna use multiple modules and files. ```ruby property :song, class: Song do property :title end ``` This supersedes the use for `:extend` or `:decorator`, which still works, of course. * Coercion now happens in a dedicated coercion object. This means that in your models virtus no longer creates accessors for coerced properties and thus values get coerced when rendering or parsing a document, only. If you want the old behavior, include `Virtus` into your model class and do the coercion yourself. * `Decorator::Coercion` is deprecated, just use `include Representable::Coercion`. * Introducing `Mapper` which does most of the rendering/parsing process. Be careful, if you used to override private methods like `#compile_fragment` this no longer works, you have to override that in `Mapper` now. * Fixed a bug where inheriting from Decorator wouldn't inherit properties correctly. h2. 1.5.3 * `Representable#update_properties_from` now always returns `represented`, which is `self` in a module representer and the decorated object in a decorator (only the latter changed). * Coercion in decorators should work now as expected. * Fixed a require bug. h2. 1.5.2 * Rename `:representer_exec` to `:decorator_scope` and make it a documented (!) feature. * Accessors for properties defined with `decorator_scope: true` will now be invoked on the decorator, not on the represented instance anymore. This allows having decorators with helper methods. * Use `MultiJson` instead of `JSON` when parsing and rendering. * Make `Representable::Decorator::Coercion` work. h2. 1.5.1 * Make lonely collections and hashes work with decorators. h2. 1.5.0 * All lambdas now receive user options, too. Note that this might break your existing lambdas (especially with `:extend` or `:class`) raising an `ArgumentError: wrong number of arguments (2 for 1)`. Fix this by declaring your block params correctly, e.g. `lambda { |name, *|`. Internally, this happens by running all lambdas through the new `Binding#represented_exec_for`. h2. 1.4.2 * Fix the processing of `:setter`, we called both the setter lambda and the setter method. h2. 1.4.1 * Added `:representer_exec` to have lambdas be executed in decorator instance context. h2. 1.4.0 * We now have two strategies for representing: the old extend approach and the brand-new decorator which leaves represented objects untouched. See "README":https://github.com/apotonick/representable#decorator-vs-extend for details. * Internally, either extending or decorating in the Binding now happens through the representer class method `::prepare` (i.e. `Decorator::prepare` or `Representable::prepare` for modules). That means any representer module or class must expose this class method. h2. 1.3.5 * Added `:reader` and `:writer` to allow overriding rendering/parsing of a property fragment and to give the user access to the entire document. h2. 1.3.4 * Replacing `json` gem with `multi_json` hoping not to cause trouble. h2. 1.3.3 * Added new options: `:binding`, `:setter` and `:getter`. * The `:if` option now eventually receives passed in user options. h2. 1.3.2 * Some minor internal changes. Added `Config#inherit` to encasulate array push behavior. h2. 1.3.1 * Bringing back `:as`. For some strange reasons "we" lost that commit from @csexton!!! h2. 1.3.0 * Remove @:exclude@ option. * Moving all read/write logic to @Binding@. If you did override @#read_fragment@ and friends in your representer/models this won't work anymore. * Options passed to @to_*/from_*@ are now passed to nested objects. h2. 1.2.9 * When @:class@ returns @nil@ we no longer try to create a new instance but use the processed fragment itself. * @:instance@ allows overriding the @ObjectBinding#create_object@ workflow by returning an instance from the lambda. This is particularly helpful when you need to inject additional data into the property object created in #deserialize. * @:extend@ and @:class@ now also accept procs which allows having polymorphic properties and collections where representer and class can be chosen at runtime. h2. 1.2.8 * Reverting all the bullshit from 1.2.7 making it even better. @Binding@s now wrap their @Definition@ instance adopting its API. Moved the binding_for_definition mechanics to the respecting @Binding@ subclass. * Added :readable and :writeable to #property: while @:readable => true@ renders the property into the document @:writeable => true@ allows updating the property's value when consuming a representation. Both default to @true@. h2. 1.2.7 * Moving @Format.binding_for_definition@ to @Format#{format}_binding_for_definition@, making it an instance method in its own "namespace". This allows mixing in multiple representer engines into a user's representer module. h2. 1.2.6 * Extracted @HashRepresenter@ which operates on hash structures. This allows you to "parse" form data, e.g. as in Rails' @params@ hash. Internally, this is used by JSON and partly by YAML. h2. 1.2.5 * Add support for YAML. h2. 1.2.4 * ObjectBinding no longer tries to extend nil values when rendering and @:render_nil@ is set. * In XML you can now use @:wrap@ to define an additional container tag around properties and collections. h2. 1.2.3 * Using virtus for coercion now works in both classes and modules. Thanks to @solnic for a great collaboration. Open-source rocks! h2. 1.2.2 * Added @XML::AttributeHash@ to store hash key-value pairs in attributes instead of dedicated tags. * @JSON::Hash@, @XML::Hash@ and @XML::AttributeHash@ now respect @:exclude@ and @:include@ when parsing and rendering. h2. 1.2.1 * Deprecated @:represent_nil@ favor of @:render_nil@. * API change: if a property is missing in an incoming document and there is no default set it is completely ignored and *not* set in the represented object. h2. 1.2.0 * Deprecated @:except@ in favor of @:exclude@. * A property with @false@ value will now be included in the rendered representation. Same applies to parsing, @false@ values will now be included. That particularly means properties that used to be unset (i.e. @nil@) after parsing might be @false@ now. * You can include @nil@ values now in your representations since @#property@ respects @:represent_nil => true@. h2. 1.1.6 * Added @:if@ option to @property@. h2. 1.1.5 * Definitions are now properly cloned when @Config@ is cloned. h2. 1.1.4 * representable_attrs is now cloned when a representer module is included in an inheriting representer. h2. 1.1.3 * Introduced `#compile_fragment` and friends to make it simpler overriding parsing and rendering steps. h2. 1.1.2 * Allow `Module.hash` to be called without arguments as this seems to be required in Padrino. h2. 1.1.1 * When a representer module is extended we no longer set the @representable_attrs ivar directly but use a setter. This makes it work with mongoid and fixes https://github.com/apotonick/roar/issues/10. h2. 1.1.0 * Added `JSON::Collection` to have plain list representations. And `JSON::Hash` for hashes. * Added the `hash` class method to XML and JSON to represent hashes. * Defining `:extend` only on a property now works for rendering. If you try parsing without a `:class` there'll be an exception, though. h2. 1.0.1 * Allow passing a list of modules to :extend, like @:extend => [Ingredient, IngredientRepresenter]@. h2. 1.0.0 * 1.0.0 release! Party time! h2. 0.13.1 * Removed property :@name from @XML@ in favor of @:attribute => true@. h2. 0.13.0 * We no longer create accessors in @Representable.property@ - you have to do this yourself using @attr_accessors@. h2. 0.12.0 * @:as@ is now @:class@. h2. 0.11.0 * Representer modules can now be injected into objects using @#extend@. * The @:extend@ option allows setting a representer module for a typed property. This will extend the contained object at runtime roughly following the DCI pattern. * Renamed @#representable_property@ and @#representable_collection@ to @#property@ and @#collection@ as we don't have to fear namespace collisions in modules. h2. 0.10.3 * Added @representable_property :default => ...@ option which is considered for both serialization and deserialization. The default is applied when the value is @nil@. Note that an empty string ain't @nil@. * @representable_attrs@ are now pushed to instance level as soon as possible. h2. 0.10.2 * Added @representable_property :accessors => false@ option to suppress adding accessors. * @Representable.representation_wrap@ is no longer inherited. * Representers can now be defined in modules. They inherit to including modules. h2. 0.10.1 * The block in @to_*@ and @from_*@ now yields the symbolized property name. If you need the binding/definition you gotta get it yourself. * Runs with Ruby 1.8 and 1.9. h2. 0.10.0 * Wrapping can now be set through @Representable.representation_wrap=@. Possible values are: * @false@: No wrapping. In XML context, this is undefined behaviour. Default in JSON. * @String@: Wrap with provided string. * @true@: compute wrapper from class name. h2. 0.9.3 * Removed the @:as => [..]@ syntax in favor of @:array => true@. h2. 0.9.2 * Arguments and block now successfully forwarded in @#from_*@. h2. 0.9.1 * Extracted common serialization into @Representable#create_representation_with@ and deserialization into @#update_properties_from@. * Both serialization and deserialization now accept a block to make them skip elements while iterating the property definitions. h2. 0.9.0 h3. Changes * Removed the :tag option in favor of :from. The Definition#from method is now authorative for all name mappings. * Removed the active_support and i18n dependency. representable-3.2.0/.github/0000755000004100000410000000000014240717435015746 5ustar www-datawww-datarepresentable-3.2.0/.github/workflows/0000755000004100000410000000000014240717435020003 5ustar www-datawww-datarepresentable-3.2.0/.github/workflows/ci.yml0000644000004100000410000000063714240717435021127 0ustar www-datawww-dataname: CI on: push: branches: - master pull_request: branches: - master jobs: test: strategy: fail-fast: false matrix: ruby: [2.5, 2.6, 2.7, '3.0', "3.1"] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - run: bundle exec rake representable-3.2.0/representable.gemspec0000644000004100000410000000262314240717435020611 0ustar www-datawww-datalib = File.expand_path('../lib/', __FILE__) $:.unshift lib unless $:.include?(lib) require 'representable/version' Gem::Specification.new do |spec| spec.name = "representable" spec.version = Representable::VERSION spec.platform = Gem::Platform::RUBY spec.authors = ["Nick Sutterer"] spec.email = ["apotonick@gmail.com"] spec.homepage = "https://github.com/trailblazer/representable/" spec.summary = %q{Renders and parses JSON/XML/YAML documents from and to Ruby objects. Includes plain properties, collections, nesting, coercion and more.} spec.description = spec.summary spec.files = `git ls-files`.split("\n") spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } spec.require_paths = ["lib"] spec.license = "MIT" spec.required_ruby_version = '>= 2.4.0' spec.add_dependency "uber", "< 0.2.0" spec.add_dependency "declarative", "< 0.1.0" spec.add_dependency "trailblazer-option", ">= 0.1.1", "< 0.2.0" spec.add_development_dependency "rake" spec.add_development_dependency "test_xml", ">= 0.1.6" spec.add_development_dependency "minitest" spec.add_development_dependency "virtus" spec.add_development_dependency "dry-types" spec.add_development_dependency "ruby-prof" if RUBY_ENGINE == "ruby" # mri end