representable 1.3.4 → 1.3.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.textile +4 -0
- data/README.md +25 -12
- data/TODO +11 -0
- data/lib/representable.rb +32 -36
- data/lib/representable/binding.rb +42 -19
- data/lib/representable/version.rb +1 -1
- data/representable.gemspec +2 -2
- data/test/representable_test.rb +93 -75
- metadata +2 -3
- data/lib/representable/readable_writeable.rb +0 -29
data/CHANGES.textile
CHANGED
data/README.md
CHANGED
@@ -65,7 +65,7 @@ If your property name doesn't match the name in the document, use the `:as` opti
|
|
65
65
|
property :title, as: :name
|
66
66
|
property :track
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
song.to_json #=> {"name":"Fallout","track":1}
|
70
70
|
|
71
71
|
|
@@ -207,7 +207,7 @@ Sometimes it's useful to override accessors to customize output or parsing.
|
|
207
207
|
|
208
208
|
module AlbumRepresenter
|
209
209
|
include Representable::JSON
|
210
|
-
|
210
|
+
|
211
211
|
property :name
|
212
212
|
collection :songs
|
213
213
|
|
@@ -260,9 +260,9 @@ Given we not only have songs, but also cover songs.
|
|
260
260
|
|
261
261
|
And a non-homogenous collection of songs.
|
262
262
|
|
263
|
-
songs = [ Song.new(title: "Weirdo", track: 5),
|
263
|
+
songs = [ Song.new(title: "Weirdo", track: 5),
|
264
264
|
CoverSong.new(title: "Truth Hits Everybody", track: 6, copyright: "The Police")]
|
265
|
-
|
265
|
+
|
266
266
|
album = Album.new(name: "Incognito", songs: songs)
|
267
267
|
|
268
268
|
|
@@ -286,7 +286,7 @@ Rendering heterogenous collections usually implies that you also need to parse t
|
|
286
286
|
include Representable::JSON
|
287
287
|
|
288
288
|
property :name
|
289
|
-
collection :songs,
|
289
|
+
collection :songs,
|
290
290
|
:extend => ...,
|
291
291
|
:class => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong : Song }
|
292
292
|
end
|
@@ -299,7 +299,7 @@ If this is not enough, you may override the entire object creation process using
|
|
299
299
|
include Representable::JSON
|
300
300
|
|
301
301
|
property :name
|
302
|
-
collection :songs,
|
302
|
+
collection :songs,
|
303
303
|
:extend => ...,
|
304
304
|
:instance => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong.new : Song.new(original: true) }
|
305
305
|
end
|
@@ -308,7 +308,7 @@ If this is not enough, you may override the entire object creation process using
|
|
308
308
|
## Hashes
|
309
309
|
|
310
310
|
As an addition to single properties and collections representable also offers to represent hash attributes.
|
311
|
-
|
311
|
+
|
312
312
|
module SongRepresenter
|
313
313
|
include Representable::JSON
|
314
314
|
|
@@ -318,7 +318,7 @@ As an addition to single properties and collections representable also offers to
|
|
318
318
|
|
319
319
|
Song.new(title: "Bliss", ratings: {"Rolling Stone" => 4.9, "FryZine" => 4.5}).
|
320
320
|
extend(SongRepresenter).to_json
|
321
|
-
|
321
|
+
|
322
322
|
#=> {"title":"Bliss","ratings":{"Rolling Stone":4.9,"FryZine":4.5}}
|
323
323
|
|
324
324
|
|
@@ -400,7 +400,7 @@ You can also map properties to tag attributes in representable.
|
|
400
400
|
property :title, attribute: true
|
401
401
|
property :track, attribute: true
|
402
402
|
end
|
403
|
-
|
403
|
+
|
404
404
|
Song.new(title: "American Idle").to_xml
|
405
405
|
#=> <song title="American Idle" />
|
406
406
|
|
@@ -418,7 +418,7 @@ It is sometimes unavoidable to wrap tag lists in a container tag.
|
|
418
418
|
|
419
419
|
Note that `:wrap` defines the container tag name.
|
420
420
|
|
421
|
-
Album.new.to_xml #=>
|
421
|
+
Album.new.to_xml #=>
|
422
422
|
<album>
|
423
423
|
<songs>
|
424
424
|
<song>Laundry Basket</song>
|
@@ -445,6 +445,19 @@ I do not recommend this approach as it bloats your domain classes with represent
|
|
445
445
|
|
446
446
|
Here's a quick overview about other available options for `#property` and its bro `#collection`.
|
447
447
|
|
448
|
+
|
449
|
+
### Overriding Read And Write
|
450
|
+
|
451
|
+
This can be handy if a property needs to be compiled from several fragments. The lambda has access to the entire object document (either hash or `Nokogiri` node) and user options.
|
452
|
+
|
453
|
+
property :title, :writer => lambda { |doc, args| doc["title"] = title || original_title }
|
454
|
+
|
455
|
+
When using the `:writer` option it is up to you to add fragments to the `doc` - representable won't add anything for this property.
|
456
|
+
|
457
|
+
The same works for parsing using `:reader`.
|
458
|
+
|
459
|
+
property :title, :reader => lambda { |doc, args| self.title = doc["title"] || doc["name"] }
|
460
|
+
|
448
461
|
### Read/Write Restrictions
|
449
462
|
|
450
463
|
Using the `:readable` and `:writeable` options access to properties can be restricted.
|
@@ -472,7 +485,7 @@ You can also define conditions on properties using `:if`, making them being cons
|
|
472
485
|
property :title
|
473
486
|
property :track, if: lambda { track > 0 }
|
474
487
|
end
|
475
|
-
|
488
|
+
|
476
489
|
When rendering or parsing, the `track` property is considered only if track is valid. Note that the block is executed in instance context, giving you access to instance methods.
|
477
490
|
|
478
491
|
As always, the block retrieves your options. Given this render call
|
@@ -505,7 +518,7 @@ Use the `:type` option to specify the conversion target. Note that `:default` st
|
|
505
518
|
module SongRepresenter
|
506
519
|
include Representable::JSON
|
507
520
|
include Representable::Coercion
|
508
|
-
|
521
|
+
|
509
522
|
property :title
|
510
523
|
property :recorded_at, :type => DateTime, :default => "May 12th, 2012"
|
511
524
|
end
|
data/TODO
CHANGED
@@ -15,3 +15,14 @@ document `XML::AttributeHash` etc
|
|
15
15
|
* Song < OpenStruct in test_helper
|
16
16
|
|
17
17
|
* have representable-options (:include, :exclude) and user-options
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
def compile_fragment(doc)
|
23
|
+
module ReaderWriter
|
24
|
+
def compile_fragment(doc)
|
25
|
+
do whatever
|
26
|
+
super
|
27
|
+
end
|
28
|
+
=> do that for all "features" (what parts would that be?: getter/setter, reader/writer, readable/writeable )?
|
data/lib/representable.rb
CHANGED
@@ -28,18 +28,18 @@ require 'representable/feature/readable_writeable'
|
|
28
28
|
# hero.extend(HeroRepresenter).to_json
|
29
29
|
module Representable
|
30
30
|
attr_writer :representable_attrs
|
31
|
-
|
31
|
+
|
32
32
|
def self.included(base)
|
33
33
|
base.class_eval do
|
34
34
|
extend ClassInclusions, ModuleExtensions
|
35
35
|
extend ClassMethods
|
36
36
|
extend ClassMethods::Declarations
|
37
|
-
|
37
|
+
|
38
38
|
include Deprecations
|
39
39
|
include Feature::ReadableWriteable
|
40
40
|
end
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
# Reads values from +doc+ and sets properties accordingly.
|
44
44
|
def update_properties_from(doc, options, format)
|
45
45
|
representable_bindings_for(format, options).each do |bin|
|
@@ -47,7 +47,7 @@ module Representable
|
|
47
47
|
end
|
48
48
|
self
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
private
|
52
52
|
# Compiles the document going through all properties.
|
53
53
|
def create_representation_with(doc, options, format)
|
@@ -70,16 +70,16 @@ private
|
|
70
70
|
# Checks and returns if the property should be included.
|
71
71
|
def skip_property?(binding, options)
|
72
72
|
return true if skip_excluded_property?(binding, options) # no need for further evaluation when :exclude'ed
|
73
|
-
|
73
|
+
|
74
74
|
skip_conditional_property?(binding)
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
def skip_excluded_property?(binding, options)
|
78
78
|
return unless props = options[:exclude] || options[:include]
|
79
79
|
res = props.include?(binding.name.to_sym)
|
80
80
|
options[:include] ? !res : res
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
def skip_conditional_property?(binding)
|
84
84
|
# TODO: move to Binding.
|
85
85
|
return unless condition = binding.options[:if]
|
@@ -89,30 +89,26 @@ private
|
|
89
89
|
|
90
90
|
not instance_exec(*args, &condition)
|
91
91
|
end
|
92
|
-
|
93
|
-
#
|
92
|
+
|
93
|
+
# TODO: remove in 1.4.
|
94
94
|
def compile_fragment(bin, doc)
|
95
|
-
|
96
|
-
|
97
|
-
bin.write_fragment(doc, value)
|
95
|
+
bin.compile_fragment(doc)
|
98
96
|
end
|
99
|
-
|
100
|
-
#
|
97
|
+
|
98
|
+
# TODO: remove in 1.4.
|
101
99
|
def uncompile_fragment(bin, doc)
|
102
|
-
bin.
|
103
|
-
bin.set(value)
|
104
|
-
end
|
100
|
+
bin.uncompile_fragment(doc)
|
105
101
|
end
|
106
102
|
|
107
103
|
def representable_attrs
|
108
104
|
@representable_attrs ||= self.class.representable_attrs # DISCUSS: copy, or better not?
|
109
105
|
end
|
110
|
-
|
106
|
+
|
111
107
|
def representable_bindings_for(format, options)
|
112
108
|
options = cleanup_options(options) # FIXME: make representable-options and user-options two different hashes.
|
113
109
|
representable_attrs.map {|attr| format.build(attr, self, options) }
|
114
110
|
end
|
115
|
-
|
111
|
+
|
116
112
|
# Returns the wrapper for the representation. Mostly used in XML.
|
117
113
|
def representation_wrap
|
118
114
|
representable_attrs.wrap_for(self.class.name)
|
@@ -122,14 +118,14 @@ private
|
|
122
118
|
def cleanup_options(options) # TODO: remove me.
|
123
119
|
options.reject { |k,v| [:include, :exclude].include?(k) }
|
124
120
|
end
|
125
|
-
|
121
|
+
|
126
122
|
module ClassInclusions
|
127
123
|
def included(base)
|
128
124
|
super
|
129
125
|
base.representable_attrs.inherit(representable_attrs)
|
130
126
|
end
|
131
127
|
end
|
132
|
-
|
128
|
+
|
133
129
|
module ModuleExtensions
|
134
130
|
# Copies the representable_attrs to the extended object.
|
135
131
|
def extended(object)
|
@@ -137,26 +133,26 @@ private
|
|
137
133
|
object.representable_attrs=(representable_attrs)
|
138
134
|
end
|
139
135
|
end
|
140
|
-
|
141
|
-
|
136
|
+
|
137
|
+
|
142
138
|
module ClassMethods
|
143
|
-
# Create and yield object and options. Called in .from_json and friends.
|
139
|
+
# Create and yield object and options. Called in .from_json and friends.
|
144
140
|
def create_represented(document, *args)
|
145
141
|
new.tap do |represented|
|
146
142
|
yield represented, *args if block_given?
|
147
143
|
end
|
148
144
|
end
|
149
|
-
|
150
|
-
|
145
|
+
|
146
|
+
|
151
147
|
module Declarations
|
152
148
|
def representable_attrs
|
153
149
|
@representable_attrs ||= build_config
|
154
150
|
end
|
155
|
-
|
151
|
+
|
156
152
|
def representation_wrap=(name)
|
157
153
|
representable_attrs.wrap = name
|
158
154
|
end
|
159
|
-
|
155
|
+
|
160
156
|
# Declares a represented document node, which is usually a XML tag or a JSON key.
|
161
157
|
#
|
162
158
|
# Examples:
|
@@ -171,7 +167,7 @@ private
|
|
171
167
|
def property(name, options={})
|
172
168
|
representable_attrs << definition_class.new(name, options)
|
173
169
|
end
|
174
|
-
|
170
|
+
|
175
171
|
# Declares a represented document node collection.
|
176
172
|
#
|
177
173
|
# Examples:
|
@@ -183,14 +179,14 @@ private
|
|
183
179
|
options[:collection] = true
|
184
180
|
property(name, options)
|
185
181
|
end
|
186
|
-
|
182
|
+
|
187
183
|
def hash(name=nil, options={})
|
188
184
|
return super() unless name # allow Object.hash.
|
189
|
-
|
185
|
+
|
190
186
|
options[:hash] = true
|
191
187
|
property(name, options)
|
192
188
|
end
|
193
|
-
|
189
|
+
|
194
190
|
private
|
195
191
|
def definition_class
|
196
192
|
Definition
|
@@ -201,19 +197,19 @@ private
|
|
201
197
|
end
|
202
198
|
end
|
203
199
|
end
|
204
|
-
|
205
|
-
|
200
|
+
|
201
|
+
|
206
202
|
# NOTE: the API of Config is subject to change so don't rely too much on this private object.
|
207
203
|
class Config < Array
|
208
204
|
attr_accessor :wrap
|
209
|
-
|
205
|
+
|
210
206
|
# Computes the wrap string or returns false.
|
211
207
|
def wrap_for(name)
|
212
208
|
return unless wrap
|
213
209
|
return infer_name_for(name) if wrap === true
|
214
210
|
wrap
|
215
211
|
end
|
216
|
-
|
212
|
+
|
217
213
|
def clone
|
218
214
|
self.class.new(collect { |d| d.clone })
|
219
215
|
end
|
@@ -11,7 +11,7 @@ module Representable
|
|
11
11
|
return definition.create_binding(*args) if definition.binding
|
12
12
|
build_for(definition, *args)
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def definition # TODO: remove in 1.4.
|
16
16
|
raise "Binding#definition is no longer supported as all Definition methods are now delegated automatically."
|
17
17
|
end
|
@@ -23,20 +23,35 @@ module Representable
|
|
23
23
|
end
|
24
24
|
|
25
25
|
attr_reader :user_options, :represented # TODO: make private/remove.
|
26
|
-
|
26
|
+
|
27
27
|
# Main entry point for rendering/parsing a property object.
|
28
28
|
def serialize(value)
|
29
29
|
value
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
def deserialize(fragment)
|
33
33
|
fragment
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
|
+
# Retrieve value and write fragment to the doc.
|
37
|
+
def compile_fragment(doc)
|
38
|
+
return represented_exec_for(:writer, doc) if options[:writer]
|
39
|
+
|
40
|
+
write_fragment(doc, get)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Parse value from doc and update the model property.
|
44
|
+
def uncompile_fragment(doc)
|
45
|
+
return represented_exec_for(:reader, doc) if options[:reader]
|
46
|
+
|
47
|
+
read_fragment(doc) do |value|
|
48
|
+
set(value)
|
49
|
+
end
|
50
|
+
end
|
36
51
|
|
37
52
|
def write_fragment(doc, value)
|
38
53
|
value = default_for(value)
|
39
|
-
|
54
|
+
|
40
55
|
write_fragment_for(value, doc)
|
41
56
|
end
|
42
57
|
|
@@ -47,7 +62,7 @@ module Representable
|
|
47
62
|
|
48
63
|
def read_fragment(doc)
|
49
64
|
value = read_fragment_for(doc)
|
50
|
-
|
65
|
+
|
51
66
|
if value == FragmentNotFound
|
52
67
|
return unless has_default?
|
53
68
|
value = default
|
@@ -61,16 +76,23 @@ module Representable
|
|
61
76
|
end
|
62
77
|
|
63
78
|
def get
|
64
|
-
return
|
79
|
+
return represented_exec_for(:getter) if options[:getter]
|
65
80
|
represented.send(getter)
|
66
81
|
end
|
67
82
|
|
68
83
|
def set(value)
|
69
|
-
value =
|
84
|
+
value = represented_exec_for(:setter, value) if options[:setter]
|
70
85
|
represented.send(setter, value)
|
71
86
|
end
|
72
|
-
|
73
|
-
|
87
|
+
|
88
|
+
private
|
89
|
+
# Execute the block for +option_name+ on the represented object.
|
90
|
+
def represented_exec_for(option_name, *args)
|
91
|
+
return unless options[option_name]
|
92
|
+
represented.instance_exec(*args+[user_options], &options[option_name])
|
93
|
+
end
|
94
|
+
|
95
|
+
|
74
96
|
# Hooks into #serialize and #deserialize to extend typed properties
|
75
97
|
# at runtime.
|
76
98
|
module Extend
|
@@ -78,11 +100,11 @@ module Representable
|
|
78
100
|
def serialize(*)
|
79
101
|
extend_for(super)
|
80
102
|
end
|
81
|
-
|
103
|
+
|
82
104
|
def deserialize(*)
|
83
105
|
extend_for(super)
|
84
106
|
end
|
85
|
-
|
107
|
+
|
86
108
|
def extend_for(object)
|
87
109
|
if mod = representer_module_for(object) # :extend.
|
88
110
|
object.extend(*mod)
|
@@ -90,7 +112,7 @@ module Representable
|
|
90
112
|
|
91
113
|
object
|
92
114
|
end
|
93
|
-
|
115
|
+
|
94
116
|
private
|
95
117
|
def representer_module_for(object, *args)
|
96
118
|
call_proc_for(representer_module, object) # TODO: how to pass additional data to the computing block?`
|
@@ -98,24 +120,25 @@ module Representable
|
|
98
120
|
|
99
121
|
def call_proc_for(proc, *args)
|
100
122
|
return proc unless proc.is_a?(Proc)
|
123
|
+
# DISCUSS: use represented_exec_for here?
|
101
124
|
@represented.instance_exec(*args, &proc)
|
102
125
|
end
|
103
126
|
end
|
104
|
-
|
127
|
+
|
105
128
|
module Object
|
106
129
|
include Binding::Extend # provides #serialize/#deserialize with extend.
|
107
|
-
|
130
|
+
|
108
131
|
def serialize(object)
|
109
132
|
return object if object.nil?
|
110
|
-
|
133
|
+
|
111
134
|
super.send(serialize_method, @user_options.merge!({:wrap => false})) # TODO: pass :binding => self
|
112
135
|
end
|
113
|
-
|
136
|
+
|
114
137
|
def deserialize(data)
|
115
138
|
# DISCUSS: does it make sense to skip deserialization of nil-values here?
|
116
139
|
super(create_object(data)).send(deserialize_method, data, @user_options)
|
117
140
|
end
|
118
|
-
|
141
|
+
|
119
142
|
def create_object(fragment)
|
120
143
|
instance_for(fragment) or class_for(fragment)
|
121
144
|
end
|
@@ -131,7 +154,7 @@ module Representable
|
|
131
154
|
end
|
132
155
|
|
133
156
|
def instance_for(fragment, *args)
|
134
|
-
return unless options[:instance]
|
157
|
+
return unless options[:instance]
|
135
158
|
call_proc_for(options[:instance], fragment)
|
136
159
|
end
|
137
160
|
end
|
data/representable.gemspec
CHANGED
@@ -13,12 +13,12 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.homepage = "http://representable.apotomo.de"
|
14
14
|
s.summary = %q{Maps representation documents from and to Ruby objects. Includes XML and JSON support, plain properties, collections and compositions.}
|
15
15
|
s.description = %q{Maps representation documents from and to Ruby objects. Includes XML and JSON support, plain properties, collections and compositions.}
|
16
|
-
|
16
|
+
|
17
17
|
s.files = `git ls-files`.split("\n")
|
18
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
20
|
s.require_paths = ["lib"]
|
21
|
-
|
21
|
+
|
22
22
|
s.add_dependency "nokogiri"
|
23
23
|
s.add_dependency "multi_json"
|
24
24
|
|
data/test/representable_test.rb
CHANGED
@@ -6,55 +6,55 @@ class RepresentableTest < MiniTest::Spec
|
|
6
6
|
property :name
|
7
7
|
attr_accessor :name
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
class PunkBand < Band
|
11
11
|
property :street_cred
|
12
12
|
attr_accessor :street_cred
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
module BandRepresentation
|
16
16
|
include Representable
|
17
|
-
|
17
|
+
|
18
18
|
property :name
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
module PunkBandRepresentation
|
22
22
|
include Representable
|
23
23
|
include BandRepresentation
|
24
|
-
|
24
|
+
|
25
25
|
property :street_cred
|
26
26
|
end
|
27
|
-
|
28
|
-
|
27
|
+
|
28
|
+
|
29
29
|
describe "#representable_attrs" do
|
30
30
|
it "responds to #representable_attrs" do
|
31
31
|
assert_equal 1, Band.representable_attrs.size
|
32
32
|
assert_equal "name", Band.representable_attrs.first.name
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
describe "in module" do
|
36
36
|
it "returns definitions" do
|
37
37
|
assert_equal 1, BandRepresentation.representable_attrs.size
|
38
38
|
assert_equal "name", BandRepresentation.representable_attrs.first.name
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
it "inherits to including modules" do
|
42
42
|
assert_equal 2, PunkBandRepresentation.representable_attrs.size
|
43
43
|
assert_equal "name", PunkBandRepresentation.representable_attrs.first.name
|
44
44
|
assert_equal "street_cred", PunkBandRepresentation.representable_attrs.last.name
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
it "inherits to including class" do
|
48
48
|
band = Class.new do
|
49
49
|
include Representable
|
50
50
|
include PunkBandRepresentation
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
assert_equal 2, band.representable_attrs.size
|
54
54
|
assert_equal "name", band.representable_attrs.first.name
|
55
55
|
assert_equal "street_cred", band.representable_attrs.last.name
|
56
56
|
end
|
57
|
-
|
57
|
+
|
58
58
|
it "allows including the concrete representer module later" do
|
59
59
|
vd = class VD
|
60
60
|
attr_accessor :name, :street_cred
|
@@ -65,7 +65,7 @@ class RepresentableTest < MiniTest::Spec
|
|
65
65
|
vd.street_cred = 1
|
66
66
|
assert_json "{\"name\":\"Vention Dention\",\"street_cred\":1}", vd.to_json
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
#it "allows including the concrete representer module only" do
|
70
70
|
# require 'representable/json'
|
71
71
|
# module RockBandRepresentation
|
@@ -78,29 +78,29 @@ class RepresentableTest < MiniTest::Spec
|
|
78
78
|
# vd.name = "Van Halen"
|
79
79
|
# assert_equal "{\"name\":\"Van Halen\"}", vd.to_json
|
80
80
|
#end
|
81
|
-
|
81
|
+
|
82
82
|
it "doesn't share inherited properties between family members" do
|
83
83
|
parent = Module.new do
|
84
84
|
include Representable
|
85
85
|
property :id
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
child = Module.new do
|
89
89
|
include Representable
|
90
90
|
include parent
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
assert parent.representable_attrs.first != child.representable_attrs.first, "definitions shouldn't be identical"
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
end
|
97
97
|
end
|
98
|
-
|
99
|
-
|
98
|
+
|
99
|
+
|
100
100
|
describe "Representable" do
|
101
101
|
describe "inheritance" do
|
102
102
|
class CoverSong < OpenStruct
|
103
|
-
end
|
103
|
+
end
|
104
104
|
module SongRepresenter
|
105
105
|
include Representable::Hash
|
106
106
|
property :name
|
@@ -124,36 +124,36 @@ class RepresentableTest < MiniTest::Spec
|
|
124
124
|
include Representable::XML
|
125
125
|
include Representable::JSON
|
126
126
|
include PunkBandRepresentation
|
127
|
-
|
127
|
+
|
128
128
|
self.representation_wrap = "band"
|
129
129
|
attr_accessor :name, :street_cred
|
130
130
|
end
|
131
|
-
|
131
|
+
|
132
132
|
band = Bodyjar.new
|
133
133
|
band.name = "Bodyjar"
|
134
|
-
|
134
|
+
|
135
135
|
assert_json "{\"band\":{\"name\":\"Bodyjar\"}}", band.to_json
|
136
136
|
assert_xml_equal "<band><name>Bodyjar</name></band>", band.to_xml
|
137
137
|
end
|
138
|
-
|
138
|
+
|
139
139
|
it "allows extending with different representers subsequentially" do
|
140
140
|
module SongXmlRepresenter
|
141
141
|
include Representable::XML
|
142
142
|
property :name, :from => "name", :attribute => true
|
143
143
|
end
|
144
|
-
|
144
|
+
|
145
145
|
module SongJsonRepresenter
|
146
146
|
include Representable::JSON
|
147
147
|
property :name
|
148
148
|
end
|
149
|
-
|
149
|
+
|
150
150
|
@song = Song.new("Days Go By")
|
151
151
|
assert_xml_equal "<song name=\"Days Go By\"/>", @song.extend(SongXmlRepresenter).to_xml
|
152
152
|
assert_json "{\"name\":\"Days Go By\"}", @song.extend(SongJsonRepresenter).to_json
|
153
153
|
end
|
154
154
|
end
|
155
|
-
|
156
|
-
|
155
|
+
|
156
|
+
|
157
157
|
describe "#property" do
|
158
158
|
describe ":from" do
|
159
159
|
# TODO: do this with all options.
|
@@ -166,68 +166,68 @@ class RepresentableTest < MiniTest::Spec
|
|
166
166
|
band = Class.new(Band) { property :friends, :as => :friend }
|
167
167
|
assert_equal "friend", band.representable_attrs.last.from
|
168
168
|
end
|
169
|
-
|
169
|
+
|
170
170
|
it "is infered from the name implicitly" do
|
171
171
|
band = Class.new(Band) { property :friends }
|
172
172
|
assert_equal "friends", band.representable_attrs.last.from
|
173
173
|
end
|
174
174
|
end
|
175
175
|
end
|
176
|
-
|
176
|
+
|
177
177
|
describe "#collection" do
|
178
178
|
class RockBand < Band
|
179
179
|
collection :albums
|
180
180
|
end
|
181
|
-
|
181
|
+
|
182
182
|
it "creates correct Definition" do
|
183
183
|
assert_equal "albums", RockBand.representable_attrs.last.name
|
184
184
|
assert RockBand.representable_attrs.last.array?
|
185
185
|
end
|
186
186
|
end
|
187
|
-
|
187
|
+
|
188
188
|
describe "#hash" do
|
189
189
|
it "also responds to the original method" do
|
190
190
|
assert_kind_of Integer, BandRepresentation.hash
|
191
191
|
end
|
192
192
|
end
|
193
|
-
|
194
|
-
|
193
|
+
|
194
|
+
|
195
195
|
describe "#representation_wrap" do
|
196
196
|
class HardcoreBand
|
197
197
|
include Representable
|
198
198
|
end
|
199
|
-
|
199
|
+
|
200
200
|
class SoftcoreBand < HardcoreBand
|
201
201
|
end
|
202
|
-
|
202
|
+
|
203
203
|
before do
|
204
204
|
@band = HardcoreBand.new
|
205
205
|
end
|
206
|
-
|
207
|
-
|
206
|
+
|
207
|
+
|
208
208
|
it "returns false per default" do
|
209
209
|
assert_equal nil, SoftcoreBand.new.send(:representation_wrap)
|
210
210
|
end
|
211
|
-
|
211
|
+
|
212
212
|
it "infers a printable class name if set to true" do
|
213
213
|
HardcoreBand.representation_wrap = true
|
214
214
|
assert_equal "hardcore_band", @band.send(:representation_wrap)
|
215
215
|
end
|
216
|
-
|
216
|
+
|
217
217
|
it "can be set explicitely" do
|
218
218
|
HardcoreBand.representation_wrap = "breach"
|
219
219
|
assert_equal "breach", @band.send(:representation_wrap)
|
220
220
|
end
|
221
221
|
end
|
222
|
-
|
223
|
-
|
222
|
+
|
223
|
+
|
224
224
|
describe "#definition_class" do
|
225
225
|
it "returns Definition class" do
|
226
226
|
assert_equal Representable::Definition, Band.send(:definition_class)
|
227
227
|
end
|
228
228
|
end
|
229
229
|
|
230
|
-
|
230
|
+
|
231
231
|
# DISCUSS: i don't like the JSON requirement here, what about some generic test module?
|
232
232
|
class PopBand
|
233
233
|
include Representable::JSON
|
@@ -240,19 +240,19 @@ class RepresentableTest < MiniTest::Spec
|
|
240
240
|
before do
|
241
241
|
@band = PopBand.new
|
242
242
|
end
|
243
|
-
|
243
|
+
|
244
244
|
it "copies values from document to object" do
|
245
245
|
@band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {}, Representable::Hash::PropertyBinding)
|
246
246
|
assert_equal "No One's Choice", @band.name
|
247
247
|
assert_equal 2, @band.groupies
|
248
248
|
end
|
249
|
-
|
249
|
+
|
250
250
|
it "accepts :exclude option" do
|
251
251
|
@band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:exclude => [:groupies]}, Representable::Hash::PropertyBinding)
|
252
252
|
assert_equal "No One's Choice", @band.name
|
253
253
|
assert_equal nil, @band.groupies
|
254
254
|
end
|
255
|
-
|
255
|
+
|
256
256
|
it "accepts :include option" do
|
257
257
|
@band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:include => [:groupies]}, Representable::Hash::PropertyBinding)
|
258
258
|
assert_equal 2, @band.groupies
|
@@ -265,11 +265,11 @@ class RepresentableTest < MiniTest::Spec
|
|
265
265
|
assert_equal "Iron Maiden", @band.name
|
266
266
|
assert_equal nil, @band.founders
|
267
267
|
end
|
268
|
-
|
268
|
+
|
269
269
|
it "always returns self" do
|
270
270
|
assert_equal @band, @band.update_properties_from({"name"=>"Nofx"}, {}, Representable::Hash::PropertyBinding)
|
271
271
|
end
|
272
|
-
|
272
|
+
|
273
273
|
it "includes false attributes" do
|
274
274
|
@band.update_properties_from({"groupies"=>false}, {}, Representable::Hash::PropertyBinding)
|
275
275
|
assert_equal false, @band.groupies
|
@@ -281,21 +281,21 @@ class RepresentableTest < MiniTest::Spec
|
|
281
281
|
end
|
282
282
|
@band.update_properties_from({}, {}, Representable::Hash::PropertyBinding)
|
283
283
|
end
|
284
|
-
|
284
|
+
|
285
285
|
it "ignores (no-default) properties not present in the incoming document" do
|
286
|
-
{ Representable::JSON => [{}, Representable::Hash::PropertyBinding],
|
286
|
+
{ Representable::JSON => [{}, Representable::Hash::PropertyBinding],
|
287
287
|
Representable::XML => [xml(%{<band/>}), Representable::XML::PropertyBinding]
|
288
288
|
}.each do |format, config|
|
289
289
|
nested_repr = Module.new do # this module is never applied.
|
290
290
|
include format
|
291
291
|
property :created_at
|
292
292
|
end
|
293
|
-
|
293
|
+
|
294
294
|
repr = Module.new do
|
295
295
|
include format
|
296
296
|
property :name, :class => Object, :extend => nested_repr
|
297
297
|
end
|
298
|
-
|
298
|
+
|
299
299
|
@band = Band.new.extend(repr)
|
300
300
|
@band.update_properties_from(config.first, {}, config.last)
|
301
301
|
assert_equal nil, @band.name, "Failed in #{format}"
|
@@ -335,23 +335,23 @@ class RepresentableTest < MiniTest::Spec
|
|
335
335
|
end
|
336
336
|
end
|
337
337
|
end
|
338
|
-
|
338
|
+
|
339
339
|
describe "#create_representation_with" do
|
340
340
|
before do
|
341
341
|
@band = PopBand.new
|
342
342
|
@band.name = "No One's Choice"
|
343
343
|
@band.groupies = 2
|
344
344
|
end
|
345
|
-
|
345
|
+
|
346
346
|
it "compiles document from properties in object" do
|
347
347
|
assert_equal({"name"=>"No One's Choice", "groupies"=>2}, @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding))
|
348
348
|
end
|
349
|
-
|
349
|
+
|
350
350
|
it "accepts :exclude option" do
|
351
351
|
hash = @band.send(:create_representation_with, {}, {:exclude => [:groupies]}, Representable::Hash::PropertyBinding)
|
352
352
|
assert_equal({"name"=>"No One's Choice"}, hash)
|
353
353
|
end
|
354
|
-
|
354
|
+
|
355
355
|
it "accepts :include option" do
|
356
356
|
hash = @band.send(:create_representation_with, {}, {:include => [:groupies]}, Representable::Hash::PropertyBinding)
|
357
357
|
assert_equal({"groupies"=>2}, hash)
|
@@ -375,7 +375,7 @@ class RepresentableTest < MiniTest::Spec
|
|
375
375
|
@band.groupies = false
|
376
376
|
assert_equal({"name"=>"No One's Choice","groupies"=>false}, @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding))
|
377
377
|
end
|
378
|
-
|
378
|
+
|
379
379
|
describe "when :render_nil is true" do
|
380
380
|
it "includes nil attribute" do
|
381
381
|
mod = Module.new do
|
@@ -383,20 +383,20 @@ class RepresentableTest < MiniTest::Spec
|
|
383
383
|
property :name
|
384
384
|
property :groupies, :render_nil => true
|
385
385
|
end
|
386
|
-
|
386
|
+
|
387
387
|
@band.extend(mod) # FIXME: use clean object.
|
388
388
|
@band.groupies = nil
|
389
389
|
hash = @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding)
|
390
390
|
assert_equal({"name"=>"No One's Choice", "groupies" => nil}, hash)
|
391
391
|
end
|
392
|
-
|
392
|
+
|
393
393
|
it "includes nil attribute without extending" do
|
394
394
|
mod = Module.new do
|
395
395
|
include Representable::JSON
|
396
396
|
property :name
|
397
397
|
property :groupies, :render_nil => true, :extend => BandRepresentation
|
398
398
|
end
|
399
|
-
|
399
|
+
|
400
400
|
@band.extend(mod) # FIXME: use clean object.
|
401
401
|
@band.groupies = nil
|
402
402
|
hash = @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding)
|
@@ -417,33 +417,33 @@ class RepresentableTest < MiniTest::Spec
|
|
417
417
|
to_hash(:include => [:original]).must_equal({"original"=>{"title"=>"Roxanne (Don't Put On The Red Light)"}})
|
418
418
|
end
|
419
419
|
end
|
420
|
-
|
420
|
+
|
421
421
|
describe ":if" do
|
422
422
|
before do
|
423
423
|
@pop = Class.new(PopBand) { attr_accessor :fame }
|
424
424
|
end
|
425
|
-
|
425
|
+
|
426
426
|
it "respects property when condition true" do
|
427
427
|
@pop.class_eval { property :fame, :if => lambda { true } }
|
428
428
|
band = @pop.new
|
429
429
|
band.update_properties_from({"fame"=>"oh yes"}, {}, Representable::Hash::PropertyBinding)
|
430
430
|
assert_equal "oh yes", band.fame
|
431
431
|
end
|
432
|
-
|
432
|
+
|
433
433
|
it "ignores property when condition false" do
|
434
434
|
@pop.class_eval { property :fame, :if => lambda { false } }
|
435
435
|
band = @pop.new
|
436
436
|
band.update_properties_from({"fame"=>"oh yes"}, {}, Representable::Hash::PropertyBinding)
|
437
437
|
assert_equal nil, band.fame
|
438
438
|
end
|
439
|
-
|
439
|
+
|
440
440
|
it "ignores property when :exclude'ed even when condition is true" do
|
441
441
|
@pop.class_eval { property :fame, :if => lambda { true } }
|
442
442
|
band = @pop.new
|
443
443
|
band.update_properties_from({"fame"=>"oh yes"}, {:exclude => [:fame]}, Representable::Hash::PropertyBinding)
|
444
444
|
assert_equal nil, band.fame
|
445
445
|
end
|
446
|
-
|
446
|
+
|
447
447
|
it "executes block in instance context" do
|
448
448
|
@pop.class_eval { property :fame, :if => lambda { groupies } }
|
449
449
|
band = @pop.new
|
@@ -470,8 +470,8 @@ class RepresentableTest < MiniTest::Spec
|
|
470
470
|
|
471
471
|
describe ":getter and :setter" do
|
472
472
|
representer! do
|
473
|
-
property :name,
|
474
|
-
:getter => lambda { |args| "#{args[:welcome]} #{name}" },
|
473
|
+
property :name,
|
474
|
+
:getter => lambda { |args| "#{args[:welcome]} #{name}" },
|
475
475
|
:setter => lambda { |val, args| self.name = "#{args[:welcome]} #{val}" }
|
476
476
|
end
|
477
477
|
|
@@ -486,17 +486,35 @@ class RepresentableTest < MiniTest::Spec
|
|
486
486
|
end
|
487
487
|
end
|
488
488
|
|
489
|
+
describe ":reader and :writer" do
|
490
|
+
representer! do
|
491
|
+
property :name,
|
492
|
+
:writer => lambda { |doc, args| doc["title"] = "#{args[:nr]}) #{name}" },
|
493
|
+
:reader => lambda { |doc, args| self.name = doc["title"].split(") ").last }
|
494
|
+
end
|
495
|
+
|
496
|
+
subject { OpenStruct.new(:name => "Disorder And Disarray").extend(representer) }
|
497
|
+
|
498
|
+
it "uses :writer when rendering" do
|
499
|
+
subject.to_hash(:nr => 14).must_equal({"title" => "14) Disorder And Disarray"})
|
500
|
+
end
|
501
|
+
|
502
|
+
it "uses :reader when parsing" do
|
503
|
+
subject.from_hash({"title" => "15) The Wars End"}).name.must_equal "The Wars End"
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
489
507
|
describe ":extend and :class" do
|
490
508
|
module UpcaseRepresenter
|
491
509
|
def to_hash(*); upcase; end
|
492
510
|
def from_hash(hsh, *args); self.class.new hsh.upcase; end # DISCUSS: from_hash must return self.
|
493
511
|
end
|
494
|
-
module DowncaseRepresenter
|
512
|
+
module DowncaseRepresenter
|
495
513
|
def to_hash(*); downcase; end
|
496
514
|
def from_hash(hsh, *args); hsh.downcase; end
|
497
515
|
end
|
498
516
|
class UpcaseString < String; end
|
499
|
-
|
517
|
+
|
500
518
|
|
501
519
|
describe "lambda blocks" do
|
502
520
|
representer! do
|
@@ -614,29 +632,29 @@ class RepresentableTest < MiniTest::Spec
|
|
614
632
|
OpenStruct.new(:title => "Affliction").extend(representer).to_hash.must_equal({:title => "Affliction"})
|
615
633
|
end
|
616
634
|
end
|
617
|
-
|
635
|
+
|
618
636
|
end
|
619
|
-
|
637
|
+
|
620
638
|
describe "Config" do
|
621
639
|
subject { Representable::Config.new }
|
622
640
|
PunkRock = Class.new
|
623
|
-
|
641
|
+
|
624
642
|
describe "wrapping" do
|
625
643
|
it "returns false per default" do
|
626
644
|
assert_equal nil, subject.wrap_for("Punk")
|
627
645
|
end
|
628
|
-
|
646
|
+
|
629
647
|
it "infers a printable class name if set to true" do
|
630
648
|
subject.wrap = true
|
631
649
|
assert_equal "punk_rock", subject.wrap_for(PunkRock)
|
632
650
|
end
|
633
|
-
|
651
|
+
|
634
652
|
it "can be set explicitely" do
|
635
653
|
subject.wrap = "Descendents"
|
636
654
|
assert_equal "Descendents", subject.wrap_for(PunkRock)
|
637
655
|
end
|
638
656
|
end
|
639
|
-
|
657
|
+
|
640
658
|
describe "clone" do
|
641
659
|
it "clones all definitions" do
|
642
660
|
subject << Object.new
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: representable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|
@@ -184,7 +184,6 @@ files:
|
|
184
184
|
- lib/representable/json.rb
|
185
185
|
- lib/representable/json/collection.rb
|
186
186
|
- lib/representable/json/hash.rb
|
187
|
-
- lib/representable/readable_writeable.rb
|
188
187
|
- lib/representable/version.rb
|
189
188
|
- lib/representable/xml.rb
|
190
189
|
- lib/representable/xml/collection.rb
|
@@ -1,29 +0,0 @@
|
|
1
|
-
module Representable
|
2
|
-
module Feature
|
3
|
-
module ReadableWriteable
|
4
|
-
def deserialize_property(binding, doc, options)
|
5
|
-
return unless binding.writeable?
|
6
|
-
super
|
7
|
-
end
|
8
|
-
|
9
|
-
def serialize_property(binding, doc, options)
|
10
|
-
return unless binding.readable?
|
11
|
-
super
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
# TODO: i hate monkey-patching Definition here since it globally adds this options. However, for now this should be ok :-)
|
17
|
-
class Definition
|
18
|
-
# TODO: make this generic like `option :writeable, :default => true`
|
19
|
-
def writeable?
|
20
|
-
return options[:writeable] if options.has_key?(:writeable)
|
21
|
-
true
|
22
|
-
end
|
23
|
-
|
24
|
-
def readable?
|
25
|
-
return options[:readable] if options.has_key?(:readable)
|
26
|
-
true
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|