representable-3.2.0/ 0000755 0000041 0000041 00000000000 14240717435 014406 5 ustar www-data www-data representable-3.2.0/test/ 0000755 0000041 0000041 00000000000 14240717435 015365 5 ustar www-data www-data representable-3.2.0/test/binding_test.rb 0000644 0000041 0000041 00000003433 14240717435 020366 0 ustar www-data www-data require '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
end representable-3.2.0/test/decorator_test.rb 0000644 0000041 0000041 00000006306 14240717435 020740 0 ustar www-data www-data require '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
end representable-3.2.0/test/exec_context_test.rb 0000644 0000041 0000041 00000006700 14240717435 021444 0 ustar www-data www-data require '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
end representable-3.2.0/test/xml_bindings_test.rb 0000644 0000041 0000041 00000003134 14240717435 021427 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000020050 14240717435 020552 0 ustar www-data www-data require '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
end representable-3.2.0/test/for_collection_test.rb 0000644 0000041 0000041 00000004301 14240717435 021750 0 ustar www-data www-data require '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
end representable-3.2.0/test/default_test.rb 0000644 0000041 0000041 00000002223 14240717435 020374 0 ustar www-data www-data require "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
end representable-3.2.0/test/features_test.rb 0000644 0000041 0000041 00000003210 14240717435 020563 0 ustar www-data www-data require '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
end representable-3.2.0/test/lonely_test.rb 0000644 0000041 0000041 00000017135 14240717435 020262 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000016460 14240717435 021110 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000005357 14240717435 022117 0 ustar www-data www-data require "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.rb 0000644 0000041 0000041 00000000675 14240717435 022206 0 ustar www-data www-data require "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
end representable-3.2.0/test/benchmarking.rb 0000644 0000041 0000041 00000004011 14240717435 020336 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000006304 14240717435 020221 0 ustar www-data www-data require '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
end representable-3.2.0/test/if_test.rb 0000644 0000041 0000041 00000004457 14240717435 017361 0 ustar www-data www-data require '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
end representable-3.2.0/test/prepare_test.rb 0000644 0000041 0000041 00000003341 14240717435 020410 0 ustar www-data www-data require '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
end representable-3.2.0/test/render_nil_test.rb 0000644 0000041 0000041 00000000765 14240717435 021102 0 ustar www-data www-data require "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.rb 0000644 0000041 0000041 00000001240 14240717435 021604 0 ustar www-data www-data require '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
end representable-3.2.0/test/uncategorized_test.rb 0000644 0000041 0000041 00000005073 14240717435 021621 0 ustar www-data www-data require "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
end representable-3.2.0/test/skip_test.rb 0000644 0000041 0000041 00000005467 14240717435 017733 0 ustar www-data www-data require '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
end representable-3.2.0/test/json_test.rb 0000644 0000041 0000041 00000025366 14240717435 017736 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000001503 14240717435 021630 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000012441 14240717435 017676 0 ustar www-data www-data require '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/ 0000755 0000041 0000041 00000000000 14240717435 017203 5 ustar www-data www-data representable-3.2.0/test/examples/example.rb 0000644 0000041 0000041 00000015306 14240717435 021170 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000001067 14240717435 021002 0 ustar www-data www-data require "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.rb 0000644 0000041 0000041 00000006210 14240717435 020055 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000004001 14240717435 017347 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000007101 14240717435 020210 0 ustar www-data www-data require '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
end representable-3.2.0/test/populator_test.rb 0000644 0000041 0000041 00000006215 14240717435 021002 0 ustar www-data www-data require "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.rb 0000644 0000041 0000041 00000005274 14240717435 021561 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000001417 14240717435 022127 0 ustar www-data www-data require '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
end representable-3.2.0/test/config/ 0000755 0000041 0000041 00000000000 14240717435 016632 5 ustar www-data www-data representable-3.2.0/test/config/inherit_test.rb 0000644 0000041 0000041 00000007114 14240717435 021663 0 ustar www-data www-data require '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"] }
end representable-3.2.0/test/wrap_test.rb 0000644 0000041 0000041 00000011237 14240717435 017726 0 ustar www-data www-data require "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.rb 0000644 0000041 0000041 00000002053 14240717435 023320 0 ustar www-data www-data require '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
end representable-3.2.0/test/user_options_test.rb 0000644 0000041 0000041 00000001331 14240717435 021500 0 ustar www-data www-data require "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=false representable-3.2.0/test/is_representable_test.rb 0000644 0000041 0000041 00000003747 14240717435 022312 0 ustar www-data www-data require '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
end representable-3.2.0/test/representable_test.rb 0000644 0000041 0000041 00000036164 14240717435 021616 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000007126 14240717435 020241 0 ustar www-data www-data require '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
end representable-3.2.0/test/option_test.rb 0000644 0000041 0000041 00000001563 14240717435 020266 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000021016 14240717435 020227 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000013154 14240717435 021571 0 ustar www-data www-data require "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
%{666FowlertypedFrau Java1991}
#:map-xml end
)
end
it "parses" do
lib = Model::Library.new
#:parse-call
Library.new(lib).from_xml(%{666FowlertypedFrau JavaMr. 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.rb 0000644 0000041 0000041 00000003273 14240717435 020224 0 ustar www-data www-data require "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
end representable-3.2.0/test/xml_test.rb 0000644 0000041 0000041 00000037517 14240717435 017566 0 ustar www-data www-data require '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.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. CharismaI Hate My BrainMr. 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 BoyCobra 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 BoyBad 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 KevinsWright and RongLaundry 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 KevinsWright and RongLaundry 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 BasketTwo KevinsWright 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 GargoyleBronx" }
# 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.rb 0000644 0000041 0000041 00000012742 14240717435 020166 0 ustar www-data www-data require "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
end representable-3.2.0/test/inherit_test.rb 0000644 0000041 0000041 00000012353 14240717435 020417 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000007267 14240717435 020401 0 ustar www-data www-data require '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
end representable-3.2.0/test/yaml_test.rb 0000644 0000041 0000041 00000010330 14240717435 017710 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000005377 14240717435 020774 0 ustar www-data www-data require '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
end representable-3.2.0/test/filter_test.rb 0000644 0000041 0000041 00000003763 14240717435 020247 0 ustar www-data www-data require '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"}) }
# end representable-3.2.0/test/test_helper_test.rb 0000644 0000041 0000041 00000001550 14240717435 021270 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000004275 14240717435 020562 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000021435 14240717435 020563 0 ustar www-data www-data require "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
end representable-3.2.0/test/test_helper.rb 0000644 0000041 0000041 00000006717 14240717435 020243 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000002270 14240717435 020542 0 ustar www-data www-data require "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.rb 0000644 0000041 0000041 00000005152 14240717435 022336 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000002263 14240717435 021775 0 ustar www-data www-data require '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
end representable-3.2.0/test/realistic_benchmark.rb 0000644 0000041 0000041 00000004755 14240717435 021716 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000004045 14240717435 021753 0 ustar www-data www-data require "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
end representable-3.2.0/README.md 0000644 0000041 0000041 00000011515 14240717435 015670 0 ustar www-data www-data # Representable
Representable maps Ruby objects to documents and back.

[](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.md 0000644 0000041 0000041 00000003221 14240717435 015772 0 ustar www-data www-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/.gitignore 0000644 0000041 0000041 00000000133 14240717435 016373 0 ustar www-data www-data spec/**/*.xml
rdoc
build
doc
Rake.config
pkg
.project
.bundle
Gemfile.lock
*.swp
*.swo
bin
representable-3.2.0/LICENSE 0000644 0000041 0000041 00000002102 14240717435 015406 0 ustar www-data www-data Copyright (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/Rakefile 0000644 0000041 0000041 00000000416 14240717435 016054 0 ustar www-data www-data require "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/TODO 0000644 0000041 0000041 00000002503 14240717435 015076 0 ustar www-data www-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/ 0000755 0000041 0000041 00000000000 14240717435 015154 5 ustar www-data www-data representable-3.2.0/lib/representable.rb 0000644 0000041 0000041 00000006610 14240717435 020337 0 ustar www-data www-data require "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/ 0000755 0000041 0000041 00000000000 14240717435 020007 5 ustar www-data www-data representable-3.2.0/lib/representable/version.rb 0000644 0000041 0000041 00000000055 14240717435 022021 0 ustar www-data www-data module Representable
VERSION = "3.2.0"
end
representable-3.2.0/lib/representable/xml/ 0000755 0000041 0000041 00000000000 14240717435 020607 5 ustar www-data www-data representable-3.2.0/lib/representable/xml/binding.rb 0000644 0000041 0000041 00000010653 14240717435 022553 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000010431 14240717435 023067 0 ustar www-data www-data module 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.rb 0000644 0000041 0000041 00000002003 14240717435 022052 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000001125 14240717435 023266 0 ustar www-data www-data module 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.rb 0000644 0000041 0000041 00000002252 14240717435 022317 0 ustar www-data www-data require "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.rb 0000644 0000041 0000041 00000003455 14240717435 022626 0 ustar www-data www-data require "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.rb 0000644 0000041 0000041 00000002532 14240717435 022363 0 ustar www-data www-data module 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.rb 0000644 0000041 0000041 00000007173 14240717435 021756 0 ustar www-data www-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/ 0000755 0000041 0000041 00000000000 14240717435 020760 5 ustar www-data www-data representable-3.2.0/lib/representable/json/hash.rb 0000644 0000041 0000041 00000000772 14240717435 022236 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000000376 14240717435 023446 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000001651 14240717435 023340 0 ustar www-data www-data module 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.rb 0000644 0000041 0000041 00000003403 14240717435 021134 0 ustar www-data www-data gem '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.rb 0000644 0000041 0000041 00000001362 14240717435 022137 0 ustar www-data www-data gem '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.rb 0000644 0000041 0000041 00000003314 14240717435 021260 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000000305 14240717435 022341 0 ustar www-data www-data module 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.rb 0000644 0000041 0000041 00000000736 14240717435 021551 0 ustar www-data www-data module 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/ 0000755 0000041 0000041 00000000000 14240717435 020751 5 ustar www-data www-data representable-3.2.0/lib/representable/yaml/binding.rb 0000644 0000041 0000041 00000002135 14240717435 022711 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000002745 14240717435 022151 0 ustar www-data www-data module 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.rb 0000644 0000041 0000041 00000006306 14240717435 024205 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000003520 14240717435 022505 0 ustar www-data www-data module 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.rb 0000644 0000041 0000041 00000000755 14240717435 021653 0 ustar www-data www-data require "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.rb 0000644 0000041 0000041 00000002043 14240717435 021304 0 ustar www-data www-data gem "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.rb 0000644 0000041 0000041 00000004715 14240717435 021431 0 ustar www-data www-data require '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/ 0000755 0000041 0000041 00000000000 14240717435 020732 5 ustar www-data www-data representable-3.2.0/lib/representable/hash/binding.rb 0000644 0000041 0000041 00000001175 14240717435 022675 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000000764 14240717435 024154 0 ustar www-data www-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.rb 0000644 0000041 0000041 00000002503 14240717435 023412 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000001762 14240717435 021304 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000006324 14240717435 022471 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000001212 14240717435 021576 0 ustar www-data www-data require '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/ 0000755 0000041 0000041 00000000000 14240717435 021255 5 ustar www-data www-data representable-3.2.0/lib/representable/object/binding.rb 0000644 0000041 0000041 00000001356 14240717435 023221 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000002006 14240717435 021636 0 ustar www-data www-data module 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.rb 0000644 0000041 0000041 00000002313 14240717435 021600 0 ustar www-data www-data require '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.rb 0000644 0000041 0000041 00000002304 14240717435 023001 0 ustar www-data www-data module 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.rb 0000644 0000041 0000041 00000007263 14240717435 023026 0 ustar www-data www-data module 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/Gemfile 0000644 0000041 0000041 00000000247 14240717435 015704 0 ustar www-data www-data source "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.md 0000644 0000041 0000041 00000072356 14240717435 016015 0 ustar www-data www-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/ 0000755 0000041 0000041 00000000000 14240717435 015746 5 ustar www-data www-data representable-3.2.0/.github/workflows/ 0000755 0000041 0000041 00000000000 14240717435 020003 5 ustar www-data www-data representable-3.2.0/.github/workflows/ci.yml 0000644 0000041 0000041 00000000637 14240717435 021127 0 ustar www-data www-data name: 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.gemspec 0000644 0000041 0000041 00000002623 14240717435 020611 0 ustar www-data www-data lib = 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