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
@@ -3,8 +3,45 @@ require 'test_helper'
3
3
  class DefinitionTest < MiniTest::Spec
4
4
  Definition = Representable::Definition
5
5
 
6
- describe "#merge!" do
6
+ # TODO: test that we DON'T clone options, that must happen in
7
+ describe "#initialize" do
8
+ it do
9
+ opts = nil
10
+
11
+ # new yields the defaultized options HASH.
12
+ definition = Definition.new(:song, :extend => Module) do |options|
13
+ options[:awesome] = true
14
+ options[:parse_filter] << 1
15
+
16
+ # default variables
17
+ options[:as].must_equal "song"
18
+ options[:extend].must_equal Module
19
+ end
20
+
21
+ #
22
+ definition[:awesome].must_equal true
23
+ definition[:parse_filter].instance_variable_get(:@value).must_equal Representable::Pipeline[1]
24
+ definition[:render_filter].instance_variable_get(:@value).must_equal Representable::Pipeline[]
25
+ end
26
+ end
27
+
28
+ describe "#[]" do
7
29
  let (:definition) { Definition.new(:song) }
30
+ # default is nil.
31
+ it { definition[:bla].must_equal nil }
32
+ end
33
+
34
+ # merge!
35
+ describe "#merge!" do
36
+ let (:definition) { Definition.new(:song, :whatever => true) }
37
+
38
+ # merges new options.
39
+ it { definition.merge!(:something => true)[:something].must_equal true }
40
+ # doesn't override original options.
41
+ it { definition.merge!({:something => true})[:whatever].must_equal true }
42
+ # override original when passed in #merge!.
43
+ it { definition.merge!({:whatever => false})[:whatever].must_equal false }
44
+
8
45
 
9
46
  it "runs macros" do
10
47
  definition[:setter].must_equal nil
@@ -12,12 +49,46 @@ class DefinitionTest < MiniTest::Spec
12
49
  definition[:setter].must_respond_to :evaluate
13
50
  end
14
51
 
15
- # it "what" do
16
- # definition.merge!(:parse_strategy => :sync, :collection => true)
17
- # definition[:bullshit].must_equal true
18
- # end
52
+ # with block
53
+ it do
54
+ definition = Definition.new(:song, :extend => Module).merge!({:something => true}) do |options|
55
+ options[:awesome] = true
56
+ options[:render_filter] << 1
57
+
58
+ # default variables
59
+ # options[:as].must_equal "song"
60
+ # options[:extend].must_equal Module
61
+ end
62
+
63
+ definition[:awesome].must_equal true
64
+ definition[:something].must_equal true
65
+ definition[:render_filter].instance_variable_get(:@value).must_equal Representable::Pipeline[1]
66
+ definition[:parse_filter].instance_variable_get(:@value).must_equal Representable::Pipeline[]
67
+ end
68
+
69
+ describe "with :parse_filter" do
70
+ let (:definition) { Definition.new(:title, :parse_filter => 1) }
71
+
72
+ # merges :parse_filter and :render_filter.
73
+ it do
74
+ merged = definition.merge!(:parse_filter => 2)[:parse_filter]
75
+
76
+ merged.instance_variable_get(:@value).must_be_kind_of Representable::Pipeline
77
+ merged.instance_variable_get(:@value).size.must_equal 2
78
+ end
79
+
80
+ # :parse_filter can also be array.
81
+ it { definition.merge!(:parse_filter => [2, 3])[:parse_filter].instance_variable_get(:@value).size.must_equal 3 }
82
+ end
83
+
84
+ # does not change arguments
85
+ it do
86
+ Definition.new(:title).merge!(options = {:whatever => 1})
87
+ options.must_equal(:whatever => 1)
88
+ end
19
89
  end
20
90
 
91
+
21
92
  describe "generic API" do
22
93
  before do
23
94
  @def = Representable::Definition.new(:songs)
@@ -25,7 +96,7 @@ class DefinitionTest < MiniTest::Spec
25
96
 
26
97
  it "responds to #representer_module" do
27
98
  assert_equal nil, Representable::Definition.new(:song).representer_module
28
- assert_equal Hash, Representable::Definition.new(:song, :extend => Hash).representer_module.evaluate(nil)
99
+ assert_equal Hash, Representable::Definition.new(:song, :extend => Hash).representer_module
29
100
  end
30
101
 
31
102
  describe "#typed?" do
@@ -68,15 +139,39 @@ class DefinitionTest < MiniTest::Spec
68
139
  assert_equal :"songs=", @def.setter
69
140
  end
70
141
 
142
+
71
143
  describe "#clone" do
144
+ subject { Representable::Definition.new(:title, :volume => 9, :clonable => Uber::Options::Value.new(1)) }
145
+
146
+ it { subject.clone.must_be_kind_of Representable::Definition }
147
+ it { subject.clone[:clonable].evaluate(nil).must_equal 1 }
148
+
72
149
  it "clones @options" do
73
150
  @def.merge!(:volume => 9)
151
+
74
152
  cloned = @def.clone
75
153
  cloned.merge!(:volume => 8)
76
154
 
77
155
  assert_equal @def[:volume], 9
78
156
  assert_equal cloned[:volume], 8
79
157
  end
158
+
159
+ # pipeline gets cloned properly
160
+ describe "pipeline cloning" do
161
+ subject { Definition.new(:title, :render_filter => 1) }
162
+
163
+ it ("yy")do
164
+ cloned = subject.clone
165
+
166
+ cloned.merge!(:render_filter => 2)
167
+
168
+ subject.instance_variable_get(:@options)[:render_filter].must_equal [1]
169
+ cloned.instance_variable_get(:@options)[:render_filter].must_equal [1,2]
170
+
171
+ subject[:render_filter].instance_variable_get(:@value).must_equal [1]
172
+ cloned[:render_filter].instance_variable_get(:@value).must_equal [1,2]
173
+ end
174
+ end
80
175
  end
81
176
  end
82
177
 
@@ -91,26 +186,26 @@ class DefinitionTest < MiniTest::Spec
91
186
  end
92
187
 
93
188
 
94
- describe "#skipable_nil_value?" do
95
- # default if skipable_nil_value?
189
+ describe "#skipable_empty_value?" do
190
+ # default if skipable_empty_value?
96
191
  before do
97
192
  @def = Representable::Definition.new(:song, :render_nil => true)
98
193
  end
99
194
 
100
195
  it "returns false when not nil" do
101
- assert_equal false, @def.skipable_nil_value?("Disconnect, Disconnect")
196
+ assert_equal false, @def.skipable_empty_value?("Disconnect, Disconnect")
102
197
  end
103
198
 
104
199
  it "returns false when nil and :render_nil => true" do
105
- assert_equal false, @def.skipable_nil_value?(nil)
200
+ assert_equal false, @def.skipable_empty_value?(nil)
106
201
  end
107
202
 
108
203
  it "returns true when nil and :render_nil => false" do
109
- assert_equal true, Representable::Definition.new(:song).skipable_nil_value?(nil)
204
+ assert_equal true, Representable::Definition.new(:song).skipable_empty_value?(nil)
110
205
  end
111
206
 
112
207
  it "returns false when not nil and :render_nil => false" do
113
- assert_equal false, Representable::Definition.new(:song).skipable_nil_value?("Fatal Flu")
208
+ assert_equal false, Representable::Definition.new(:song).skipable_empty_value?("Fatal Flu")
114
209
  end
115
210
  end
116
211
 
@@ -149,54 +244,6 @@ class DefinitionTest < MiniTest::Spec
149
244
  end
150
245
 
151
246
 
152
- describe "#writeable?" do
153
-
154
- it "returns true when :writeable is not given" do
155
- @def = Representable::Definition.new(:song)
156
- assert_equal true, @def.writeable?
157
- end
158
-
159
- it "returns true when :writeable => true" do
160
- @def = Representable::Definition.new(:song, :writeable => true)
161
- assert_equal true, @def.writeable?
162
- end
163
-
164
- it "returns false when :writeable => false" do
165
- @def = Representable::Definition.new(:song, :writeable => false)
166
- assert_equal false, @def.writeable?
167
- end
168
-
169
- it "returns nil when :writeable is nil" do
170
- @def = Representable::Definition.new(:song, :writeable => nil)
171
- assert_equal nil, @def.writeable?
172
- end
173
-
174
- end
175
-
176
- describe "#readable?" do
177
-
178
- it "returns true when :readable is not given" do
179
- @def = Representable::Definition.new(:song)
180
- assert_equal true, @def.readable?
181
- end
182
-
183
- it "returns true when :readable => true" do
184
- @def = Representable::Definition.new(:song, :readable => true)
185
- assert_equal true, @def.readable?
186
- end
187
-
188
- it "returns false when :readable => false" do
189
- @def = Representable::Definition.new(:song, :readable => false)
190
- assert_equal false, @def.readable?
191
- end
192
-
193
- it "returns nil when :readable is nil" do
194
- @def = Representable::Definition.new(:song, :readable => nil)
195
- assert_equal nil, @def.readable?
196
- end
197
-
198
- end
199
-
200
247
  describe "#binding" do
201
248
  it "returns true when :binding is set" do
202
249
  assert Representable::Definition.new(:songs, :binding => Object)[:binding]
@@ -223,10 +270,6 @@ class DefinitionTest < MiniTest::Spec
223
270
  it "responds to #array?" do
224
271
  assert @def.array?
225
272
  end
226
-
227
- it "responds to #default" do
228
- assert_equal nil, @def.send(:default)
229
- end
230
273
  end
231
274
 
232
275
 
@@ -0,0 +1,41 @@
1
+ require 'test_helper'
2
+
3
+ class FeaturesTest < MiniTest::Spec
4
+ module Title
5
+ def title; "Is It A Lie"; end
6
+ end
7
+ module Length
8
+ def length; "2:31"; end
9
+ end
10
+
11
+ definition = lambda {
12
+ feature Title
13
+ feature Length
14
+
15
+ # exec_context: :decorator, so the readers are called on the Decorator instance (which gets readers from features!).
16
+ property :title, exec_context: :decorator
17
+ property :length, exec_context: :decorator
18
+ property :details do
19
+ property :title, exec_context: :decorator
20
+ end
21
+ }
22
+
23
+ let (:song) { OpenStruct.new(:details => Object.new) }
24
+
25
+ describe "Module" do
26
+ representer! do
27
+ instance_exec &definition
28
+ end
29
+
30
+ it { song.extend(representer).to_hash.must_equal({"title"=>"Is It A Lie", "length"=>"2:31", "details"=>{"title"=>"Is It A Lie"}}) }
31
+ end
32
+
33
+
34
+ describe "Decorator" do
35
+ representer!(:decorator => true) do
36
+ instance_exec &definition
37
+ end
38
+
39
+ it { representer.new(song).to_hash.must_equal({"title"=>"Is It A Lie", "length"=>"2:31", "details"=>{"title"=>"Is It A Lie"}}) }
40
+ end
41
+ end
@@ -0,0 +1,59 @@
1
+ require 'test_helper'
2
+
3
+ class FilterPipelineTest < MiniTest::Spec
4
+ let (:block1) { lambda { |value, *| "1: #{value}" } }
5
+ let (:block2) { lambda { |value, *| "2: #{value}" } }
6
+
7
+ subject { Representable::Pipeline[block1, block2] }
8
+
9
+ it { subject.call(Object, "Horowitz").must_equal "2: 1: Horowitz" }
10
+ end
11
+
12
+
13
+ class FilterTest < MiniTest::Spec
14
+ representer! do
15
+ property :title
16
+ property :track,
17
+ :parse_filter => lambda { |val, doc, options| "#{val.downcase},#{doc},#{options}" },
18
+ :render_filter => lambda { |val, doc, options| "#{val.upcase},#{doc},#{options}" }
19
+ end
20
+
21
+ # gets doc and options.
22
+ it {
23
+ song = OpenStruct.new.extend(representer).from_hash("title" => "VULCAN EARS", "track" => "Nine")
24
+ song.title.must_equal "VULCAN EARS"
25
+ song.track.must_equal "nine,{\"title\"=>\"VULCAN EARS\", \"track\"=>\"Nine\"},{}"
26
+ }
27
+
28
+ it { OpenStruct.new("title" => "vulcan ears", "track" => "Nine").extend(representer).to_hash.must_equal( {"title"=>"vulcan ears", "track"=>"NINE,{\"title\"=>\"vulcan ears\"},{}"}) }
29
+
30
+
31
+ describe "#parse_filter" do
32
+ representer! do
33
+ property :track,
34
+ :parse_filter => [
35
+ lambda { |val, doc, options| "#{val}-1" },
36
+ lambda { |val, doc, options| "#{val}-2" }],
37
+ :render_filter => [
38
+ lambda { |val, doc, options| "#{val}-1" },
39
+ lambda { |val, doc, options| "#{val}-2" }]
40
+
41
+ # definition[:parse_filter].instance_variable_get(:@value) << lambda { |val, doc, options| "#{val}-1" }
42
+ # property :track, :parse_filter => lambda { |val, doc, options| "#{val}-2" }, :inherit => true
43
+ end
44
+
45
+ # order matters.
46
+ it { OpenStruct.new.extend(representer).from_hash("track" => "Nine").track.must_equal "Nine-1-2" }
47
+ it { OpenStruct.new("track" => "Nine").extend(representer).to_hash.must_equal({"track"=>"Nine-1-2"}) }
48
+ end
49
+ end
50
+
51
+
52
+ class RenderFilterTest < MiniTest::Spec
53
+ representer! do
54
+ property :track, :render_filter => [lambda { |val, doc, options| "#{val}-1" } ]
55
+ property :track, :render_filter => [lambda { |val, doc, options| "#{val}-2" } ], :inherit => true
56
+ end
57
+
58
+ it { OpenStruct.new("track" => "Nine").extend(representer).to_hash.must_equal({"track"=>"Nine-1-2"}) }
59
+ end
@@ -0,0 +1,74 @@
1
+ require 'test_helper'
2
+
3
+ class ForCollectionTest < MiniTest::Spec
4
+ module SongRepresenter
5
+ include Representable::JSON
6
+
7
+ property :name
8
+ end
9
+
10
+ let (:songs) { [Song.new("Days Go By"), Song.new("Can't Take Them All")] }
11
+ let (:json) { "[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]" }
12
+
13
+
14
+ # Module.for_collection
15
+ # Decorator.for_collection
16
+ for_formats(
17
+ :hash => [Representable::Hash, out=[{"name" => "Days Go By"}, {"name"=>"Can't Take Them All"}], out],
18
+ :json => [Representable::JSON, out="[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]", out],
19
+ # :xml => [Representable::XML, out="<a><song></song><song></song></a>", out]
20
+ ) do |format, mod, output, input|
21
+
22
+ describe "Module::for_collection [#{format}]" do
23
+ let (:format) { format }
24
+
25
+ let (:representer) {
26
+ Module.new do
27
+ include mod
28
+ property :name#, :as => :title
29
+
30
+ collection_representer :class => Song
31
+
32
+ # self.representation_wrap = :songs if format == :xml
33
+ end
34
+ }
35
+
36
+ it { render(songs.extend(representer.for_collection)).must_equal_document output }
37
+ it { render(representer.for_collection.prepare(songs)).must_equal_document output }
38
+ # parsing needs the class set, at least
39
+ it { parse([].extend(representer.for_collection), input).must_equal songs }
40
+ end
41
+
42
+ describe "Module::for_collection without configuration [#{format}]" do
43
+ let (:format) { format }
44
+
45
+ let (:representer) {
46
+ Module.new do
47
+ include mod
48
+ property :name
49
+ end
50
+ }
51
+
52
+ # rendering works out of the box, no config necessary
53
+ it { render(songs.extend(representer.for_collection)).must_equal_document output }
54
+ end
55
+
56
+
57
+ describe "Decorator::for_collection [#{format}]" do
58
+ let (:format) { format }
59
+ let (:representer) {
60
+ Class.new(Representable::Decorator) do
61
+ include mod
62
+ property :name
63
+
64
+ collection_representer :class => Song
65
+ end
66
+ }
67
+
68
+ it { render(representer.for_collection.new(songs)).must_equal_document output }
69
+ it { parse(representer.for_collection.new([]), input).must_equal songs }
70
+ end
71
+ end
72
+
73
+ # with module including module
74
+ end
@@ -40,6 +40,7 @@ class InheritTest < MiniTest::Spec
40
40
  representer! do
41
41
  include SongRepresenter
42
42
 
43
+ puts "passing block"
43
44
  property :name, :inherit => true do # inherit as: title
44
45
  property :string, :as => :s
45
46
  property :length
@@ -86,11 +87,51 @@ class InheritTest < MiniTest::Spec
86
87
  it "replaces inherited property" do
87
88
  representer.representable_attrs.size.must_equal 2
88
89
 
89
- definition = representer.representable_attrs[:track] # TODO: find a better way to assert Definition identity.
90
- definition.keys.size.must_equal 2
90
+ definition = representer.representable_attrs.get(:track) # TODO: find a better way to assert Definition identity.
91
+ # definition.keys.size.must_equal 2
91
92
  definition[:representable]. must_equal true
92
93
  definition[:as].evaluate(nil).must_equal "track" # was "no".
93
94
  end
94
95
  end
95
96
 
96
- end
97
+
98
+ # decorator
99
+ describe ":inherit with decorator" do
100
+ representer!(:decorator => true) do
101
+ property :hit do
102
+ property :title
103
+ end
104
+ end
105
+
106
+ let (:inheriting) {
107
+ class InheritingDecorator < representer
108
+ property :hit, :inherit => true do
109
+ property :length
110
+ end
111
+ self
112
+ end
113
+ }
114
+
115
+ it { inheriting.new(OpenStruct.new(:hit => OpenStruct.new(:title => "Hole In Your Soul", :length => "2:59"))).to_hash.must_equal(
116
+ {"hit"=>{"title"=>"Hole In Your Soul", "length"=>"2:59"}}) }
117
+ end
118
+ end
119
+
120
+
121
+ # class InheritancingTest < MiniTest::Spec
122
+ # class SongDecorator < Representable::Decorator
123
+ # include Representable::Hash
124
+ # property :album do
125
+ # # does have Hash.
126
+ # property :title
127
+ # end
128
+ # end
129
+
130
+ # class JsonSongDecorator < SongDecorator
131
+ # include Representable::XML
132
+ # end
133
+
134
+ # it do
135
+ # puts JsonSongDecorator.new(OpenStruct.new(:album => OpenStruct.new(:title => "Erotic Cakes", :tracks => nil))).to_xml
136
+ # end
137
+ # end