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.
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