representable 1.8.5 → 2.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|