representable 1.3.4 → 1.3.5
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.
- 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
|