representable 1.7.7 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +42 -8
  3. data/README.md +208 -55
  4. data/Rakefile +0 -6
  5. data/lib/representable.rb +39 -43
  6. data/lib/representable/binding.rb +59 -37
  7. data/lib/representable/bindings/hash_bindings.rb +3 -4
  8. data/lib/representable/bindings/xml_bindings.rb +10 -10
  9. data/lib/representable/bindings/yaml_bindings.rb +2 -2
  10. data/lib/representable/coercion.rb +1 -1
  11. data/lib/representable/config.rb +11 -5
  12. data/lib/representable/definition.rb +67 -35
  13. data/lib/representable/deserializer.rb +23 -27
  14. data/lib/representable/hash.rb +15 -4
  15. data/lib/representable/hash/allow_symbols.rb +27 -0
  16. data/lib/representable/json.rb +0 -1
  17. data/lib/representable/json/collection.rb +0 -2
  18. data/lib/representable/mapper.rb +6 -13
  19. data/lib/representable/parse_strategies.rb +57 -0
  20. data/lib/representable/readable_writeable.rb +29 -0
  21. data/lib/representable/serializer.rb +9 -4
  22. data/lib/representable/version.rb +1 -1
  23. data/lib/representable/xml.rb +1 -1
  24. data/lib/representable/xml/collection.rb +0 -2
  25. data/lib/representable/yaml.rb +0 -1
  26. data/representable.gemspec +1 -0
  27. data/test/as_test.rb +43 -0
  28. data/test/class_test.rb +124 -0
  29. data/test/config_test.rb +13 -3
  30. data/test/decorator_scope_test.rb +28 -0
  31. data/test/definition_test.rb +46 -35
  32. data/test/exec_context_test.rb +93 -0
  33. data/test/generic_test.rb +0 -154
  34. data/test/getter_setter_test.rb +28 -0
  35. data/test/hash_bindings_test.rb +35 -35
  36. data/test/hash_test.rb +0 -20
  37. data/test/if_test.rb +78 -0
  38. data/test/inherit_test.rb +21 -1
  39. data/test/inheritance_test.rb +1 -1
  40. data/test/inline_test.rb +40 -2
  41. data/test/instance_test.rb +286 -0
  42. data/test/is_representable_test.rb +77 -0
  43. data/test/json_test.rb +6 -29
  44. data/test/nested_test.rb +30 -0
  45. data/test/parse_strategy_test.rb +249 -0
  46. data/test/pass_options_test.rb +27 -0
  47. data/test/prepare_test.rb +67 -0
  48. data/test/reader_writer_test.rb +19 -0
  49. data/test/representable_test.rb +25 -265
  50. data/test/stringify_hash_test.rb +41 -0
  51. data/test/test_helper.rb +12 -4
  52. data/test/wrap_test.rb +48 -0
  53. data/test/xml_bindings_test.rb +37 -37
  54. data/test/xml_test.rb +14 -14
  55. metadata +94 -30
  56. data/lib/representable/deprecations.rb +0 -4
  57. data/lib/representable/feature/readable_writeable.rb +0 -30
@@ -0,0 +1,93 @@
1
+ require 'test_helper'
2
+
3
+ class ExecContextTest < MiniTest::Spec
4
+ for_formats(
5
+ :hash => [Representable::Hash, {Song => "Rebel Fate"}, {Song=>"Timing"}],
6
+ # :xml => [Representable::XML, "<open_struct>\n <song>\n <name>Alive</name>\n </song>\n</open_struct>", "<open_struct><song><name>You've Taken Everything</name></song>/open_struct>"],
7
+ # :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"],
8
+ ) do |format, mod, input, output|
9
+
10
+ let (:song) { representer.prepare(Song.new("Timing")) }
11
+ let (:format) { format }
12
+
13
+
14
+ describe "exec_context: nil" do
15
+ representer!(:module => mod) do
16
+ property :name, :as => lambda { |*| self.class }
17
+ end
18
+
19
+ it { render(song).must_equal_document output }
20
+ it { parse(song, input).name.must_equal "Rebel Fate" }
21
+ end
22
+
23
+
24
+ describe "exec_context: :decorator" do
25
+ representer!(:module => mod) do
26
+ property :name, :as => lambda { |*| self.class }, :exec_context => :decorator
27
+ end
28
+
29
+ it { render(song).must_equal_document output }
30
+ it { parse(song, input).name.must_equal "Rebel Fate" }
31
+ end
32
+
33
+
34
+ describe "exec_context: :binding" do
35
+ representer!(:module => mod) do
36
+ property :name,
37
+ :as => lambda { |*| self.class }, # to actually test
38
+ :exec_context => :binding,
39
+ :setter => lambda { |v, *args| represented.name = v # to make parsing work.
40
+ }
41
+ end
42
+
43
+ it { render(song).must_equal_document({Representable::Hash::PropertyBinding => "name"}) }
44
+ it { parse(song, {Representable::Hash::PropertyBinding => "Rebel Fate"}).name.must_equal "Rebel Fate" }
45
+ end
46
+
47
+
48
+ describe "Decorator" do
49
+ # DISCUSS: do we need this test?
50
+ describe "exec_context: nil" do
51
+ representer!(:module => mod, :decorator => true) do
52
+ property :name, :as => lambda { |*| self.class }
53
+ end
54
+
55
+ it { render(song).must_equal_document output }
56
+ it { parse(song, input).name.must_equal "Rebel Fate" }
57
+ end
58
+
59
+
60
+ describe "exec_context: :decorator" do # this tests if lambdas are run in the right context, if methods are called in the right context and if we can access the represented object.
61
+ representer!(:module => mod, :decorator => true) do
62
+ property :name, :as => lambda { |*| self.class.superclass }, :exec_context => :decorator
63
+
64
+ define_method :name do # def in Decorator class.
65
+ "Timebomb"
66
+ end
67
+
68
+ define_method :"name=" do |v| # def in Decorator class.
69
+ represented.name = v
70
+ end
71
+ end
72
+
73
+ it { render(song).must_equal_document({Representable::Decorator=>"Timebomb"}) }
74
+ it { parse(song, {Representable::Decorator=>"Listless"}).name.must_equal "Listless" }
75
+ end
76
+
77
+
78
+ # DISCUSS: do we need this test?
79
+ describe "exec_context: :binding" do
80
+ representer!(:module => mod, :decorator => true) do
81
+ property :name,
82
+ :as => lambda { |*| self.class }, # to actually test
83
+ :exec_context => :binding,
84
+ :setter => lambda { |v, *args| represented.name = v # to make parsing work.
85
+ }
86
+ end
87
+
88
+ it { render(song).must_equal_document({Representable::Hash::PropertyBinding => "name"}) }
89
+ it { parse(song, {Representable::Hash::PropertyBinding => "Rebel Fate"}).name.must_equal "Rebel Fate" }
90
+ end
91
+ end
92
+ end
93
+ end
@@ -73,160 +73,6 @@ class GenericTest < MiniTest::Spec
73
73
  end
74
74
 
75
75
 
76
- describe "property with instance: { nil }" do # TODO: introduce :representable option?
77
- representer!(:inject => :song_representer) do
78
- property :song, :instance => lambda { |*| nil }, :extend => song_representer
79
- end
80
-
81
- let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) }
82
-
83
- it "calls #to_hash on song instance, nothing else" do
84
- hit.to_hash.must_equal("song"=>{"title"=>"Resist Stance"})
85
- end
86
-
87
- it "calls #from_hash on the existing song instance, nothing else" do
88
- song_id = hit.song.object_id
89
- hit.from_hash("song"=>{"title"=>"Suffer"})
90
- hit.song.title.must_equal "Suffer"
91
- hit.song.object_id.must_equal song_id
92
- end
93
- end
94
-
95
-
96
- for_formats(
97
- :hash => [Representable::Hash, {"song"=>{"title"=>"Resist Stance"}}, {"song"=>{"title"=>"Suffer"}}],
98
- :xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><song><title>Suffer</title></song></open_struct>",],
99
- :yaml => [Representable::YAML, "---\nsong:\n title: Resist Stance\n", "---\nsong:\n title: Suffer\n"],
100
- ) do |format, mod, output, input|
101
-
102
- describe "[#{format}] property with parse_strategy: :sync" do # TODO: introduce :representable option?
103
- let (:format) { format }
104
-
105
- representer!(:module => mod, :name => :song_representer) do
106
- property :title
107
- self.representation_wrap = :song if format == :xml
108
- end
109
-
110
- representer!(:inject => :song_representer, :module => mod) do
111
- property :song, :parse_strategy => :sync, :extend => song_representer
112
- end
113
-
114
- let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) }
115
-
116
- it "calls #to_hash on song instance, nothing else" do
117
- render(hit).must_equal_document(output)
118
- end
119
-
120
-
121
- it "calls #from_hash on the existing song instance, nothing else" do
122
- song_id = hit.song.object_id
123
-
124
- parse(hit, input)
125
-
126
- hit.song.title.must_equal "Suffer"
127
- hit.song.object_id.must_equal song_id
128
- end
129
- end
130
- end
131
-
132
- # FIXME: there's a bug with XML and the collection name!
133
- for_formats(
134
- :hash => [Representable::Hash, {"songs"=>[{"title"=>"Resist Stance"}]}, {"songs"=>[{"title"=>"Suffer"}]}],
135
- #:json => [Representable::JSON, "{\"song\":{\"name\":\"Alive\"}}", "{\"song\":{\"name\":\"You've Taken Everything\"}}"],
136
- :xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><songs><title>Suffer</title></songs></open_struct>"],
137
- :yaml => [Representable::YAML, "---\nsongs:\n- title: Resist Stance\n", "---\nsongs:\n- title: Suffer\n"],
138
- ) do |format, mod, output, input|
139
-
140
- describe "[#{format}] collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
141
- let (:format) { format }
142
- representer!(:module => mod, :name => :song_representer) do
143
- property :title
144
- self.representation_wrap = :song if format == :xml
145
- end
146
-
147
- representer!(:inject => :song_representer, :module => mod) do
148
- collection :songs, :parse_strategy => :sync, :extend => song_representer
149
- end
150
-
151
- let (:album) { OpenStruct.new(:songs => [song]).extend(representer) }
152
-
153
- it "calls #to_hash on song instances, nothing else" do
154
- render(album).must_equal_document(output)
155
- end
156
-
157
- it "calls #from_hash on the existing song instance, nothing else" do
158
- collection_id = album.songs.object_id
159
- song = album.songs.first
160
- song_id = song.object_id
161
-
162
- parse(album, input)
163
-
164
- album.songs.first.title.must_equal "Suffer"
165
- song.title.must_equal "Suffer"
166
- #album.songs.object_id.must_equal collection_id # TODO: don't replace!
167
- song.object_id.must_equal song_id
168
- end
169
- end
170
- end
171
-
172
-
173
- # Lonely Collection
174
- require "representable/hash/collection"
175
-
176
- for_formats(
177
- :hash => [Representable::Hash::Collection, [{"title"=>"Resist Stance"}], [{"title"=>"Suffer"}]],
178
- # :xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><songs><title>Suffer</title></songs></open_struct>"],
179
- ) do |format, mod, output, input|
180
-
181
- describe "[#{format}] lonely collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
182
- let (:format) { format }
183
- representer!(:module => Representable::Hash, :name => :song_representer) do
184
- property :title
185
- self.representation_wrap = :song if format == :xml
186
- end
187
-
188
- representer!(:inject => :song_representer, :module => mod) do
189
- items :parse_strategy => :sync, :extend => song_representer
190
- end
191
-
192
- let (:album) { [song].extend(representer) }
193
-
194
- it "calls #to_hash on song instances, nothing else" do
195
- render(album).must_equal_document(output)
196
- end
197
-
198
- it "calls #from_hash on the existing song instance, nothing else" do
199
- #collection_id = album.object_id
200
- song = album.first
201
- song_id = song.object_id
202
-
203
- parse(album, input)
204
-
205
- album.first.title.must_equal "Suffer"
206
- song.title.must_equal "Suffer"
207
- song.object_id.must_equal song_id
208
- end
209
- end
210
- end
211
-
212
- describe "mix :extend and inline representers" do
213
- representer! do
214
- rpr_module = Module.new do
215
- include Representable::Hash
216
- property :title
217
- end
218
- property :song, :extend => rpr_module do
219
- property :artist
220
- end
221
- end
222
-
223
- it do OpenStruct.new(:song => OpenStruct.new(:title => "The Fever And The Sound", :artist => "Strung Out")).extend(representer).
224
- to_hash.
225
- must_equal({"song"=>{"artist"=>"Strung Out", "title"=>"The Fever And The Sound"}})
226
- end
227
- end
228
-
229
-
230
76
  # wrap_test
231
77
  for_formats(
232
78
  :hash => [Representable::Hash, {}],
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ class GetterSetterTest < BaseTest
4
+ representer! do
5
+ property :name, # key under :name.
6
+ :getter => lambda { |args| "#{args[:welcome]} #{song_name}" },
7
+ :setter => lambda { |val, args| self.song_name = "#{args[:welcome]} #{val}" }
8
+ end
9
+
10
+ subject { Struct.new(:song_name).new("Mony Mony").extend(representer) }
11
+
12
+ it "uses :getter when rendering" do
13
+ subject.instance_eval { def name; raise; end }
14
+ subject.to_hash(:welcome => "Hi").must_equal({"name" => "Hi Mony Mony"})
15
+ end
16
+
17
+ it "does not call original reader when rendering" do
18
+ subject.instance_eval { def name; raise; end; self }.to_hash({})
19
+ end
20
+
21
+ it "uses :setter when parsing" do
22
+ subject.from_hash({"name" => "Eyes Without A Face"}, :welcome => "Hello").song_name.must_equal "Hello Eyes Without A Face"
23
+ end
24
+
25
+ it "does not call original writer when parsing" do
26
+ subject.instance_eval { def name=(*); raise; end; self }.from_hash({"name"=>"Dana D And Talle T"}, {})
27
+ end
28
+ end
@@ -5,91 +5,91 @@ class HashBindingTest < MiniTest::Spec
5
5
  include Representable::JSON
6
6
  property :name
7
7
  end
8
-
8
+
9
9
  class SongWithRepresenter < ::Song
10
10
  include Representable
11
11
  include SongRepresenter
12
12
  end
13
-
14
-
13
+
14
+
15
15
  describe "PropertyBinding" do
16
16
  describe "#read" do
17
17
  before do
18
- @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song), nil)
18
+ @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song), nil, nil)
19
19
  end
20
-
20
+
21
21
  it "returns fragment if present" do
22
22
  assert_equal "Stick The Flag Up Your Goddamn Ass, You Sonofabitch", @property.read({"song" => "Stick The Flag Up Your Goddamn Ass, You Sonofabitch"})
23
23
  assert_equal "", @property.read({"song" => ""})
24
24
  assert_equal nil, @property.read({"song" => nil})
25
25
  end
26
-
26
+
27
27
  it "returns FRAGMENT_NOT_FOUND if not in document" do
28
28
  assert_equal Representable::Binding::FragmentNotFound, @property.read({})
29
29
  end
30
-
30
+
31
31
  end
32
-
32
+
33
33
  describe "with plain text" do
34
34
  before do
35
- @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song), nil)
35
+ @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song), nil, nil)
36
36
  end
37
-
37
+
38
38
  it "extracts with #read" do
39
39
  assert_equal "Thinning the Herd", @property.read("song" => "Thinning the Herd")
40
40
  end
41
-
41
+
42
42
  it "inserts with #write" do
43
43
  doc = {}
44
44
  assert_equal("Thinning the Herd", @property.write(doc,"Thinning the Herd"))
45
45
  assert_equal({"song"=>"Thinning the Herd"}, doc)
46
46
  end
47
47
  end
48
-
48
+
49
49
  describe "with an object" do
50
50
  before do
51
- @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song, :class => SongWithRepresenter), nil)
51
+ @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song, :class => SongWithRepresenter), nil, nil)
52
52
  @doc = {}
53
53
  end
54
-
54
+
55
55
  it "extracts with #read" do
56
56
  assert_equal SongWithRepresenter.new("Thinning the Herd"), @property.read("song" => {"name" => "Thinning the Herd"})
57
57
  end
58
-
58
+
59
59
  it "inserts with #write" do
60
60
  assert_equal({"name"=>"Thinning the Herd"}, @property.write(@doc, SongWithRepresenter.new("Thinning the Herd")))
61
61
  assert_equal({"song" => {"name"=>"Thinning the Herd"}}, @doc)
62
62
  end
63
63
  end
64
-
64
+
65
65
  describe "with an object and :extend" do
66
66
  before do
67
- @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song, :class => Song, :extend => SongRepresenter), nil)
67
+ @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song, :class => Song, :extend => SongRepresenter), nil, nil)
68
68
  @doc = {}
69
69
  end
70
-
70
+
71
71
  it "extracts with #read" do
72
72
  assert_equal Song.new("Thinning the Herd"), @property.read("song" => {"name" => "Thinning the Herd"})
73
73
  end
74
-
74
+
75
75
  it "inserts with #write" do
76
76
  assert_equal({"name"=>"Thinning the Herd"}, @property.write(@doc, Song.new("Thinning the Herd")))
77
77
  assert_equal({"song" => {"name"=>"Thinning the Herd"}}, @doc)
78
78
  end
79
79
  end
80
80
  end
81
-
82
-
81
+
82
+
83
83
  describe "CollectionBinding" do
84
84
  describe "with plain text items" do
85
85
  before do
86
- @property = Representable::Hash::CollectionBinding.new(Representable::Definition.new(:songs, :collection => true), nil)
86
+ @property = Representable::Hash::CollectionBinding.new(Representable::Definition.new(:songs, :collection => true), Album.new, nil)
87
87
  end
88
-
88
+
89
89
  it "extracts with #read" do
90
90
  assert_equal ["The Gargoyle", "Bronx"], @property.read("songs" => ["The Gargoyle", "Bronx"])
91
91
  end
92
-
92
+
93
93
  it "inserts with #write" do
94
94
  doc = {}
95
95
  assert_equal(["The Gargoyle", "Bronx"], @property.write(doc, ["The Gargoyle", "Bronx"]))
@@ -97,32 +97,32 @@ class HashBindingTest < MiniTest::Spec
97
97
  end
98
98
  end
99
99
  end
100
-
101
-
102
-
103
-
100
+
101
+
102
+
103
+
104
104
  describe "HashBinding" do
105
105
  describe "with plain text items" do
106
106
  before do
107
- @property = Representable::Hash::HashBinding.new(Representable::Definition.new(:songs, :hash => true), nil)
107
+ @property = Representable::Hash::HashBinding.new(Representable::Definition.new(:songs, :hash => true), nil, nil)
108
108
  end
109
-
109
+
110
110
  it "extracts with #read" do
111
111
  assert_equal({"first" => "The Gargoyle", "second" => "Bronx"} , @property.read("songs" => {"first" => "The Gargoyle", "second" => "Bronx"}))
112
112
  end
113
-
113
+
114
114
  it "inserts with #write" do
115
115
  doc = {}
116
116
  assert_equal({"first" => "The Gargoyle", "second" => "Bronx"}, @property.write(doc, {"first" => "The Gargoyle", "second" => "Bronx"}))
117
117
  assert_equal({"songs"=>{"first" => "The Gargoyle", "second" => "Bronx"}}, doc)
118
118
  end
119
119
  end
120
-
120
+
121
121
  describe "with objects" do
122
122
  before do
123
- @property = Representable::Hash::HashBinding.new(Representable::Definition.new(:songs, :hash => true, :class => Song, :extend => SongRepresenter), nil)
123
+ @property = Representable::Hash::HashBinding.new(Representable::Definition.new(:songs, :hash => true, :class => Song, :extend => SongRepresenter), nil, nil)
124
124
  end
125
-
125
+
126
126
  it "doesn't change the represented hash in #write" do
127
127
  song = Song.new("Better Than That")
128
128
  hash = {"first" => song}
@@ -130,6 +130,6 @@ class HashBindingTest < MiniTest::Spec
130
130
  assert_equal({"first" => song}, hash)
131
131
  end
132
132
  end
133
-
133
+
134
134
  end
135
135
  end
@@ -1,5 +1,4 @@
1
1
  require 'test_helper'
2
- require 'representable/hash'
3
2
 
4
3
  class HashTest < MiniTest::Spec
5
4
  def self.hash_representer(&block)
@@ -147,23 +146,4 @@ class HashTest < MiniTest::Spec
147
146
  end
148
147
  end
149
148
  end
150
-
151
-
152
- class DefinitionTest < MiniTest::Spec
153
- it "what" do
154
- class Representable::Hash::Binding < SimpleDelegator
155
-
156
- end
157
-
158
- definition = Representable::Definition.new(:name)
159
- wrapped = Representable::Hash::Binding.new(definition)
160
-
161
- wrapped.name.must_equal "name"
162
- wrapped.hash?.must_equal nil
163
- wrapped.array?.must_equal nil
164
- wrapped.options.must_equal({})
165
-
166
- #wrapped.
167
- end
168
- end
169
149
  end