representable 1.7.7 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +42 -8
- data/README.md +208 -55
- data/Rakefile +0 -6
- data/lib/representable.rb +39 -43
- data/lib/representable/binding.rb +59 -37
- data/lib/representable/bindings/hash_bindings.rb +3 -4
- data/lib/representable/bindings/xml_bindings.rb +10 -10
- data/lib/representable/bindings/yaml_bindings.rb +2 -2
- data/lib/representable/coercion.rb +1 -1
- data/lib/representable/config.rb +11 -5
- data/lib/representable/definition.rb +67 -35
- data/lib/representable/deserializer.rb +23 -27
- data/lib/representable/hash.rb +15 -4
- data/lib/representable/hash/allow_symbols.rb +27 -0
- data/lib/representable/json.rb +0 -1
- data/lib/representable/json/collection.rb +0 -2
- data/lib/representable/mapper.rb +6 -13
- data/lib/representable/parse_strategies.rb +57 -0
- data/lib/representable/readable_writeable.rb +29 -0
- data/lib/representable/serializer.rb +9 -4
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +1 -1
- data/lib/representable/xml/collection.rb +0 -2
- data/lib/representable/yaml.rb +0 -1
- data/representable.gemspec +1 -0
- data/test/as_test.rb +43 -0
- data/test/class_test.rb +124 -0
- data/test/config_test.rb +13 -3
- data/test/decorator_scope_test.rb +28 -0
- data/test/definition_test.rb +46 -35
- data/test/exec_context_test.rb +93 -0
- data/test/generic_test.rb +0 -154
- data/test/getter_setter_test.rb +28 -0
- data/test/hash_bindings_test.rb +35 -35
- data/test/hash_test.rb +0 -20
- data/test/if_test.rb +78 -0
- data/test/inherit_test.rb +21 -1
- data/test/inheritance_test.rb +1 -1
- data/test/inline_test.rb +40 -2
- data/test/instance_test.rb +286 -0
- data/test/is_representable_test.rb +77 -0
- data/test/json_test.rb +6 -29
- data/test/nested_test.rb +30 -0
- data/test/parse_strategy_test.rb +249 -0
- data/test/pass_options_test.rb +27 -0
- data/test/prepare_test.rb +67 -0
- data/test/reader_writer_test.rb +19 -0
- data/test/representable_test.rb +25 -265
- data/test/stringify_hash_test.rb +41 -0
- data/test/test_helper.rb +12 -4
- data/test/wrap_test.rb +48 -0
- data/test/xml_bindings_test.rb +37 -37
- data/test/xml_test.rb +14 -14
- metadata +94 -30
- data/lib/representable/deprecations.rb +0 -4
- 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
|
6
|
-
|
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
|
-
|
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
|
data/lib/representable/xml.rb
CHANGED
@@ -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
|
data/lib/representable/yaml.rb
CHANGED
data/representable.gemspec
CHANGED
data/test/as_test.rb
ADDED
@@ -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
|
data/test/class_test.rb
ADDED
@@ -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.
|
data/test/config_test.rb
CHANGED
@@ -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
|
data/test/definition_test.rb
CHANGED
@@ -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
|
-
|
10
|
-
|
11
|
-
|
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.
|
73
|
+
@def.merge!(:volume => 9)
|
53
74
|
cloned = @def.clone
|
54
|
-
cloned.
|
75
|
+
cloned.merge!(:volume => 8)
|
55
76
|
|
56
|
-
assert_equal @def
|
57
|
-
assert_equal cloned
|
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)
|
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)
|
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
|
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
|
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
|
262
|
+
assert_equal subject[:binding], Object
|
255
263
|
end
|
256
264
|
end
|
257
265
|
|
258
|
-
describe "#
|
259
|
-
it
|
260
|
-
|
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
|