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