representable 1.8.5 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGES.md +36 -0
- data/Gemfile +3 -3
- data/README.md +62 -2
- data/Rakefile +1 -1
- data/lib/representable.rb +32 -109
- data/lib/representable/TODO.getting_serious +7 -1
- data/lib/representable/autoload.rb +10 -0
- data/lib/representable/binding.rb +20 -16
- data/lib/representable/bindings/xml_bindings.rb +0 -1
- data/lib/representable/coercion.rb +23 -31
- data/lib/representable/config.rb +45 -46
- data/lib/representable/declarative.rb +78 -0
- data/lib/representable/decorator.rb +39 -10
- data/lib/representable/definition.rb +40 -33
- data/lib/representable/deserializer.rb +2 -0
- data/lib/representable/for_collection.rb +25 -0
- data/lib/representable/hash.rb +4 -8
- data/lib/representable/hash/collection.rb +2 -9
- data/lib/representable/hash_methods.rb +0 -7
- data/lib/representable/inheritable.rb +50 -0
- data/lib/representable/json.rb +3 -9
- data/lib/representable/json/collection.rb +1 -3
- data/lib/representable/json/hash.rb +4 -9
- data/lib/representable/mapper.rb +8 -5
- data/lib/representable/parse_strategies.rb +1 -0
- data/lib/representable/pipeline.rb +14 -0
- data/lib/representable/represent.rb +6 -0
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +3 -18
- data/lib/representable/xml/collection.rb +2 -4
- data/lib/representable/xml/hash.rb +2 -10
- data/lib/representable/yaml.rb +1 -20
- data/representable.gemspec +2 -2
- data/test/class_test.rb +5 -10
- data/test/coercion_test.rb +31 -92
- data/test/config/inherit_test.rb +128 -0
- data/test/config_test.rb +114 -80
- data/test/definition_test.rb +107 -64
- data/test/features_test.rb +41 -0
- data/test/filter_test.rb +59 -0
- data/test/for_collection_test.rb +74 -0
- data/test/inherit_test.rb +44 -3
- data/test/inheritable_test.rb +97 -0
- data/test/inline_test.rb +0 -18
- data/test/instance_test.rb +0 -19
- data/test/json_test.rb +9 -44
- data/test/lonely_test.rb +1 -0
- data/test/parse_strategy_test.rb +30 -0
- data/test/represent_test.rb +88 -0
- data/test/representable_test.rb +3 -50
- data/test/schema_test.rb +123 -0
- data/test/test_helper.rb +1 -1
- data/test/xml_test.rb +34 -38
- metadata +25 -15
- data/lib/representable/decorator/coercion.rb +0 -4
- data/lib/representable/readable_writeable.rb +0 -29
- data/test/inheritance_test.rb +0 -22
data/test/coercion_test.rb
CHANGED
@@ -1,113 +1,52 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
require 'representable/coercion'
|
3
|
-
require 'representable/decorator/coercion'
|
4
3
|
|
5
4
|
class VirtusCoercionTest < MiniTest::Spec
|
6
|
-
|
7
|
-
|
8
|
-
end
|
5
|
+
representer! do
|
6
|
+
include Representable::Coercion
|
9
7
|
|
10
|
-
|
8
|
+
property :title # no coercion.
|
9
|
+
property :length, :type => Float
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
include Representable::JSON
|
15
|
-
include Representable::Coercion
|
16
|
-
property :composed_at, :type => DateTime
|
17
|
-
property :track, :type => Integer
|
18
|
-
property :title # no coercion.
|
11
|
+
property :band, :class => OpenStruct do
|
12
|
+
property :founded, :type => Integer
|
19
13
|
end
|
20
14
|
|
21
|
-
|
22
|
-
|
23
|
-
song.composed_at.must_equal date
|
24
|
-
song.track.must_equal 18
|
25
|
-
song.title.must_equal "Scarified"
|
15
|
+
collection :songs, :class => OpenStruct do
|
16
|
+
property :ok, :type => Virtus::Attribute::Boolean
|
26
17
|
end
|
27
|
-
|
28
|
-
it "coerces when rendering" do
|
29
|
-
song = Song.new.extend(SongRepresenter)
|
30
|
-
song.title = "Scarified"
|
31
|
-
song.composed_at = "Fri, 18 Nov 1983"
|
32
|
-
|
33
|
-
song.to_hash.must_equal({"title" => "Scarified", "composed_at" => date})
|
34
|
-
end
|
35
18
|
end
|
36
19
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
include Representable::Coercion
|
20
|
+
let (:album) { OpenStruct.new(:title => "Dire Straits", :length => 41.34,
|
21
|
+
:band => OpenStruct.new(:founded => "1977"),
|
22
|
+
:songs => [OpenStruct.new(:ok => 1), OpenStruct.new(:ok => 0)]) }
|
41
23
|
|
42
|
-
|
43
|
-
property :track, :type => Integer
|
24
|
+
it { album.extend(representer).to_hash.must_equal({"title"=>"Dire Straits", "length"=>41.34, "band"=>{"founded"=>1977}, "songs"=>[{"ok"=>true}, {"ok"=>false}]}) }
|
44
25
|
|
45
|
-
|
46
|
-
|
26
|
+
it {
|
27
|
+
album = OpenStruct.new
|
28
|
+
album.extend(representer)
|
29
|
+
album.from_hash({"title"=>"Dire Straits", "length"=>"41.34", "band"=>{"founded"=>"1977"}, "songs"=>[{"ok"=>1}, {"ok"=>0}]})
|
47
30
|
|
48
|
-
it
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
31
|
+
# it
|
32
|
+
album.length.must_equal 41.34
|
33
|
+
album.band.founded.must_equal 1977
|
34
|
+
album.songs[0].ok.must_equal true
|
35
|
+
}
|
53
36
|
|
54
|
-
it "respects the :default options" do
|
55
|
-
song = ImmigrantSong.new.from_json("{}")
|
56
|
-
song.composed_at.must_equal DateTime.parse("Mon, 12 May 2012 00:00:00 +0000")
|
57
|
-
end
|
58
|
-
end
|
59
37
|
|
60
|
-
describe "
|
61
|
-
|
62
|
-
include Representable::JSON
|
38
|
+
describe "with user :parse_filter and :render_filter" do
|
39
|
+
representer! do
|
63
40
|
include Representable::Coercion
|
64
41
|
|
65
|
-
property :
|
66
|
-
|
42
|
+
property :length, :type => Float,
|
43
|
+
:parse_filter => lambda { |fragment, doc, options| "#{fragment}.1" }, # happens BEFORE coercer.
|
44
|
+
:render_filter => lambda { |fragment, doc, options| "#{fragment}.1" }
|
67
45
|
end
|
68
46
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
song.title.must_equal "Scarified"
|
74
|
-
end
|
75
|
-
|
76
|
-
it "coerses with inherited decorator" do
|
77
|
-
song = Class.new(SongRepresentation).new(OpenStruct.new).from_json("{\"composed_at\":\"November 18th, 1983\", \"title\": \"Scarified\"}")
|
78
|
-
song.composed_at.must_equal date
|
79
|
-
end
|
80
|
-
|
81
|
-
it "coerces when rendering" do
|
82
|
-
SongRepresentation.new(
|
83
|
-
OpenStruct.new(
|
84
|
-
:composed_at => "November 18th, 1983",
|
85
|
-
:title => "Scarified"
|
86
|
-
)
|
87
|
-
).to_hash.must_equal({"composed_at"=>date, "title"=>"Scarified"})
|
88
|
-
end
|
47
|
+
# user's :parse_filter(s) are run before coercion.
|
48
|
+
it { OpenStruct.new.extend(representer).from_hash("length"=>"1").length.must_equal 1.1 }
|
49
|
+
# user's :render_filter(s) are run before coercion.
|
50
|
+
it { OpenStruct.new(:length=>1).extend(representer).to_hash.must_equal({"length" => 1.1}) }
|
89
51
|
end
|
90
|
-
|
91
|
-
# DISCUSS: do we actually wanna have accessors in a decorator/module? i guess the better idea is to let coercion happen through from_/to_,
|
92
|
-
# only, to make it as simple as possible.
|
93
|
-
# describe "without serialization/deserialization" do
|
94
|
-
# let (:coercer_class) do
|
95
|
-
# class SongCoercer < Representable::Decorator
|
96
|
-
# include Representable::Decorator::Coercion
|
97
|
-
|
98
|
-
# property :composed_at, :type => DateTime
|
99
|
-
# property :title
|
100
|
-
|
101
|
-
# self
|
102
|
-
# end
|
103
|
-
# end
|
104
|
-
|
105
|
-
# it "coerces when setting" do
|
106
|
-
# coercer = coercer_class.new(song = OpenStruct.new)
|
107
|
-
# coercer.composed_at = "November 18th, 1983"
|
108
|
-
# #coercer.title = "Scarified"
|
109
|
-
|
110
|
-
# song.composed_at.must_equal date
|
111
|
-
# end
|
112
|
-
# end
|
113
|
-
end
|
52
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
# tests defining representers in modules, decorators and classes and the inheritance when combined.
|
4
|
+
|
5
|
+
class ConfigInheritTest < MiniTest::Spec
|
6
|
+
def assert_cloned(child, parent, property)
|
7
|
+
child_def = child.representable_attrs.get(property)
|
8
|
+
parent_def = parent.representable_attrs.get(property)
|
9
|
+
|
10
|
+
child_def.merge!(:alias => property)
|
11
|
+
|
12
|
+
child_def[:alias].wont_equal parent_def[:alias]
|
13
|
+
child_def.object_id.wont_equal parent_def.object_id
|
14
|
+
end
|
15
|
+
# class Object
|
16
|
+
|
17
|
+
# end
|
18
|
+
module GenreModule
|
19
|
+
include Representable
|
20
|
+
property :genre
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
# in Decorator ------------------------------------------------
|
25
|
+
class Decorator < Representable::Decorator
|
26
|
+
property :title
|
27
|
+
end
|
28
|
+
|
29
|
+
it { Decorator.representable_attrs[:definitions].keys.must_equal ["title"] }
|
30
|
+
|
31
|
+
# in inheriting Decorator
|
32
|
+
|
33
|
+
class InheritingDecorator < Decorator
|
34
|
+
property :location
|
35
|
+
end
|
36
|
+
|
37
|
+
it { InheritingDecorator.representable_attrs[:definitions].keys.must_equal ["title", "location"] }
|
38
|
+
it { assert_cloned(InheritingDecorator, Decorator, "title") }
|
39
|
+
|
40
|
+
# in inheriting and including Decorator
|
41
|
+
|
42
|
+
class InheritingAndIncludingDecorator < Decorator
|
43
|
+
include GenreModule
|
44
|
+
property :location
|
45
|
+
end
|
46
|
+
|
47
|
+
it { InheritingAndIncludingDecorator.representable_attrs[:definitions].keys.must_equal ["title", "genre", "location"] }
|
48
|
+
it { assert_cloned(InheritingAndIncludingDecorator, GenreModule, :genre) }
|
49
|
+
|
50
|
+
|
51
|
+
# in module ---------------------------------------------------
|
52
|
+
module Module
|
53
|
+
include Representable
|
54
|
+
property :title
|
55
|
+
end
|
56
|
+
|
57
|
+
it { Module.representable_attrs[:definitions].keys.must_equal ["title"] }
|
58
|
+
|
59
|
+
|
60
|
+
# in module including module
|
61
|
+
module SubModule
|
62
|
+
include Representable
|
63
|
+
include Module
|
64
|
+
|
65
|
+
property :location
|
66
|
+
end
|
67
|
+
|
68
|
+
it { SubModule.representable_attrs[:definitions].keys.must_equal ["title", "location"] }
|
69
|
+
it { assert_cloned(SubModule, Module, :title) }
|
70
|
+
|
71
|
+
# including preserves order
|
72
|
+
module IncludingModule
|
73
|
+
include Representable
|
74
|
+
property :genre
|
75
|
+
include Module
|
76
|
+
|
77
|
+
property :location
|
78
|
+
end
|
79
|
+
|
80
|
+
it { IncludingModule.representable_attrs[:definitions].keys.must_equal ["genre", "title", "location"] }
|
81
|
+
|
82
|
+
|
83
|
+
# included in class -------------------------------------------
|
84
|
+
class Class
|
85
|
+
include Representable
|
86
|
+
include IncludingModule
|
87
|
+
end
|
88
|
+
|
89
|
+
it { Class.representable_attrs[:definitions].keys.must_equal ["genre", "title", "location"] }
|
90
|
+
it { assert_cloned(Class, IncludingModule, :title) }
|
91
|
+
it { assert_cloned(Class, IncludingModule, :location) }
|
92
|
+
it { assert_cloned(Class, IncludingModule, :genre) }
|
93
|
+
|
94
|
+
# included in class with order
|
95
|
+
class DefiningClass
|
96
|
+
include Representable
|
97
|
+
property :street_cred
|
98
|
+
include IncludingModule
|
99
|
+
end
|
100
|
+
|
101
|
+
it { DefiningClass.representable_attrs[:definitions].keys.must_equal ["street_cred", "genre", "title", "location"] }
|
102
|
+
|
103
|
+
# in class
|
104
|
+
class RepresenterClass
|
105
|
+
include Representable
|
106
|
+
property :title
|
107
|
+
end
|
108
|
+
|
109
|
+
it { RepresenterClass.representable_attrs[:definitions].keys.must_equal ["title"] }
|
110
|
+
|
111
|
+
|
112
|
+
# in inheriting class
|
113
|
+
class InheritingClass < RepresenterClass
|
114
|
+
include Representable
|
115
|
+
property :location
|
116
|
+
end
|
117
|
+
|
118
|
+
it { InheritingClass.representable_attrs[:definitions].keys.must_equal ["title", "location"] }
|
119
|
+
it { assert_cloned(InheritingClass, RepresenterClass, :title) }
|
120
|
+
|
121
|
+
# in inheriting class and including
|
122
|
+
class InheritingAndIncludingClass < RepresenterClass
|
123
|
+
property :location
|
124
|
+
include GenreModule
|
125
|
+
end
|
126
|
+
|
127
|
+
it { InheritingAndIncludingClass.representable_attrs[:definitions].keys.must_equal ["title", "location", "genre"] }
|
128
|
+
end
|
data/test/config_test.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
# tested feature: ::property
|
4
|
-
|
5
3
|
class ConfigTest < MiniTest::Spec
|
6
4
|
subject { Representable::Config.new }
|
7
5
|
PunkRock = Class.new
|
8
|
-
|
9
|
-
let (:definition) { Representable::Definition.new(:title) }
|
6
|
+
Definition = Representable::Definition
|
10
7
|
|
11
8
|
describe "wrapping" do
|
12
9
|
it "returns false per default" do
|
@@ -24,35 +21,54 @@ class ConfigTest < MiniTest::Spec
|
|
24
21
|
end
|
25
22
|
end
|
26
23
|
|
27
|
-
describe "#
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
24
|
+
# describe "#[]" do
|
25
|
+
# before { subject.add(:title, {:me => true}) }
|
26
|
+
|
27
|
+
# it { subject[:unknown].must_equal nil }
|
28
|
+
# it { subject.get(:title)[:me].must_equal true }
|
29
|
+
# it { subject["title"][:me].must_equal true }
|
30
|
+
# end
|
31
|
+
|
32
|
+
# []=
|
33
|
+
# []=(... inherit: true)
|
34
|
+
# forwarded to Config#definitions
|
35
|
+
# that goes to ConfigDefinitionsTest
|
36
|
+
describe "#add" do
|
37
|
+
before { subject.add(:title, {:me => true}) }
|
38
|
+
|
39
|
+
# must be kind of Definition
|
40
|
+
it { subject.size.must_equal 1 }
|
41
|
+
it { subject.get(:title).name.must_equal "title" }
|
42
|
+
it { subject.get(:title)[:me].must_equal true }
|
43
|
+
|
44
|
+
# this is actually tested in context in inherit_test.
|
45
|
+
it "overrides former definition" do
|
46
|
+
subject.add(:title, {:peer => Module})
|
47
|
+
subject.get(:title)[:me].must_equal nil
|
48
|
+
subject.get(:title)[:peer].must_equal Module
|
33
49
|
end
|
34
|
-
end
|
35
50
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
51
|
+
describe "inherit: true" do
|
52
|
+
before {
|
53
|
+
subject.add(:title, {:me => true})
|
54
|
+
subject.add(:title, {:peer => Module, :inherit => true})
|
55
|
+
}
|
40
56
|
|
41
|
-
|
42
|
-
subject
|
43
|
-
subject << overrider = Representable::Definition.new(:title)
|
44
|
-
|
45
|
-
subject.size.must_equal 1
|
46
|
-
subject.first.must_equal overrider
|
57
|
+
it { subject.get(:title)[:me].must_equal true }
|
58
|
+
it { subject.get(:title)[:peer].must_equal Module }
|
47
59
|
end
|
48
60
|
end
|
49
61
|
|
50
|
-
describe "#[]" do
|
51
|
-
before { subject << definition }
|
52
62
|
|
53
|
-
|
54
|
-
|
55
|
-
|
63
|
+
describe "#each" do
|
64
|
+
before { subject.add(:title, {:me => true}) }
|
65
|
+
|
66
|
+
it "what" do
|
67
|
+
definitions = []
|
68
|
+
subject.each { |dfn| definitions << dfn }
|
69
|
+
definitions.size.must_equal 1
|
70
|
+
definitions[0][:me].must_equal true
|
71
|
+
end
|
56
72
|
end
|
57
73
|
|
58
74
|
describe "#options" do
|
@@ -63,60 +79,78 @@ class ConfigTest < MiniTest::Spec
|
|
63
79
|
end
|
64
80
|
end
|
65
81
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
82
|
+
|
83
|
+
describe "#add" do
|
84
|
+
subject { Representable::Config.new.add(:title, {:me => true}) }
|
85
|
+
|
86
|
+
it { subject.must_be_kind_of Representable::Definition }
|
87
|
+
it { subject[:me].must_equal true }
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "#get" do
|
91
|
+
subject { Representable::Config.new }
|
92
|
+
|
93
|
+
it do
|
94
|
+
title = subject.add(:title, {})
|
95
|
+
length = subject.add(:length, {})
|
96
|
+
|
97
|
+
subject.get(:title).must_equal title
|
98
|
+
subject.get(:length).must_equal length
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
describe "xxx- #inherit!" do
|
104
|
+
let (:title) { Definition.new(:title) }
|
105
|
+
let (:length) { Definition.new(:length) }
|
106
|
+
let (:stars) { Definition.new(:stars) }
|
107
|
+
|
108
|
+
it do
|
109
|
+
parent = Representable::Config.new
|
110
|
+
parent.add(:title, {:alias => "Callname"})
|
111
|
+
parent[:features][Object] = true
|
112
|
+
# DISCUSS: build Inheritable::Hash automatically in options? is there a gem for that?
|
113
|
+
parent.options[:additional_features] = Representable::Inheritable::Hash[Object => true]
|
114
|
+
|
115
|
+
subject.inherit!(parent)
|
116
|
+
|
117
|
+
# add to inherited config:
|
118
|
+
subject.add(:stars, {})
|
119
|
+
subject[:features][Module] = true
|
120
|
+
subject.options[:additional_features][Module] = true
|
121
|
+
|
122
|
+
subject[:features].must_equal({Object => true, Module => true})
|
123
|
+
|
124
|
+
parent.options[:additional_features].must_equal({Object => true})
|
125
|
+
subject.options[:additional_features].must_equal({Object => true, Module => true})
|
126
|
+
|
127
|
+
# test Definition interface:
|
128
|
+
|
129
|
+
# definitions.size.must_equal([subject.get(:title), subject.get(:stars)])
|
130
|
+
subject.get(:title).object_id.wont_equal parent.get(:title).object_id
|
131
|
+
# subject.get(:stars).object_id.must_equal stars.object_id
|
132
|
+
end
|
133
|
+
|
134
|
+
# Config with Inheritable::Array
|
135
|
+
it "xx" do
|
136
|
+
parent = Representable::Config.new
|
137
|
+
parent.options[:links] = Representable::Inheritable::Array.new
|
138
|
+
parent.options[:links] << "//1"
|
139
|
+
|
140
|
+
subject.options[:links] = Representable::Inheritable::Array.new
|
141
|
+
subject.options[:links] << "//2"
|
142
|
+
|
143
|
+
subject.inherit!(parent)
|
144
|
+
subject.options[:links].must_equal ["//2", "//1"]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe "#features" do
|
149
|
+
it do
|
150
|
+
subject[:features][Object] = true
|
151
|
+
subject[:features][Module] = true
|
152
|
+
|
153
|
+
subject.features.must_equal [Object, Module]
|
120
154
|
end
|
121
155
|
end
|
122
156
|
end
|