representable 1.7.7 → 1.8.0

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