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