representable 1.2.9 → 1.3.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.
- data/CHANGES.textile +6 -0
- data/README.md +485 -0
- data/TODO +11 -1
- data/lib/representable.rb +13 -22
- data/lib/representable/binding.rb +32 -4
- data/lib/representable/bindings/hash_bindings.rb +4 -4
- data/lib/representable/bindings/xml_bindings.rb +13 -10
- data/lib/representable/bindings/yaml_bindings.rb +3 -6
- data/lib/representable/deprecations.rb +0 -7
- data/lib/representable/hash_methods.rb +2 -2
- data/lib/representable/json/collection.rb +2 -2
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml/collection.rb +4 -4
- data/test/example.rb +152 -21
- data/test/json_test.rb +3 -2
- data/test/representable_test.rb +56 -20
- data/test/test_helper.rb +9 -0
- data/test/xml_test.rb +34 -12
- metadata +3 -5
- data/.rspec +0 -1
- data/LICENSE +0 -20
- data/README.rdoc +0 -416
data/TODO
CHANGED
@@ -4,4 +4,14 @@ class: |key, hsh|
|
|
4
4
|
* Allow passing options to Binding#serialize.
|
5
5
|
serialize(.., options{:exclude => ..})
|
6
6
|
|
7
|
-
* Allow :writer and :reader / :getter/:setter
|
7
|
+
* Allow :writer and :reader / :getter/:setter
|
8
|
+
|
9
|
+
document `XML::AttributeHash` etc
|
10
|
+
|
11
|
+
* deprecate :from, make :a(lia)s authorative.
|
12
|
+
* cleanup ReadableWriteable
|
13
|
+
* deprecate Representable::*::ClassMethods (::from_hash and friends)
|
14
|
+
|
15
|
+
* Song < OpenStruct in test_helper
|
16
|
+
|
17
|
+
* have representable-options (:include, :exclude) and user-options
|
data/lib/representable.rb
CHANGED
@@ -42,7 +42,7 @@ module Representable
|
|
42
42
|
|
43
43
|
# Reads values from +doc+ and sets properties accordingly.
|
44
44
|
def update_properties_from(doc, options, format)
|
45
|
-
representable_bindings_for(format).each do |bin|
|
45
|
+
representable_bindings_for(format, options).each do |bin|
|
46
46
|
deserialize_property(bin, doc, options)
|
47
47
|
end
|
48
48
|
self
|
@@ -51,7 +51,7 @@ module Representable
|
|
51
51
|
private
|
52
52
|
# Compiles the document going through all properties.
|
53
53
|
def create_representation_with(doc, options, format)
|
54
|
-
representable_bindings_for(format).each do |bin|
|
54
|
+
representable_bindings_for(format, options).each do |bin|
|
55
55
|
serialize_property(bin, doc, options)
|
56
56
|
end
|
57
57
|
doc
|
@@ -88,44 +88,35 @@ private
|
|
88
88
|
# Retrieve value and write fragment to the doc.
|
89
89
|
def compile_fragment(bin, doc)
|
90
90
|
value = send(bin.getter)
|
91
|
-
value = bin.default_for(value)
|
92
91
|
|
93
|
-
|
92
|
+
bin.write_fragment(doc, value)
|
94
93
|
end
|
95
94
|
|
96
95
|
# Parse value from doc and update the model property.
|
97
96
|
def uncompile_fragment(bin, doc)
|
98
|
-
|
99
|
-
|
100
|
-
if value == Binding::FragmentNotFound
|
101
|
-
return unless bin.has_default?
|
102
|
-
value = bin.default
|
97
|
+
bin.read_fragment(doc) do |value|
|
98
|
+
send(bin.setter, value)
|
103
99
|
end
|
104
|
-
|
105
|
-
send(bin.setter, value)
|
106
|
-
end
|
107
|
-
|
108
|
-
def write_fragment_for(bin, value, doc) # DISCUSS: move to Binding?
|
109
|
-
return if bin.skipable_nil_value?(value)
|
110
|
-
bin.write(doc, value)
|
111
|
-
end
|
112
|
-
|
113
|
-
def read_fragment_for(bin, doc) # DISCUSS: move to Binding?
|
114
|
-
bin.read(doc)
|
115
100
|
end
|
116
101
|
|
117
102
|
def representable_attrs
|
118
103
|
@representable_attrs ||= self.class.representable_attrs # DISCUSS: copy, or better not?
|
119
104
|
end
|
120
105
|
|
121
|
-
def representable_bindings_for(format)
|
122
|
-
|
106
|
+
def representable_bindings_for(format, options)
|
107
|
+
options = cleanup_options(options) # FIXME: make representable-options and user-options two different hashes.
|
108
|
+
representable_attrs.map {|attr| format.build_for(attr, self, options) }
|
123
109
|
end
|
124
110
|
|
125
111
|
# Returns the wrapper for the representation. Mostly used in XML.
|
126
112
|
def representation_wrap
|
127
113
|
representable_attrs.wrap_for(self.class.name)
|
128
114
|
end
|
115
|
+
|
116
|
+
private
|
117
|
+
def cleanup_options(options) # TODO: remove me.
|
118
|
+
options.reject { |k,v| [:include, :exclude].include?(k) }
|
119
|
+
end
|
129
120
|
|
130
121
|
module ClassInclusions
|
131
122
|
def included(base)
|
@@ -10,9 +10,10 @@ module Representable
|
|
10
10
|
raise "Binding#definition is no longer supported as all Definition methods are now delegated automatically."
|
11
11
|
end
|
12
12
|
|
13
|
-
def initialize(definition, represented)
|
13
|
+
def initialize(definition, represented, user_options={}) # TODO: remove default arg.
|
14
14
|
super(definition)
|
15
|
-
@represented
|
15
|
+
@represented = represented
|
16
|
+
@user_options = user_options
|
16
17
|
end
|
17
18
|
|
18
19
|
# Main entry point for rendering/parsing a property object.
|
@@ -24,6 +25,33 @@ module Representable
|
|
24
25
|
fragment
|
25
26
|
end
|
26
27
|
|
28
|
+
|
29
|
+
def write_fragment(doc, value)
|
30
|
+
value = default_for(value)
|
31
|
+
|
32
|
+
write_fragment_for(value, doc)
|
33
|
+
end
|
34
|
+
|
35
|
+
def write_fragment_for(value, doc)
|
36
|
+
return if skipable_nil_value?(value)
|
37
|
+
write(doc, value)
|
38
|
+
end
|
39
|
+
|
40
|
+
def read_fragment(doc)
|
41
|
+
value = read_fragment_for(doc)
|
42
|
+
|
43
|
+
if value == FragmentNotFound
|
44
|
+
return unless has_default?
|
45
|
+
value = default
|
46
|
+
end
|
47
|
+
|
48
|
+
yield value
|
49
|
+
end
|
50
|
+
|
51
|
+
def read_fragment_for(doc)
|
52
|
+
read(doc)
|
53
|
+
end
|
54
|
+
|
27
55
|
|
28
56
|
# Hooks into #serialize and #deserialize to extend typed properties
|
29
57
|
# at runtime.
|
@@ -62,12 +90,12 @@ module Representable
|
|
62
90
|
def serialize(object)
|
63
91
|
return object if object.nil?
|
64
92
|
|
65
|
-
super.send(serialize_method, :wrap => false) # TODO: pass :binding => self
|
93
|
+
super.send(serialize_method, @user_options.merge!({:wrap => false})) # TODO: pass :binding => self
|
66
94
|
end
|
67
95
|
|
68
96
|
def deserialize(data)
|
69
97
|
# DISCUSS: does it make sense to skip deserialization of nil-values here?
|
70
|
-
super(create_object(data)).send(deserialize_method, data)
|
98
|
+
super(create_object(data)).send(deserialize_method, data, @user_options)
|
71
99
|
end
|
72
100
|
|
73
101
|
def create_object(fragment)
|
@@ -16,10 +16,10 @@ module Representable
|
|
16
16
|
|
17
17
|
|
18
18
|
class PropertyBinding < Representable::Binding
|
19
|
-
def self.build_for(definition,
|
20
|
-
return CollectionBinding.new(definition,
|
21
|
-
return HashBinding.new(definition,
|
22
|
-
new(definition,
|
19
|
+
def self.build_for(definition, *args) # TODO: remove default arg.
|
20
|
+
return CollectionBinding.new(definition, *args) if definition.array?
|
21
|
+
return HashBinding.new(definition, *args) if definition.hash?
|
22
|
+
new(definition, *args)
|
23
23
|
end
|
24
24
|
|
25
25
|
def initialize(*args) # FIXME. make generic.
|
@@ -24,12 +24,12 @@ module Representable
|
|
24
24
|
|
25
25
|
|
26
26
|
class PropertyBinding < Binding
|
27
|
-
def self.build_for(definition,
|
28
|
-
return CollectionBinding.new(definition,
|
29
|
-
return HashBinding.new(definition,
|
30
|
-
return AttributeHashBinding.new(definition,
|
31
|
-
return AttributeBinding.new(definition,
|
32
|
-
new(definition,
|
27
|
+
def self.build_for(definition, *args)
|
28
|
+
return CollectionBinding.new(definition, *args) if definition.array?
|
29
|
+
return HashBinding.new(definition, *args) if definition.hash? and not definition.options[:use_attributes] # FIXME: hate this.
|
30
|
+
return AttributeHashBinding.new(definition, *args) if definition.hash? and definition.options[:use_attributes]
|
31
|
+
return AttributeBinding.new(definition, *args) if definition.attribute
|
32
|
+
new(definition, *args)
|
33
33
|
end
|
34
34
|
|
35
35
|
def initialize(*args)
|
@@ -48,10 +48,7 @@ module Representable
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def read(node)
|
51
|
-
|
52
|
-
selector = "./#{options[:wrap]}/#{xpath}" if options[:wrap]
|
53
|
-
nodes = node.search(selector)
|
54
|
-
|
51
|
+
nodes = find_nodes(node)
|
55
52
|
return FragmentNotFound if nodes.size == 0 # TODO: write dedicated test!
|
56
53
|
|
57
54
|
deserialize_from(nodes)
|
@@ -83,6 +80,12 @@ module Representable
|
|
83
80
|
from
|
84
81
|
end
|
85
82
|
|
83
|
+
def find_nodes(doc)
|
84
|
+
selector = xpath
|
85
|
+
selector = "#{options[:wrap]}/#{xpath}" if options[:wrap]
|
86
|
+
nodes = doc.xpath(selector)
|
87
|
+
end
|
88
|
+
|
86
89
|
def node_for(parent, name)
|
87
90
|
Nokogiri::XML::Node.new(name.to_s, parent.document)
|
88
91
|
end
|
@@ -19,12 +19,9 @@ module Representable
|
|
19
19
|
end
|
20
20
|
|
21
21
|
class PropertyBinding < Representable::Hash::PropertyBinding
|
22
|
-
def self.build_for(definition,
|
23
|
-
return CollectionBinding.new(definition,
|
24
|
-
|
25
|
-
#return AttributeHashBinding.new(definition) if definition.hash? and definition.options[:use_attributes]
|
26
|
-
#return AttributeBinding.new(definition) if definition.attribute
|
27
|
-
new(definition, represented)
|
22
|
+
def self.build_for(definition, *args)
|
23
|
+
return CollectionBinding.new(definition, *args) if definition.array?
|
24
|
+
new(definition, *args)
|
28
25
|
end
|
29
26
|
|
30
27
|
def initialize(*args) # FIXME. make generic.
|
@@ -1,11 +1,4 @@
|
|
1
1
|
module Representable
|
2
2
|
module Deprecations
|
3
|
-
def skip_excluded_property?(binding, options) # TODO: remove with 1.3.
|
4
|
-
if options[:except]
|
5
|
-
options[:exclude] = options[:except]
|
6
|
-
warn "The :except option is deprecated and will be removed in 1.3. Please use :exclude."
|
7
|
-
end # i wanted a one-liner but failed :)
|
8
|
-
super
|
9
|
-
end
|
10
3
|
end
|
11
4
|
end
|
@@ -8,13 +8,13 @@ module Representable
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def create_representation_with(doc, options, format)
|
11
|
-
bin = representable_bindings_for(format).first
|
11
|
+
bin = representable_bindings_for(format, options).first
|
12
12
|
hash = filter_keys_for(self, options)
|
13
13
|
bin.write(doc, hash)
|
14
14
|
end
|
15
15
|
|
16
16
|
def update_properties_from(doc, options, format)
|
17
|
-
bin = representable_bindings_for(format).first
|
17
|
+
bin = representable_bindings_for(format, options).first
|
18
18
|
hash = filter_keys_for(doc, options)
|
19
19
|
value = bin.deserialize_from(hash)
|
20
20
|
replace(value)
|
@@ -18,12 +18,12 @@ module Representable::JSON
|
|
18
18
|
|
19
19
|
|
20
20
|
def create_representation_with(doc, options, format)
|
21
|
-
bin = representable_bindings_for(format).first
|
21
|
+
bin = representable_bindings_for(format, options).first
|
22
22
|
bin.serialize_for(self)
|
23
23
|
end
|
24
24
|
|
25
25
|
def update_properties_from(doc, options, format)
|
26
|
-
bin = representable_bindings_for(format).first
|
26
|
+
bin = representable_bindings_for(format, options).first
|
27
27
|
value = bin.deserialize_from(doc)
|
28
28
|
replace(value)
|
29
29
|
end
|
@@ -18,13 +18,13 @@ module Representable::XML
|
|
18
18
|
|
19
19
|
|
20
20
|
def create_representation_with(doc, options, format)
|
21
|
-
bin = representable_bindings_for(format).first
|
22
|
-
bin.
|
21
|
+
bin = representable_bindings_for(format, options).first
|
22
|
+
bin.write(doc, self)
|
23
23
|
end
|
24
24
|
|
25
25
|
def update_properties_from(doc, options, format)
|
26
|
-
bin = representable_bindings_for(format).first
|
27
|
-
value = bin.deserialize_from(doc)
|
26
|
+
bin = representable_bindings_for(format, options).first
|
27
|
+
value = bin.deserialize_from(doc.search("./*")) # FIXME: use Binding#read.
|
28
28
|
replace(value)
|
29
29
|
end
|
30
30
|
|
data/test/example.rb
CHANGED
@@ -4,6 +4,14 @@ Bundler.setup
|
|
4
4
|
require 'representable/yaml'
|
5
5
|
require 'ostruct'
|
6
6
|
|
7
|
+
def reset_representer(*module_name)
|
8
|
+
module_name.each do |mod|
|
9
|
+
mod.module_eval do
|
10
|
+
@representable_attrs = nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
7
15
|
class Song < OpenStruct
|
8
16
|
end
|
9
17
|
|
@@ -37,11 +45,7 @@ puts song.extend(SongRepresenter).to_json
|
|
37
45
|
|
38
46
|
######### collections
|
39
47
|
|
40
|
-
|
41
|
-
include Representable::JSON
|
42
|
-
|
43
|
-
self.representation_wrap= false
|
44
|
-
end
|
48
|
+
reset_representer(SongRepresenter)
|
45
49
|
|
46
50
|
module SongRepresenter
|
47
51
|
include Representable::JSON
|
@@ -76,6 +80,7 @@ end
|
|
76
80
|
album = Album.new(:name => "The Police", :song => song)
|
77
81
|
puts album.extend(AlbumRepresenter).to_json
|
78
82
|
|
83
|
+
reset_representer(AlbumRepresenter)
|
79
84
|
|
80
85
|
module AlbumRepresenter
|
81
86
|
include Representable::JSON
|
@@ -88,23 +93,25 @@ album = Album.new(:name => "The Police", :songs => [song, Song.new(:title => "Sy
|
|
88
93
|
puts album.extend(AlbumRepresenter).to_json
|
89
94
|
|
90
95
|
|
91
|
-
|
92
|
-
|
93
|
-
|
96
|
+
album = Album.new.extend(AlbumRepresenter).from_json(%{{"name":"Offspring","songs":[{"title":"Genocide"},{"title":"Nitro","composers":["Offspring"]}]}
|
97
|
+
})
|
98
|
+
puts album.inspect
|
94
99
|
|
100
|
+
reset_representer(SongRepresenter)
|
95
101
|
|
96
102
|
######### using helpers (customizing the rendering/parsing)
|
97
103
|
module AlbumRepresenter
|
104
|
+
include Representable::JSON
|
105
|
+
|
98
106
|
def name
|
99
|
-
super.
|
107
|
+
super.upcase
|
100
108
|
end
|
101
109
|
end
|
102
|
-
album = Album.new(:name => "The Police", :songs => [song, Song.new(:title => "Synchronicity")])
|
103
|
-
puts album.extend(AlbumRepresenter).to_json
|
104
110
|
|
105
|
-
|
106
|
-
|
107
|
-
|
111
|
+
puts Album.new(:name => "The Police").
|
112
|
+
extend(AlbumRepresenter).to_json
|
113
|
+
|
114
|
+
reset_representer(SongRepresenter)
|
108
115
|
|
109
116
|
|
110
117
|
######### inheritance
|
@@ -139,10 +146,7 @@ end
|
|
139
146
|
song = Song.new(:title => "Fallout", :composers => ["Steward Copeland", "Sting"])
|
140
147
|
puts song.extend(SongRepresenter).to_xml
|
141
148
|
|
142
|
-
|
143
|
-
SongRepresenter.module_eval do
|
144
|
-
@representable_attrs = nil
|
145
|
-
end
|
149
|
+
reset_representer(SongRepresenter)
|
146
150
|
|
147
151
|
|
148
152
|
### YAML
|
@@ -172,8 +176,135 @@ module SongRepresenter
|
|
172
176
|
end
|
173
177
|
puts song.extend(SongRepresenter).to_yaml
|
174
178
|
|
179
|
+
SongRepresenter.module_eval do
|
180
|
+
@representable_attrs = nil
|
181
|
+
end
|
182
|
+
|
183
|
+
# R/W support
|
184
|
+
song = Song.new(:title => "You're Wrong", :track => 4)
|
185
|
+
module SongRepresenter
|
186
|
+
include Representable::Hash
|
187
|
+
|
188
|
+
property :title, :readable => false
|
189
|
+
property :track
|
190
|
+
end
|
191
|
+
puts song.extend(SongRepresenter).to_hash
|
192
|
+
|
193
|
+
SongRepresenter.module_eval do
|
194
|
+
@representable_attrs = nil
|
195
|
+
end
|
196
|
+
|
197
|
+
module SongRepresenter
|
198
|
+
include Representable::Hash
|
199
|
+
|
200
|
+
property :title, :writeable => false
|
201
|
+
property :track
|
202
|
+
end
|
203
|
+
song = Song.new.extend(SongRepresenter)
|
204
|
+
song.from_hash({:title => "Fallout", :track => 1})
|
205
|
+
puts song
|
175
206
|
|
176
207
|
######### custom methods in representer (using helpers)
|
177
|
-
#########
|
178
|
-
#########
|
179
|
-
#########
|
208
|
+
######### conditions
|
209
|
+
######### :as
|
210
|
+
######### XML::Collection
|
211
|
+
######### polymorphic :extend and :class, instance context!, :instance
|
212
|
+
class CoverSong < Song
|
213
|
+
end
|
214
|
+
|
215
|
+
songs = [Song.new(title: "Weirdo", track: 5), CoverSong.new(title: "Truth Hits Everybody", track: 6, copyright: "The Police")]
|
216
|
+
album = Album.new(name: "Incognito", songs: songs)
|
217
|
+
|
218
|
+
|
219
|
+
reset_representer(SongRepresenter, AlbumRepresenter)
|
220
|
+
|
221
|
+
module SongRepresenter
|
222
|
+
include Representable::Hash
|
223
|
+
|
224
|
+
property :title
|
225
|
+
property :track
|
226
|
+
end
|
227
|
+
|
228
|
+
module CoverSongRepresenter
|
229
|
+
include Representable::Hash
|
230
|
+
include SongRepresenter
|
231
|
+
|
232
|
+
property :copyright
|
233
|
+
end
|
234
|
+
|
235
|
+
module AlbumRepresenter
|
236
|
+
include Representable::Hash
|
237
|
+
|
238
|
+
property :name
|
239
|
+
collection :songs, :extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter }
|
240
|
+
end
|
241
|
+
|
242
|
+
puts album.extend(AlbumRepresenter).to_hash
|
243
|
+
|
244
|
+
reset_representer(AlbumRepresenter)
|
245
|
+
|
246
|
+
module AlbumRepresenter
|
247
|
+
include Representable::Hash
|
248
|
+
|
249
|
+
property :name
|
250
|
+
collection :songs,
|
251
|
+
:extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter },
|
252
|
+
:class => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong : Song } #=> {"title"=>"Weirdo", "track"=>5}
|
253
|
+
end
|
254
|
+
|
255
|
+
album = Album.new.extend(AlbumRepresenter).from_hash({"name"=>"Incognito", "songs"=>[{"title"=>"Weirdo", "track"=>5}, {"title"=>"Truth Hits Everybody", "track"=>6, "copyright"=>"The Police"}]})
|
256
|
+
puts album.inspect
|
257
|
+
|
258
|
+
reset_representer(AlbumRepresenter)
|
259
|
+
|
260
|
+
module AlbumRepresenter
|
261
|
+
include Representable::Hash
|
262
|
+
|
263
|
+
property :name
|
264
|
+
collection :songs,
|
265
|
+
:extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter },
|
266
|
+
:instance => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong.new : Song.new(original: true) }
|
267
|
+
end
|
268
|
+
|
269
|
+
album = Album.new.extend(AlbumRepresenter).from_hash({"name"=>"Incognito", "songs"=>[{"title"=>"Weirdo", "track"=>5}, {"title"=>"Truth Hits Everybody", "track"=>6, "copyright"=>"The Police"}]})
|
270
|
+
puts album.inspect
|
271
|
+
|
272
|
+
######### #hash
|
273
|
+
|
274
|
+
reset_representer(SongRepresenter)
|
275
|
+
|
276
|
+
module SongRepresenter
|
277
|
+
include Representable::JSON
|
278
|
+
|
279
|
+
property :title
|
280
|
+
hash :ratings
|
281
|
+
end
|
282
|
+
|
283
|
+
puts Song.new(title: "Bliss", ratings: {"Rolling Stone" => 4.9, "FryZine" => 4.5}).extend(SongRepresenter).to_json
|
284
|
+
|
285
|
+
######### JSON::Hash
|
286
|
+
|
287
|
+
module FavoriteSongsRepresenter
|
288
|
+
include Representable::JSON::Hash
|
289
|
+
end
|
290
|
+
|
291
|
+
puts( {"Nick" => "Hyper Music", "El" => "Blown In The Wind"}.extend(FavoriteSongsRepresenter).to_json)
|
292
|
+
|
293
|
+
require 'representable/json/hash'
|
294
|
+
module FavoriteSongsRepresenter
|
295
|
+
include Representable::JSON::Hash
|
296
|
+
|
297
|
+
values extend: SongRepresenter, class: Song
|
298
|
+
end
|
299
|
+
|
300
|
+
puts( {"Nick" => Song.new(title: "Hyper Music")}.extend(FavoriteSongsRepresenter).to_json)
|
301
|
+
|
302
|
+
|
303
|
+
require 'representable/json/collection'
|
304
|
+
module SongsRepresenter
|
305
|
+
include Representable::JSON::Collection
|
306
|
+
|
307
|
+
items extend: SongRepresenter, class: Song
|
308
|
+
end
|
309
|
+
|
310
|
+
puts [Song.new(title: "Hyper Music"), Song.new(title: "Screenager")].extend(SongsRepresenter).to_json
|