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