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
@@ -2,8 +2,14 @@ require "representable/deserializer"
2
2
 
3
3
  module Representable
4
4
  class ObjectSerializer < ObjectDeserializer
5
- def call # TODO: make typed? switch here!
6
- return @object if @object.nil?
5
+ def initialize(binding, object)
6
+ super(binding)
7
+ @object = object
8
+ end
9
+
10
+ def call
11
+ # return unless @binding.typed? # FIXME: fix that in XML/YAML.
12
+ return @object if @object.nil? # DISCUSS: move to Object#serialize ?
7
13
 
8
14
  representable = prepare(@object)
9
15
 
@@ -12,8 +18,7 @@ module Representable
12
18
 
13
19
  private
14
20
  def serialize(object, user_options)
15
- # TODO: this used to be handled in #serialize where Object added it's behaviour. treat scalars as objects to remove this switch:
16
- return object unless @binding.typed?
21
+ return object unless @binding.representable?
17
22
 
18
23
  object.send(@binding.serialize_method, user_options.merge!({:wrap => false}))
19
24
  end
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.7.7"
2
+ VERSION = "1.8.0"
3
3
  end
@@ -52,7 +52,7 @@ module Representable
52
52
  # Returns a Nokogiri::XML object representing this object.
53
53
  def to_node(options={})
54
54
  options[:doc] ||= Nokogiri::XML::Document.new
55
- root_tag = options[:wrap] || representation_wrap
55
+ root_tag = options[:wrap] || representation_wrap(options)
56
56
 
57
57
  create_representation_with(Nokogiri::XML::Node.new(root_tag.to_s, options[:doc]), options, PropertyBinding)
58
58
  end
@@ -1,5 +1,3 @@
1
- require 'representable/hash/collection'
2
-
3
1
  module Representable::XML
4
2
  module Collection
5
3
  include Representable::XML
@@ -1,4 +1,3 @@
1
- require 'representable/hash'
2
1
  require 'representable/bindings/yaml_bindings'
3
2
 
4
3
  module Representable
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
 
22
22
  s.add_dependency "nokogiri"
23
23
  s.add_dependency "multi_json"
24
+ s.add_dependency "uber"
24
25
 
25
26
  s.add_development_dependency "rake"
26
27
  s.add_development_dependency "test_xml", ">= 0.1.6"
@@ -0,0 +1,43 @@
1
+ require 'test_helper'
2
+
3
+ class AsTest < MiniTest::Spec
4
+ for_formats(
5
+ :hash => [Representable::Hash, {"title" => "Wie Es Geht"}, {"title" => "Revolution"}],
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("Revolution")) }
11
+ let (:format) { format }
12
+
13
+
14
+ describe "as: with :symbol" do
15
+ representer!(:module => mod) do
16
+ property :name, :as => :title
17
+ end
18
+
19
+ it { render(song).must_equal_document output }
20
+ it { parse(song, input).name.must_equal "Wie Es Geht" }
21
+ end
22
+
23
+
24
+ describe "as: with lambda" do
25
+ representer!(:module => mod) do
26
+ property :name, :as => lambda { |*| "#{self.class}" }
27
+ end
28
+
29
+ it { render(song).must_equal_document({"Song" => "Revolution"}) }
30
+ it { parse(song, {"Song" => "Wie Es Geht"}).name.must_equal "Wie Es Geht" }
31
+ end
32
+
33
+
34
+ describe "lambda arguments" do
35
+ representer! do
36
+ property :name, :as => lambda { |*args| args.inspect }
37
+ end
38
+
39
+ it { render(song, :volume => 1).must_equal_document({"[{:volume=>1}]" => "Revolution"}) }
40
+ it { parse(song, {"[{:volume=>1}]" => "Wie Es Geht"}, :volume => 1).name.must_equal "Wie Es Geht" }
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,124 @@
1
+ require 'test_helper'
2
+
3
+ class ClassTest < BaseTest
4
+
5
+ class RepresentingSong
6
+ attr_reader :name
7
+
8
+ def from_hash(doc, *args)
9
+ @name = doc["__name__"]
10
+
11
+ self # DISCUSS: do we wanna be able to return whatever we want here? this is a trick to replace the actual object
12
+ end
13
+ end
14
+
15
+
16
+ describe "class: ClassName, only" do
17
+ representer! do
18
+ property :song, :class => RepresentingSong # supposed this class exposes #from_hash itself.
19
+ end
20
+
21
+ it "creates fresh instance and doesn't extend" do
22
+ song = representer.prepare(OpenStruct.new).from_hash({"song" => {"__name__" => "Captured"}}).song
23
+ song.must_be_instance_of RepresentingSong
24
+ song.name.must_equal "Captured"
25
+ end
26
+ end
27
+
28
+
29
+ describe "class: lambda, only" do
30
+ representer! do
31
+ property :song, :class => lambda { |*| RepresentingSong }
32
+ end
33
+
34
+ it "creates fresh instance and doesn't extend" do
35
+ song = representer.prepare(OpenStruct.new).from_hash({"song" => {"__name__" => "Captured"}}).song
36
+ song.must_be_instance_of RepresentingSong
37
+ song.name.must_equal "Captured"
38
+ end
39
+ end
40
+
41
+
42
+ describe "lambda { nil }" do # TODO: remove in 2.0.
43
+ representer! do
44
+ property :title, :class => nil
45
+ end
46
+
47
+ it "skips creating new instance" do
48
+ object = Object.new
49
+ object.instance_eval do
50
+ def from_hash(hash, *args)
51
+ hash
52
+ end
53
+ end
54
+
55
+ song = OpenStruct.new.extend(representer).from_hash(hash = {"title" => object})
56
+ song.title.must_equal object
57
+ end
58
+ end
59
+
60
+
61
+ describe "lambda receiving fragment and args" do
62
+ let (:klass) { Class.new do
63
+ class << self
64
+ attr_accessor :args
65
+ end
66
+
67
+ def from_hash(*)
68
+ self.class.new
69
+ end
70
+ end }
71
+
72
+ representer!(:inject => :klass) do
73
+ _klass = klass
74
+ property :song, :class => lambda { |fragment, args| _klass.args=([fragment,args]); _klass }
75
+ end
76
+
77
+ it { representer.prepare(OpenStruct.new).from_hash({"song" => {"name" => "Captured"}}, :volume => true).song.class.args.
78
+ must_equal([{"name"=>"Captured"}, {:volume=>true}]) }
79
+ end
80
+
81
+
82
+ describe "collection: lambda receiving fragment and args" do
83
+ let (:klass) { Class.new do
84
+ class << self
85
+ attr_accessor :args
86
+ end
87
+
88
+ def from_hash(*)
89
+ self.class.new
90
+ end
91
+ end }
92
+
93
+ representer!(:inject => :klass) do
94
+ _klass = klass
95
+ collection :songs, :class => lambda { |fragment, i, args| _klass.args=([fragment,i,args]); _klass }
96
+ end
97
+
98
+ it { representer.prepare(OpenStruct.new).from_hash({"songs" => [{"name" => "Captured"}]}, :volume => true).songs.first.class.args.
99
+ must_equal([{"name"=>"Captured"}, 0, {:volume=>true}]) }
100
+ end
101
+
102
+
103
+ describe "class: implementing #from_hash" do
104
+ let(:parser) do
105
+ Class.new do
106
+ def from_hash(*)
107
+ [1,2,3,4]
108
+ end
109
+ end
110
+ end
111
+
112
+ representer!(:inject => :parser) do
113
+ property :song, :class => parser # supposed this class exposes #from_hash itself.
114
+ end
115
+
116
+ it "allows returning arbitrary objects in #from_hash" do
117
+ representer.prepare(OpenStruct.new).from_hash({"song" => 1}).song.must_equal [1,2,3,4]
118
+ end
119
+ end
120
+ end
121
+
122
+ #TODO: test fragment,
123
+
124
+ # `class: Song` only, no :extend.
@@ -1,5 +1,7 @@
1
1
  require 'test_helper'
2
2
 
3
+ # tested feature: ::property
4
+
3
5
  class ConfigTest < MiniTest::Spec
4
6
  subject { Representable::Config.new }
5
7
  PunkRock = Class.new
@@ -8,17 +10,17 @@ class ConfigTest < MiniTest::Spec
8
10
 
9
11
  describe "wrapping" do
10
12
  it "returns false per default" do
11
- assert_equal nil, subject.wrap_for("Punk")
13
+ assert_equal nil, subject.wrap_for("Punk", nil)
12
14
  end
13
15
 
14
16
  it "infers a printable class name if set to true" do
15
17
  subject.wrap = true
16
- assert_equal "punk_rock", subject.wrap_for(PunkRock)
18
+ assert_equal "punk_rock", subject.wrap_for(PunkRock, nil)
17
19
  end
18
20
 
19
21
  it "can be set explicitely" do
20
22
  subject.wrap = "Descendents"
21
- assert_equal "Descendents", subject.wrap_for(PunkRock)
23
+ assert_equal "Descendents", subject.wrap_for(PunkRock, nil)
22
24
  end
23
25
  end
24
26
 
@@ -35,6 +37,14 @@ class ConfigTest < MiniTest::Spec
35
37
  it "returns Definition" do
36
38
  (subject << definition).must_equal definition
37
39
  end
40
+
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
47
+ end
38
48
  end
39
49
 
40
50
  describe "#[]" do
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ # TODO: remove in 2.0.
4
+ class DecoratorScopeTest < MiniTest::Spec
5
+ representer! do
6
+ property :title, :getter => lambda { |*| title_from_representer }, :decorator_scope => true
7
+ end
8
+
9
+ let (:representer_with_method) {
10
+ Module.new do
11
+ include Representable::Hash
12
+ property :title, :decorator_scope => true
13
+ def title; "Crystal Planet"; end
14
+ end
15
+ }
16
+
17
+ it "executes lambdas in represented context" do
18
+ Class.new do
19
+ def title_from_representer
20
+ "Sounds Of Silence"
21
+ end
22
+ end.new.extend(representer).to_hash.must_equal({"title"=>"Sounds Of Silence"})
23
+ end
24
+
25
+ it "executes method in represented context" do
26
+ Object.new.extend(representer_with_method).to_hash.must_equal({"title"=>"Crystal Planet"})
27
+ end
28
+ end
@@ -1,16 +1,31 @@
1
1
  require 'test_helper'
2
2
 
3
3
  class DefinitionTest < MiniTest::Spec
4
+ Definition = Representable::Definition
5
+
6
+ describe "#merge!" do
7
+ let (:definition) { Definition.new(:song) }
8
+
9
+ it "runs macros" do
10
+ definition[:setter].must_equal nil
11
+ definition.merge!(:parse_strategy => :sync)
12
+ definition[:setter].must_respond_to :evaluate
13
+ end
14
+
15
+ # it "what" do
16
+ # definition.merge!(:parse_strategy => :sync, :collection => true)
17
+ # definition[:bullshit].must_equal true
18
+ # end
19
+ end
20
+
4
21
  describe "generic API" do
5
22
  before do
6
23
  @def = Representable::Definition.new(:songs)
7
24
  end
8
25
 
9
- describe "DCI" do
10
- it "responds to #representer_module" do
11
- assert_equal nil, Representable::Definition.new(:song).representer_module
12
- assert_equal Hash, Representable::Definition.new(:song, :extend => Hash).representer_module
13
- end
26
+ it "responds to #representer_module" do
27
+ assert_equal nil, Representable::Definition.new(:song).representer_module
28
+ assert_equal Hash, Representable::Definition.new(:song, :extend => Hash).representer_module.evaluate(nil)
14
29
  end
15
30
 
16
31
  describe "#typed?" do
@@ -31,6 +46,16 @@ class DefinitionTest < MiniTest::Spec
31
46
  end
32
47
  end
33
48
 
49
+
50
+ describe "#representable?" do
51
+ it { assert Definition.new(:song, :representable => true).representable? }
52
+ it { Definition.new(:song, :representable => true, :extend => Object).representable?.must_equal true }
53
+ it { refute Definition.new(:song, :representable => false, :extend => Object).representable? }
54
+ it { assert Definition.new(:song, :extend => Object).representable? }
55
+ it { refute Definition.new(:song).representable? }
56
+ end
57
+
58
+
34
59
  it "responds to #getter and returns string" do
35
60
  assert_equal "songs", @def.getter
36
61
  end
@@ -43,18 +68,14 @@ class DefinitionTest < MiniTest::Spec
43
68
  assert_equal :"songs=", @def.setter
44
69
  end
45
70
 
46
- it "responds to #deserialize_class" do
47
- assert_equal nil, @def.deserialize_class
48
- end
49
-
50
71
  describe "#clone" do
51
72
  it "clones @options" do
52
- @def.options[:volume] = 9
73
+ @def.merge!(:volume => 9)
53
74
  cloned = @def.clone
54
- cloned.options[:volume] = 8
75
+ cloned.merge!(:volume => 8)
55
76
 
56
- assert_equal @def.options[:volume], 9
57
- assert_equal cloned.options[:volume], 8
77
+ assert_equal @def[:volume], 9
78
+ assert_equal cloned[:volume], 8
58
79
  end
59
80
  end
60
81
  end
@@ -178,18 +199,18 @@ class DefinitionTest < MiniTest::Spec
178
199
 
179
200
  describe "#binding" do
180
201
  it "returns true when :binding is set" do
181
- assert Representable::Definition.new(:songs, :binding => Object).binding
202
+ assert Representable::Definition.new(:songs, :binding => Object)[:binding]
182
203
  end
183
204
 
184
205
  it "returns false when :binding is not set" do
185
- assert !Representable::Definition.new(:songs).binding
206
+ assert !Representable::Definition.new(:songs)[:binding]
186
207
  end
187
208
  end
188
209
 
189
210
  describe "#create_binding" do
190
211
  it "executes the block (without special context)" do
191
212
  definition = Representable::Definition.new(:title, :binding => lambda { |*args| @binding = Representable::Binding.new(*args) })
192
- definition.create_binding(object=Object.new).must_equal @binding
213
+ definition.create_binding(object=Object.new, nil, nil).must_equal @binding
193
214
  @binding.instance_variable_get(:@represented).must_equal object
194
215
  end
195
216
  end
@@ -203,34 +224,21 @@ class DefinitionTest < MiniTest::Spec
203
224
  assert @def.array?
204
225
  end
205
226
 
206
- it "responds to #deserialize_class" do
207
- assert_equal nil, @def.deserialize_class
208
- end
209
-
210
227
  it "responds to #default" do
211
228
  assert_equal nil, @def.send(:default)
212
229
  end
213
230
  end
214
231
 
215
- describe ":class => Item" do
216
- before do
217
- @def = Representable::Definition.new(:songs, :class => Hash)
218
- end
219
-
220
- it "responds to #deserialize_class" do
221
- assert_equal Hash, @def.deserialize_class
222
- end
223
- end
224
232
 
225
233
  describe ":default => value" do
226
234
  it "responds to #default" do
227
235
  @def = Representable::Definition.new(:song)
228
- assert_equal nil, @def.send(:default)
236
+ assert_equal nil, @def[:default]
229
237
  end
230
238
 
231
239
  it "accepts a default value" do
232
240
  @def = Representable::Definition.new(:song, :default => "Atheist Peace")
233
- assert_equal "Atheist Peace", @def.send(:default)
241
+ assert_equal "Atheist Peace", @def[:default]
234
242
  end
235
243
  end
236
244
 
@@ -251,12 +259,15 @@ class DefinitionTest < MiniTest::Spec
251
259
  end
252
260
 
253
261
  it "responds to #binding" do
254
- assert_equal subject.binding, Object
262
+ assert_equal subject[:binding], Object
255
263
  end
256
264
  end
257
265
 
258
- describe "#sync?" do
259
- it { Representable::Definition.new(:song).sync?.must_equal false }
260
- it { Representable::Definition.new(:song, :parse_strategy => :sync).sync?.must_equal true }
266
+ describe "#[]=" do
267
+ it "raises exception since it's deprecated" do
268
+ assert_raises NoMethodError do
269
+ Definition.new(:title)[:extend] = Module.new # use merge! after initialize.
270
+ end
271
+ end
261
272
  end
262
273
  end