representable 2.4.0.rc3 → 2.4.0.rc4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +7 -2
- data/Rakefile +6 -0
- data/lib/representable.rb +21 -17
- data/lib/representable/binding.rb +3 -2
- data/lib/representable/definition.rb +1 -1
- data/lib/representable/deprecations.rb +31 -5
- data/lib/representable/deserializer.rb +24 -34
- data/lib/representable/hash/collection.rb +9 -2
- data/lib/representable/hash_methods.rb +2 -2
- data/lib/representable/parse_strategies.rb +6 -7
- data/lib/representable/pipeline.rb +12 -1
- data/lib/representable/pipeline_factories.rb +9 -2
- data/lib/representable/serializer.rb +3 -3
- data/lib/representable/version.rb +1 -1
- data/test-with-deprecations/as_test.rb +65 -0
- data/test-with-deprecations/benchmarking.rb +83 -0
- data/test-with-deprecations/binding_test.rb +46 -0
- data/test-with-deprecations/blaaaaaaaa_test.rb +69 -0
- data/test-with-deprecations/cached_test.rb +147 -0
- data/test-with-deprecations/class_test.rb +119 -0
- data/test-with-deprecations/coercion_test.rb +52 -0
- data/test-with-deprecations/config/inherit_test.rb +135 -0
- data/test-with-deprecations/config_test.rb +122 -0
- data/test-with-deprecations/decorator_scope_test.rb +28 -0
- data/test-with-deprecations/decorator_test.rb +96 -0
- data/test-with-deprecations/default_test.rb +34 -0
- data/test-with-deprecations/defaults_options_test.rb +93 -0
- data/test-with-deprecations/definition_test.rb +264 -0
- data/test-with-deprecations/example.rb +310 -0
- data/test-with-deprecations/examples/object.rb +31 -0
- data/test-with-deprecations/exec_context_test.rb +93 -0
- data/test-with-deprecations/features_test.rb +70 -0
- data/test-with-deprecations/filter_test.rb +57 -0
- data/test-with-deprecations/for_collection_test.rb +74 -0
- data/test-with-deprecations/generic_test.rb +116 -0
- data/test-with-deprecations/getter_setter_test.rb +21 -0
- data/test-with-deprecations/hash_bindings_test.rb +87 -0
- data/test-with-deprecations/hash_test.rb +160 -0
- data/test-with-deprecations/heritage_test.rb +62 -0
- data/test-with-deprecations/if_test.rb +79 -0
- data/test-with-deprecations/include_exclude_test.rb +88 -0
- data/test-with-deprecations/inherit_test.rb +159 -0
- data/test-with-deprecations/inline_test.rb +272 -0
- data/test-with-deprecations/instance_test.rb +266 -0
- data/test-with-deprecations/is_representable_test.rb +77 -0
- data/test-with-deprecations/json_test.rb +355 -0
- data/test-with-deprecations/lonely_test.rb +239 -0
- data/test-with-deprecations/mongoid_test.rb +31 -0
- data/test-with-deprecations/nested_test.rb +115 -0
- data/test-with-deprecations/object_test.rb +60 -0
- data/{test/---deserialize-pipeline_test.rb → test-with-deprecations/parse_pipeline_test.rb} +29 -2
- data/test-with-deprecations/parse_strategy_test.rb +279 -0
- data/{test → test-with-deprecations}/pass_options_test.rb +0 -0
- data/test-with-deprecations/pipeline_test.rb +277 -0
- data/test-with-deprecations/populator_test.rb +105 -0
- data/test-with-deprecations/prepare_test.rb +67 -0
- data/test-with-deprecations/private_options_test.rb +18 -0
- data/test-with-deprecations/reader_writer_test.rb +19 -0
- data/test-with-deprecations/realistic_benchmark.rb +115 -0
- data/test-with-deprecations/render_nil_test.rb +21 -0
- data/test-with-deprecations/represent_test.rb +88 -0
- data/test-with-deprecations/representable_test.rb +511 -0
- data/test-with-deprecations/schema_test.rb +148 -0
- data/test-with-deprecations/serialize_deserialize_test.rb +33 -0
- data/test-with-deprecations/skip_test.rb +81 -0
- data/test-with-deprecations/stringify_hash_test.rb +41 -0
- data/test-with-deprecations/test_helper.rb +135 -0
- data/test-with-deprecations/test_helper_test.rb +25 -0
- data/test-with-deprecations/uncategorized_test.rb +67 -0
- data/test-with-deprecations/user_options_test.rb +15 -0
- data/test-with-deprecations/wrap_test.rb +152 -0
- data/test-with-deprecations/xml_bindings_test.rb +62 -0
- data/test-with-deprecations/xml_test.rb +503 -0
- data/test-with-deprecations/yaml_test.rb +162 -0
- data/test/as_test.rb +3 -3
- data/test/cached_test.rb +2 -2
- data/test/class_test.rb +5 -5
- data/test/exec_context_test.rb +2 -2
- data/test/filter_test.rb +1 -1
- data/test/getter_setter_test.rb +4 -4
- data/test/if_test.rb +2 -2
- data/test/include_exclude_test.rb +88 -0
- data/test/instance_test.rb +15 -15
- data/test/lonely_test.rb +18 -2
- data/test/object_test.rb +4 -4
- data/test/parse_pipeline_test.rb +64 -0
- data/test/parse_strategy_test.rb +3 -3
- data/test/pipeline_test.rb +8 -12
- data/test/prepare_test.rb +2 -3
- data/test/reader_writer_test.rb +3 -3
- data/test/representable_test.rb +12 -48
- data/test/serialize_deserialize_test.rb +9 -9
- data/test/skip_test.rb +11 -11
- data/test/test_helper.rb +2 -0
- data/test/uncategorized_test.rb +10 -10
- data/test/user_options_test.rb +15 -0
- data/test/wrap_test.rb +1 -1
- metadata +65 -4
@@ -0,0 +1,18 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class PrivateOptionsTest < MiniTest::Spec # TODO: move me to separate file.
|
4
|
+
representer!(decorator: true) do
|
5
|
+
end
|
6
|
+
|
7
|
+
options = {exclude: "name"}
|
8
|
+
|
9
|
+
it "render: doesn't modify options" do
|
10
|
+
representer.new(nil).to_hash(options)
|
11
|
+
options.must_equal({exclude: "name"})
|
12
|
+
end
|
13
|
+
|
14
|
+
it "parse: doesn't modify options" do
|
15
|
+
representer.new(nil).from_hash(options)
|
16
|
+
options.must_equal({exclude: "name"})
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ReaderWriterTest < BaseTest
|
4
|
+
representer! do
|
5
|
+
property :name,
|
6
|
+
:writer => lambda { |doc, args| doc["title"] = "#{args[:nr]}) #{name}" },
|
7
|
+
:reader => lambda { |doc, args| self.name = doc["title"].split(") ").last }
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { OpenStruct.new(:name => "Disorder And Disarray").extend(representer) }
|
11
|
+
|
12
|
+
it "uses :writer when rendering" do
|
13
|
+
subject.to_hash(:nr => 14).must_equal({"title" => "14) Disorder And Disarray"})
|
14
|
+
end
|
15
|
+
|
16
|
+
it "uses :reader when parsing" do
|
17
|
+
subject.from_hash({"title" => "15) The Wars End"}).name.must_equal "The Wars End"
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
Kernel.class_eval do
|
5
|
+
# def respond_to_missing?
|
6
|
+
# raise
|
7
|
+
# end
|
8
|
+
# alias_method :orig_hash, :hash
|
9
|
+
# def hash
|
10
|
+
# puts "hash in #{self.class}"
|
11
|
+
# raise self.inspect if self.class == Symbol
|
12
|
+
# orig_hash
|
13
|
+
# end
|
14
|
+
end
|
15
|
+
Representable.deprecations = false
|
16
|
+
|
17
|
+
SONG_PROPERTIES = 50.times.collect do |i|
|
18
|
+
"song_property_#{i}"
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
module SongRepresenter
|
23
|
+
include Representable::JSON
|
24
|
+
|
25
|
+
SONG_PROPERTIES.each { |p| property p }
|
26
|
+
end
|
27
|
+
|
28
|
+
class NestedProperty < Representable::Decorator
|
29
|
+
include Representable::JSON
|
30
|
+
|
31
|
+
SONG_PROPERTIES.each { |p| property p }
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
class SongDecorator < Representable::Decorator
|
36
|
+
include Representable::JSON
|
37
|
+
|
38
|
+
SONG_PROPERTIES.each { |p| property p, extend: NestedProperty }
|
39
|
+
end
|
40
|
+
|
41
|
+
class AlbumRepresenter < Representable::Decorator
|
42
|
+
include Representable::JSON
|
43
|
+
|
44
|
+
# collection :songs, extend: SongRepresenter
|
45
|
+
collection :songs, extend: SongDecorator
|
46
|
+
end
|
47
|
+
|
48
|
+
Song = Struct.new(*SONG_PROPERTIES.map(&:to_sym))
|
49
|
+
Album = Struct.new(:songs)
|
50
|
+
|
51
|
+
def random_song
|
52
|
+
Song.new(*SONG_PROPERTIES.collect { |p| Song.new(*SONG_PROPERTIES) })
|
53
|
+
end
|
54
|
+
|
55
|
+
times = []
|
56
|
+
|
57
|
+
3.times.each do
|
58
|
+
album = Album.new(100.times.collect { random_song })
|
59
|
+
|
60
|
+
times << Benchmark.measure do
|
61
|
+
puts "================ next!"
|
62
|
+
AlbumRepresenter.new(album).to_json
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
puts times.join("")
|
67
|
+
|
68
|
+
album = Album.new(100.times.collect { random_song })
|
69
|
+
require 'ruby-prof'
|
70
|
+
RubyProf.start
|
71
|
+
AlbumRepresenter.new(album).to_hash
|
72
|
+
res = RubyProf.stop
|
73
|
+
printer = RubyProf::FlatPrinter.new(res)
|
74
|
+
printer.print(array = [])
|
75
|
+
|
76
|
+
array[0..60].each { |a| puts a }
|
77
|
+
|
78
|
+
# 100 songs, 100 attrs
|
79
|
+
# 0.050000 0.000000 0.050000 ( 0.093157)
|
80
|
+
|
81
|
+
## 100 songs, 1000 attrs
|
82
|
+
# 0.470000 0.010000 0.480000 ( 0.483708)
|
83
|
+
|
84
|
+
|
85
|
+
### without binding cache:
|
86
|
+
# 2.790000 0.030000 2.820000 ( 2.820190)
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
### with extend: on Song, with binding cache>
|
91
|
+
# 2.490000 0.030000 2.520000 ( 2.517433) 2.4-3.0
|
92
|
+
### without skip?
|
93
|
+
# 2.030000 0.020000 2.050000 ( 2.050796) 2.1-2.3
|
94
|
+
|
95
|
+
### without :writer
|
96
|
+
# 2.270000 0.010000 2.280000 ( 2.284530 1.9-2.2
|
97
|
+
### without :render_filter
|
98
|
+
# 2.020000 0.000000 2.020000 ( 2.030234) 1.5-2.0
|
99
|
+
###without default_for and skipable?
|
100
|
+
# 1.730000 0.010000 1.740000 ( 1.735597 1.4-1.7
|
101
|
+
### without :serialize
|
102
|
+
# 1.780000 0.010000 1.790000 ( 1.786791) 1.4-1.7
|
103
|
+
### using decorator
|
104
|
+
# 1.400000 0.030000 1.430000 ( 1.434206) 1.4-1.6
|
105
|
+
### with prepare AFTER representable?
|
106
|
+
# 1.330000 0.010000 1.340000 ( 1.335900) 1.1-1.3
|
107
|
+
|
108
|
+
|
109
|
+
# representable 2.0
|
110
|
+
# 3.000000 0.020000 3.020000 ( 3.013031) 2.7-3.0
|
111
|
+
|
112
|
+
# no method missing
|
113
|
+
# 2.280000 0.030000 2.310000 ( 2.313522) 2.2-2.5
|
114
|
+
# no def_delegator in Definition
|
115
|
+
# 2.130000 0.010000 2.140000 ( 2.136115) 1.7-2.1
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class RenderNilTest < MiniTest::Spec
|
4
|
+
Song = Struct.new(:title)
|
5
|
+
|
6
|
+
describe "render_nil: true" do
|
7
|
+
representer! do
|
8
|
+
property :title, render_nil: true
|
9
|
+
end
|
10
|
+
|
11
|
+
it { Song.new.extend(representer).to_hash.must_equal({"title"=>nil}) }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "with :extend it shouldn't extend nil" do
|
15
|
+
representer! do
|
16
|
+
property :title, render_nil: true, extend: Class
|
17
|
+
end
|
18
|
+
|
19
|
+
it { Song.new.extend(representer).to_hash.must_equal({"title"=>nil}) }
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class RepresentTest < MiniTest::Spec
|
4
|
+
let (:songs) { [song, Song.new("Can't Take Them All")] }
|
5
|
+
let (:song) { Song.new("Days Go By") }
|
6
|
+
|
7
|
+
for_formats(
|
8
|
+
:hash => [Representable::Hash, out=[{"name" => "Days Go By"}, {"name"=>"Can't Take Them All"}], out],
|
9
|
+
# :json => [Representable::JSON, out="[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]", out],
|
10
|
+
# :xml => [Representable::XML, out="<a><song></song><song></song></a>", out]
|
11
|
+
) do |format, mod, output, input|
|
12
|
+
|
13
|
+
# Representer.represents detects collection.
|
14
|
+
describe "Module#to_/from_#{format}" do
|
15
|
+
let (:format) { format }
|
16
|
+
|
17
|
+
let (:representer) {
|
18
|
+
Module.new do
|
19
|
+
include mod
|
20
|
+
property :name
|
21
|
+
|
22
|
+
collection_representer :class => Song # TODOOOOOOOOOOOO: test without Song and fix THIS FUCKINGNoMethodError: undefined method `name=' for {"name"=>"Days Go By"}:Hash ERROR!!!!!!!!!!!!!!!
|
23
|
+
end
|
24
|
+
}
|
25
|
+
|
26
|
+
it { render(representer.represent(songs)).must_equal_document output }
|
27
|
+
it { parse(representer.represent([]), input).must_equal songs }
|
28
|
+
end
|
29
|
+
|
30
|
+
# Decorator.represents detects collection.
|
31
|
+
describe "Decorator#to_/from_#{format}" do
|
32
|
+
let (:format) { format }
|
33
|
+
let (:representer) {
|
34
|
+
Class.new(Representable::Decorator) do
|
35
|
+
include mod
|
36
|
+
property :name
|
37
|
+
|
38
|
+
collection_representer :class => Song
|
39
|
+
end
|
40
|
+
}
|
41
|
+
|
42
|
+
it { render(representer.represent(songs)).must_equal_document output }
|
43
|
+
it("ficken") { parse(representer.represent([]), input).must_equal songs }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
for_formats(
|
49
|
+
:hash => [Representable::Hash, out={"name" => "Days Go By"}, out],
|
50
|
+
:json => [Representable::JSON, out="{\"name\":\"Days Go By\"}", out],
|
51
|
+
# :xml => [Representable::XML, out="<a><song></song><song></song></a>", out]
|
52
|
+
) do |format, mod, output, input|
|
53
|
+
|
54
|
+
# Representer.represents detects singular.
|
55
|
+
describe "Module#to_/from_#{format}" do
|
56
|
+
let (:format) { format }
|
57
|
+
|
58
|
+
let (:representer) {
|
59
|
+
Module.new do
|
60
|
+
include mod
|
61
|
+
property :name
|
62
|
+
|
63
|
+
collection_representer :class => Song
|
64
|
+
end
|
65
|
+
}
|
66
|
+
|
67
|
+
it { render(representer.represent(song)).must_equal_document output }
|
68
|
+
it { parse(representer.represent(Song.new), input).must_equal song }
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
# Decorator.represents detects singular.
|
73
|
+
describe "Decorator#to_/from_#{format}" do
|
74
|
+
let (:format) { format }
|
75
|
+
let (:representer) {
|
76
|
+
Class.new(Representable::Decorator) do
|
77
|
+
include mod
|
78
|
+
property :name
|
79
|
+
|
80
|
+
collection_representer :class => Song
|
81
|
+
end
|
82
|
+
}
|
83
|
+
|
84
|
+
it { render(representer.represent(song)).must_equal_document output }
|
85
|
+
it { parse(representer.represent(Song.new), input).must_equal song }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,511 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class RepresentableTest < MiniTest::Spec
|
4
|
+
class Band
|
5
|
+
include Representable::Hash
|
6
|
+
property :name
|
7
|
+
attr_accessor :name
|
8
|
+
end
|
9
|
+
|
10
|
+
class PunkBand < Band
|
11
|
+
property :street_cred
|
12
|
+
attr_accessor :street_cred
|
13
|
+
end
|
14
|
+
|
15
|
+
module BandRepresentation
|
16
|
+
include Representable
|
17
|
+
|
18
|
+
property :name
|
19
|
+
end
|
20
|
+
|
21
|
+
module PunkBandRepresentation
|
22
|
+
include Representable
|
23
|
+
include BandRepresentation
|
24
|
+
|
25
|
+
property :street_cred
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
describe "#representable_attrs" do
|
30
|
+
describe "in module" do
|
31
|
+
it "allows including the concrete representer module later" do
|
32
|
+
vd = class VD
|
33
|
+
attr_accessor :name, :street_cred
|
34
|
+
include Representable::JSON
|
35
|
+
include PunkBandRepresentation
|
36
|
+
end.new
|
37
|
+
vd.name = "Vention Dention"
|
38
|
+
vd.street_cred = 1
|
39
|
+
assert_json "{\"name\":\"Vention Dention\",\"street_cred\":1}", vd.to_json
|
40
|
+
end
|
41
|
+
|
42
|
+
#it "allows including the concrete representer module only" do
|
43
|
+
# require 'representable/json'
|
44
|
+
# module RockBandRepresentation
|
45
|
+
# include Representable::JSON
|
46
|
+
# property :name
|
47
|
+
# end
|
48
|
+
# vd = class VH
|
49
|
+
# include RockBandRepresentation
|
50
|
+
# end.new
|
51
|
+
# vd.name = "Van Halen"
|
52
|
+
# assert_equal "{\"name\":\"Van Halen\"}", vd.to_json
|
53
|
+
#end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
describe "inheritance" do
|
59
|
+
class CoverSong < OpenStruct
|
60
|
+
end
|
61
|
+
|
62
|
+
module SongRepresenter
|
63
|
+
include Representable::Hash
|
64
|
+
property :name
|
65
|
+
end
|
66
|
+
|
67
|
+
module CoverSongRepresenter
|
68
|
+
include Representable::Hash
|
69
|
+
include SongRepresenter
|
70
|
+
property :by
|
71
|
+
end
|
72
|
+
|
73
|
+
it "merges properties from all ancestors" do
|
74
|
+
props = {"name"=>"The Brews", "by"=>"Nofx"}
|
75
|
+
assert_equal(props, CoverSong.new(props).extend(CoverSongRepresenter).to_hash)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "allows mixing in multiple representers" do
|
79
|
+
require 'representable/json'
|
80
|
+
require 'representable/xml'
|
81
|
+
class Bodyjar
|
82
|
+
include Representable::XML
|
83
|
+
include Representable::JSON
|
84
|
+
include PunkBandRepresentation
|
85
|
+
|
86
|
+
self.representation_wrap = "band"
|
87
|
+
attr_accessor :name, :street_cred
|
88
|
+
end
|
89
|
+
|
90
|
+
band = Bodyjar.new
|
91
|
+
band.name = "Bodyjar"
|
92
|
+
|
93
|
+
assert_json "{\"band\":{\"name\":\"Bodyjar\"}}", band.to_json
|
94
|
+
assert_xml_equal "<band><name>Bodyjar</name></band>", band.to_xml
|
95
|
+
end
|
96
|
+
|
97
|
+
it "allows extending with different representers subsequentially" do
|
98
|
+
module SongXmlRepresenter
|
99
|
+
include Representable::XML
|
100
|
+
property :name, :as => "name", :attribute => true
|
101
|
+
end
|
102
|
+
|
103
|
+
module SongJsonRepresenter
|
104
|
+
include Representable::JSON
|
105
|
+
property :name
|
106
|
+
end
|
107
|
+
|
108
|
+
@song = Song.new("Days Go By")
|
109
|
+
assert_xml_equal "<song name=\"Days Go By\"/>", @song.extend(SongXmlRepresenter).to_xml
|
110
|
+
assert_json "{\"name\":\"Days Go By\"}", @song.extend(SongJsonRepresenter).to_json
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# test if we call super in
|
115
|
+
# ::inherited
|
116
|
+
# ::included
|
117
|
+
# ::extended
|
118
|
+
module Representer
|
119
|
+
include Representable # overrides ::inherited.
|
120
|
+
end
|
121
|
+
|
122
|
+
class BaseClass
|
123
|
+
def self.inherited(subclass)
|
124
|
+
super
|
125
|
+
subclass.instance_eval { def other; end }
|
126
|
+
end
|
127
|
+
|
128
|
+
include Representable # overrides ::inherited.
|
129
|
+
include Representer
|
130
|
+
end
|
131
|
+
|
132
|
+
class SubClass < BaseClass # triggers Representable::inherited, then OtherModule::inherited.
|
133
|
+
end
|
134
|
+
|
135
|
+
# test ::inherited.
|
136
|
+
it do
|
137
|
+
BaseClass.respond_to?(:other).must_equal false
|
138
|
+
SubClass.respond_to?(:other).must_equal true
|
139
|
+
end
|
140
|
+
|
141
|
+
module DifferentIncluded
|
142
|
+
def included(includer)
|
143
|
+
includer.instance_eval { def different; end }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
module CombinedIncluded
|
148
|
+
extend DifferentIncluded # defines ::included.
|
149
|
+
include Representable # overrides ::included.
|
150
|
+
end
|
151
|
+
|
152
|
+
class IncludingClass
|
153
|
+
include Representable
|
154
|
+
include CombinedIncluded
|
155
|
+
end
|
156
|
+
|
157
|
+
# test ::included.
|
158
|
+
it do
|
159
|
+
IncludingClass.respond_to?(:representable_attrs) # from Representable
|
160
|
+
IncludingClass.respond_to?(:different)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
describe "#property" do
|
166
|
+
it "doesn't modify options hash" do
|
167
|
+
options = {}
|
168
|
+
representer.property(:title, options)
|
169
|
+
options.must_equal({})
|
170
|
+
end
|
171
|
+
|
172
|
+
representer! {}
|
173
|
+
|
174
|
+
it "returns the Definition instance" do
|
175
|
+
representer.property(:name).must_be_kind_of Representable::Definition
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "#collection" do
|
180
|
+
class RockBand < Band
|
181
|
+
collection :albums
|
182
|
+
end
|
183
|
+
|
184
|
+
it "creates correct Definition" do
|
185
|
+
assert_equal "albums", RockBand.representable_attrs.get(:albums).name
|
186
|
+
assert RockBand.representable_attrs.get(:albums).array?
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "#hash" do
|
191
|
+
it "also responds to the original method" do
|
192
|
+
assert_kind_of Integer, BandRepresentation.hash
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
class Hometown
|
197
|
+
attr_accessor :name
|
198
|
+
end
|
199
|
+
|
200
|
+
module HometownRepresentable
|
201
|
+
include Representable::JSON
|
202
|
+
property :name
|
203
|
+
end
|
204
|
+
|
205
|
+
# DISCUSS: i don't like the JSON requirement here, what about some generic test module?
|
206
|
+
class PopBand
|
207
|
+
include Representable::JSON
|
208
|
+
property :name
|
209
|
+
property :groupies
|
210
|
+
property :hometown, class: Hometown, extend: HometownRepresentable
|
211
|
+
attr_accessor :name, :groupies, :hometown
|
212
|
+
end
|
213
|
+
|
214
|
+
describe "#update_properties_from" do
|
215
|
+
before do
|
216
|
+
@band = PopBand.new
|
217
|
+
end
|
218
|
+
|
219
|
+
it "copies values from document to object" do
|
220
|
+
@band.from_hash({"name"=>"No One's Choice", "groupies"=>2})
|
221
|
+
assert_equal "No One's Choice", @band.name
|
222
|
+
assert_equal 2, @band.groupies
|
223
|
+
end
|
224
|
+
|
225
|
+
it "ignores non-writeable properties" do
|
226
|
+
@band = Class.new(Band) { property :name; collection :founders, :writeable => false; attr_accessor :founders }.new
|
227
|
+
@band.from_hash("name" => "Iron Maiden", "groupies" => 2, "founders" => ["Steve Harris"])
|
228
|
+
assert_equal "Iron Maiden", @band.name
|
229
|
+
assert_equal nil, @band.founders
|
230
|
+
end
|
231
|
+
|
232
|
+
it "always returns the represented" do
|
233
|
+
assert_equal @band, @band.from_hash({"name"=>"Nofx"})
|
234
|
+
end
|
235
|
+
|
236
|
+
it "includes false attributes" do
|
237
|
+
@band.from_hash({"groupies"=>false})
|
238
|
+
assert_equal false, @band.groupies
|
239
|
+
end
|
240
|
+
|
241
|
+
it "ignores properties not present in the incoming document" do
|
242
|
+
@band.instance_eval do
|
243
|
+
def name=(*); raise "I should never be called!"; end
|
244
|
+
end
|
245
|
+
@band.from_hash({})
|
246
|
+
end
|
247
|
+
|
248
|
+
|
249
|
+
# FIXME: do we need this test with XML _and_ JSON?
|
250
|
+
it "ignores (no-default) properties not present in the incoming document" do
|
251
|
+
{ Representable::Hash => [:from_hash, {}],
|
252
|
+
Representable::XML => [:from_xml, xml(%{<band/>}).to_s]
|
253
|
+
}.each do |format, config|
|
254
|
+
nested_repr = Module.new do # this module is never applied. # FIXME: can we make that a simpler test?
|
255
|
+
include format
|
256
|
+
property :created_at
|
257
|
+
end
|
258
|
+
|
259
|
+
repr = Module.new do
|
260
|
+
include format
|
261
|
+
property :name, :class => Object, :extend => nested_repr
|
262
|
+
end
|
263
|
+
|
264
|
+
@band = Band.new.extend(repr)
|
265
|
+
@band.send(config.first, config.last)
|
266
|
+
assert_equal nil, @band.name, "Failed in #{format}"
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe "passing options" do
|
271
|
+
module TrackRepresenter
|
272
|
+
include Representable::Hash
|
273
|
+
|
274
|
+
end
|
275
|
+
|
276
|
+
representer! do
|
277
|
+
property :track, class: OpenStruct do
|
278
|
+
property :nr
|
279
|
+
|
280
|
+
property :length, class: OpenStruct do
|
281
|
+
def to_hash(options)
|
282
|
+
{seconds: options[:user_options][:nr]}
|
283
|
+
end
|
284
|
+
|
285
|
+
def from_hash(hash, options)
|
286
|
+
super.tap do
|
287
|
+
self.seconds = options[:user_options][:nr]
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def to_hash(options)
|
293
|
+
super.merge({"nr" => options[:user_options][:nr]})
|
294
|
+
end
|
295
|
+
|
296
|
+
def from_hash(data, options)
|
297
|
+
super.tap do
|
298
|
+
self.nr = options[:user_options][:nr]
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
it "#to_hash propagates to nested objects" do
|
305
|
+
OpenStruct.new(track: OpenStruct.new(nr: 1, length: OpenStruct.new(seconds: nil))).extend(representer).extend(Representable::Debug).
|
306
|
+
to_hash(nr: 9).must_equal({"track"=>{"nr"=>9, "length"=>{seconds: 9}}})
|
307
|
+
end
|
308
|
+
|
309
|
+
it "#from_hash propagates to nested objects" do
|
310
|
+
song = OpenStruct.new.extend(representer).from_hash({"track"=>{"nr" => "replace me", "length"=>{"seconds"=>"replacing"}}}, :nr => 9)
|
311
|
+
song.track.nr.must_equal 9
|
312
|
+
song.track.length.seconds.must_equal 9
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
describe "#create_representation_with" do
|
318
|
+
before do
|
319
|
+
@band = PopBand.new
|
320
|
+
@band.name = "No One's Choice"
|
321
|
+
@band.groupies = 2
|
322
|
+
end
|
323
|
+
|
324
|
+
it "compiles document from properties in object" do
|
325
|
+
assert_equal({"name"=>"No One's Choice", "groupies"=>2}, @band.to_hash)
|
326
|
+
end
|
327
|
+
|
328
|
+
it "ignores non-readable properties" do
|
329
|
+
@band = Class.new(Band) { property :name; collection :founder_ids, :readable => false; attr_accessor :founder_ids }.new
|
330
|
+
@band.name = "Iron Maiden"
|
331
|
+
@band.founder_ids = [1,2,3]
|
332
|
+
|
333
|
+
hash = @band.to_hash
|
334
|
+
assert_equal({"name" => "Iron Maiden"}, hash)
|
335
|
+
end
|
336
|
+
|
337
|
+
it "does not write nil attributes" do
|
338
|
+
@band.groupies = nil
|
339
|
+
assert_equal({"name"=>"No One's Choice"}, @band.to_hash)
|
340
|
+
end
|
341
|
+
|
342
|
+
it "writes false attributes" do
|
343
|
+
@band.groupies = false
|
344
|
+
assert_equal({"name"=>"No One's Choice","groupies"=>false}, @band.to_hash)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
|
349
|
+
describe ":extend and :class" do
|
350
|
+
module UpcaseRepresenter
|
351
|
+
include Representable
|
352
|
+
def to_hash(*); upcase; end
|
353
|
+
def from_hash(hsh, *args); replace hsh.upcase; end # DISCUSS: from_hash must return self.
|
354
|
+
end
|
355
|
+
module DowncaseRepresenter
|
356
|
+
include Representable
|
357
|
+
def to_hash(*); downcase; end
|
358
|
+
def from_hash(hsh, *args); replace hsh.downcase; end
|
359
|
+
end
|
360
|
+
class UpcaseString < String; end
|
361
|
+
|
362
|
+
|
363
|
+
describe "lambda blocks" do
|
364
|
+
representer! do
|
365
|
+
property :name, :extend => lambda { |name, *| compute_representer(name) }
|
366
|
+
end
|
367
|
+
|
368
|
+
it "executes lambda in represented instance context" do
|
369
|
+
Song.new("Carnage").instance_eval do
|
370
|
+
def compute_representer(name)
|
371
|
+
UpcaseRepresenter
|
372
|
+
end
|
373
|
+
self
|
374
|
+
end.extend(representer).to_hash.must_equal({"name" => "CARNAGE"})
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
describe ":instance" do
|
379
|
+
obj = String.new("Fate")
|
380
|
+
mod = Module.new { include Representable; def from_hash(*); self; end }
|
381
|
+
representer! do
|
382
|
+
property :name, :extend => mod, :instance => lambda { |*| obj }
|
383
|
+
end
|
384
|
+
|
385
|
+
it "uses object from :instance but still extends it" do
|
386
|
+
song = Song.new.extend(representer).from_hash("name" => "Eric's Had A Bad Day")
|
387
|
+
song.name.must_equal obj
|
388
|
+
song.name.must_be_kind_of mod
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
describe "property with :extend" do
|
393
|
+
representer! do
|
394
|
+
property :name, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String
|
395
|
+
end
|
396
|
+
|
397
|
+
it "uses lambda when rendering" do
|
398
|
+
assert_equal({"name" => "you make me thick"}, Song.new("You Make Me Thick").extend(representer).to_hash )
|
399
|
+
assert_equal({"name" => "STEPSTRANGER"}, Song.new(UpcaseString.new "Stepstranger").extend(representer).to_hash )
|
400
|
+
end
|
401
|
+
|
402
|
+
it "uses lambda when parsing" do
|
403
|
+
Song.new.extend(representer).from_hash({"name" => "You Make Me Thick"}).name.must_equal "you make me thick"
|
404
|
+
Song.new.extend(representer).from_hash({"name" => "Stepstranger"}).name.must_equal "stepstranger" # DISCUSS: we compare "".is_a?(UpcaseString)
|
405
|
+
end
|
406
|
+
|
407
|
+
describe "with :class lambda" do
|
408
|
+
representer! do
|
409
|
+
property :name, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter },
|
410
|
+
:class => lambda { |fragment, *| fragment == "Still Failing?" ? String : UpcaseString }
|
411
|
+
end
|
412
|
+
|
413
|
+
it "creates instance from :class lambda when parsing" do
|
414
|
+
song = OpenStruct.new.extend(representer).from_hash({"name" => "Quitters Never Win"})
|
415
|
+
song.name.must_be_kind_of UpcaseString
|
416
|
+
song.name.must_equal "QUITTERS NEVER WIN"
|
417
|
+
|
418
|
+
song = OpenStruct.new.extend(representer).from_hash({"name" => "Still Failing?"})
|
419
|
+
song.name.must_be_kind_of String
|
420
|
+
song.name.must_equal "still failing?"
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
|
426
|
+
describe "collection with :extend" do
|
427
|
+
representer! do
|
428
|
+
collection :songs, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String
|
429
|
+
end
|
430
|
+
|
431
|
+
it "uses lambda for each item when rendering" do
|
432
|
+
Album.new([UpcaseString.new("Dean Martin"), "Charlie Still Smirks"]).extend(representer).to_hash.must_equal("songs"=>["DEAN MARTIN", "charlie still smirks"])
|
433
|
+
end
|
434
|
+
|
435
|
+
it "uses lambda for each item when parsing" do
|
436
|
+
album = Album.new.extend(representer).from_hash("songs"=>["DEAN MARTIN", "charlie still smirks"])
|
437
|
+
album.songs.must_equal ["dean martin", "charlie still smirks"] # DISCUSS: we compare "".is_a?(UpcaseString)
|
438
|
+
end
|
439
|
+
|
440
|
+
describe "with :class lambda" do
|
441
|
+
representer! do
|
442
|
+
collection :songs, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter },
|
443
|
+
:class => lambda { |fragment, *| fragment == "Still Failing?" ? String : UpcaseString }
|
444
|
+
end
|
445
|
+
|
446
|
+
it "creates instance from :class lambda for each item when parsing" do
|
447
|
+
album = Album.new.extend(representer).from_hash("songs"=>["Still Failing?", "charlie still smirks"])
|
448
|
+
album.songs.must_equal ["still failing?", "CHARLIE STILL SMIRKS"]
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
describe ":decorator" do
|
454
|
+
let (:extend_rpr) { Module.new { include Representable::Hash; collection :songs, :extend => SongRepresenter } }
|
455
|
+
let (:decorator_rpr) { Module.new { include Representable::Hash; collection :songs, :decorator => SongRepresenter } }
|
456
|
+
let (:songs) { [Song.new("Bloody Mary")] }
|
457
|
+
|
458
|
+
it "is aliased to :extend" do
|
459
|
+
Album.new(songs).extend(extend_rpr).to_hash.must_equal Album.new(songs).extend(decorator_rpr).to_hash
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
describe ":binding" do
|
464
|
+
representer! do
|
465
|
+
class MyBinding < Representable::Binding
|
466
|
+
def write(doc, *args)
|
467
|
+
doc[:title] = represented.title
|
468
|
+
end
|
469
|
+
end
|
470
|
+
property :title, :binding => lambda { |*args| MyBinding.new(*args) }
|
471
|
+
end
|
472
|
+
|
473
|
+
it "uses the specified binding instance" do
|
474
|
+
OpenStruct.new(:title => "Affliction").extend(representer).to_hash.must_equal({:title => "Affliction"})
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
|
479
|
+
# TODO: Move to global place since it's used twice.
|
480
|
+
class SongRepresentation < Representable::Decorator
|
481
|
+
include Representable::JSON
|
482
|
+
property :name
|
483
|
+
end
|
484
|
+
class AlbumRepresentation < Representable::Decorator
|
485
|
+
include Representable::JSON
|
486
|
+
|
487
|
+
collection :songs, :class => Song, :extend => SongRepresentation
|
488
|
+
end
|
489
|
+
|
490
|
+
describe "::prepare" do
|
491
|
+
let (:song) { Song.new("Still Friends In The End") }
|
492
|
+
let (:album) { Album.new([song]) }
|
493
|
+
|
494
|
+
describe "module including Representable" do
|
495
|
+
it "uses :extend strategy" do
|
496
|
+
album_rpr = Module.new { include Representable::Hash; collection :songs, :class => Song, :extend => SongRepresenter}
|
497
|
+
|
498
|
+
album_rpr.prepare(album).to_hash.must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]})
|
499
|
+
album.must_respond_to :to_hash
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
describe "Decorator subclass" do
|
504
|
+
it "uses :decorate strategy" do
|
505
|
+
AlbumRepresentation.prepare(album).to_hash.must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]})
|
506
|
+
album.wont_respond_to :to_hash
|
507
|
+
end
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|
511
|
+
end
|