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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/CHANGES.md +36 -0
  4. data/Gemfile +3 -3
  5. data/README.md +62 -2
  6. data/Rakefile +1 -1
  7. data/lib/representable.rb +32 -109
  8. data/lib/representable/TODO.getting_serious +7 -1
  9. data/lib/representable/autoload.rb +10 -0
  10. data/lib/representable/binding.rb +20 -16
  11. data/lib/representable/bindings/xml_bindings.rb +0 -1
  12. data/lib/representable/coercion.rb +23 -31
  13. data/lib/representable/config.rb +45 -46
  14. data/lib/representable/declarative.rb +78 -0
  15. data/lib/representable/decorator.rb +39 -10
  16. data/lib/representable/definition.rb +40 -33
  17. data/lib/representable/deserializer.rb +2 -0
  18. data/lib/representable/for_collection.rb +25 -0
  19. data/lib/representable/hash.rb +4 -8
  20. data/lib/representable/hash/collection.rb +2 -9
  21. data/lib/representable/hash_methods.rb +0 -7
  22. data/lib/representable/inheritable.rb +50 -0
  23. data/lib/representable/json.rb +3 -9
  24. data/lib/representable/json/collection.rb +1 -3
  25. data/lib/representable/json/hash.rb +4 -9
  26. data/lib/representable/mapper.rb +8 -5
  27. data/lib/representable/parse_strategies.rb +1 -0
  28. data/lib/representable/pipeline.rb +14 -0
  29. data/lib/representable/represent.rb +6 -0
  30. data/lib/representable/version.rb +1 -1
  31. data/lib/representable/xml.rb +3 -18
  32. data/lib/representable/xml/collection.rb +2 -4
  33. data/lib/representable/xml/hash.rb +2 -10
  34. data/lib/representable/yaml.rb +1 -20
  35. data/representable.gemspec +2 -2
  36. data/test/class_test.rb +5 -10
  37. data/test/coercion_test.rb +31 -92
  38. data/test/config/inherit_test.rb +128 -0
  39. data/test/config_test.rb +114 -80
  40. data/test/definition_test.rb +107 -64
  41. data/test/features_test.rb +41 -0
  42. data/test/filter_test.rb +59 -0
  43. data/test/for_collection_test.rb +74 -0
  44. data/test/inherit_test.rb +44 -3
  45. data/test/inheritable_test.rb +97 -0
  46. data/test/inline_test.rb +0 -18
  47. data/test/instance_test.rb +0 -19
  48. data/test/json_test.rb +9 -44
  49. data/test/lonely_test.rb +1 -0
  50. data/test/parse_strategy_test.rb +30 -0
  51. data/test/represent_test.rb +88 -0
  52. data/test/representable_test.rb +3 -50
  53. data/test/schema_test.rb +123 -0
  54. data/test/test_helper.rb +1 -1
  55. data/test/xml_test.rb +34 -38
  56. metadata +25 -15
  57. data/lib/representable/decorator/coercion.rb +0 -4
  58. data/lib/representable/readable_writeable.rb +0 -29
  59. data/test/inheritance_test.rb +0 -22
@@ -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
- class Song # note that we have to define accessors for the properties here.
7
- attr_accessor :title, :composed_at, :track
8
- end
5
+ representer! do
6
+ include Representable::Coercion
9
7
 
10
- let (:date) { DateTime.parse("Fri, 18 Nov 1983 00:00:00 +0000") }
8
+ property :title # no coercion.
9
+ property :length, :type => Float
11
10
 
12
- describe "on object level" do
13
- module SongRepresenter
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
- it "coerces properties in #from_json" do
22
- song = Song.new.extend(SongRepresenter).from_json('{"composed_at":"November 18th, 1983","track":"18","title":"Scarified"}')
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
- describe "on class level" do
38
- class ImmigrantSong
39
- include Representable::JSON
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
- property :composed_at, :type => DateTime, :default => "May 12th, 2012"
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
- attr_accessor :composed_at, :track
46
- end
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 "coerces into the provided type" do
49
- song = ImmigrantSong.new.from_json("{\"composed_at\":\"November 18th, 1983\",\"track\":\"18\"}")
50
- song.composed_at.must_equal date
51
- song.track.must_equal 18
52
- end
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 "on decorator" do
61
- class SongRepresentation < Representable::Decorator
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 :composed_at, :type => DateTime
66
- property :title
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
- it "coerces when parsing" do
70
- song = SongRepresentation.new(OpenStruct.new).from_json("{\"composed_at\":\"November 18th, 1983\", \"title\": \"Scarified\"}")
71
- song.must_be_kind_of OpenStruct
72
- song.composed_at.must_equal date
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
@@ -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 "#cloned" do
28
- it "clones all definitions" do
29
- subject << obj = definition
30
-
31
- subject.cloned.map(&:name).must_equal ["title"]
32
- subject.cloned.first.object_id.wont_equal obj.object_id
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
- describe "#<<" do
37
- it "returns Definition" do
38
- (subject << definition).must_equal definition
39
- end
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
- it "overwrites old property" do
42
- subject << definition
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
- it { subject[:unknown].must_equal nil }
54
- it { subject[:title].must_equal definition }
55
- it { subject["title"].must_equal definition }
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
- describe "Config inheritance" do
67
- # TODO: this section will soon be moved to uber.
68
- describe "inheritance when including" do
69
- # TODO: test all the below issues AND if cloning works.
70
- module TestMethods
71
- def representer_for(modules=[Representable], &block)
72
- Module.new do
73
- extend TestMethods
74
- include *modules
75
- module_exec(&block)
76
- end
77
- end
78
- end
79
- include TestMethods
80
-
81
- it "inherits to uninitialized child" do
82
- representer_for do # child
83
- include(representer_for do # parent
84
- representable_attrs.inheritable_array(:links) << "bar"
85
- end)
86
- end.representable_attrs.inheritable_array(:links).must_equal(["bar"])
87
- end
88
-
89
- it "works with uninitialized parent" do
90
- representer_for do # child
91
- representable_attrs.inheritable_array(:links) << "bar"
92
-
93
- include(representer_for do # parent
94
- end)
95
- end.representable_attrs.inheritable_array(:links).must_equal(["bar"])
96
- end
97
-
98
- it "inherits when both are initialized" do
99
- representer_for do # child
100
- representable_attrs.inheritable_array(:links) << "bar"
101
-
102
- include(representer_for do # parent
103
- representable_attrs.inheritable_array(:links) << "stadium"
104
- end)
105
- end.representable_attrs.inheritable_array(:links).must_equal(["bar", "stadium"])
106
- end
107
-
108
- it "clones parent inheritables" do # FIXME: actually we don't clone here!
109
- representer_for do # child
110
- representable_attrs.inheritable_array(:links) << "bar"
111
-
112
- include(parent = representer_for do # parent
113
- representable_attrs.inheritable_array(:links) << "stadium"
114
- end)
115
-
116
- parent.representable_attrs.inheritable_array(:links) << "park" # modify parent array.
117
-
118
- end.representable_attrs.inheritable_array(:links).must_equal(["bar", "stadium"])
119
- end
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