representable 1.2.9 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
- write_fragment_for(bin, value, doc)
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
- value = read_fragment_for(bin, doc)
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
- representable_attrs.map {|attr| format.build_for(attr, self) }
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 = 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, represented)
20
- return CollectionBinding.new(definition, represented) if definition.array?
21
- return HashBinding.new(definition, represented) if definition.hash?
22
- new(definition, represented)
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, represented)
28
- return CollectionBinding.new(definition, represented) if definition.array?
29
- return HashBinding.new(definition, represented) if definition.hash? and not definition.options[:use_attributes] # FIXME: hate this.
30
- return AttributeHashBinding.new(definition, represented) if definition.hash? and definition.options[:use_attributes]
31
- return AttributeBinding.new(definition, represented) if definition.attribute
32
- new(definition, represented)
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
- selector = "./#{xpath}"
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, represented)
23
- return CollectionBinding.new(definition, represented) if definition.array?
24
- #return HashBinding.new(definition) if definition.hash? and not definition.options[:use_attributes] # FIXME: hate this.
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
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.2.9"
2
+ VERSION = "1.3.0"
3
3
  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.serialize_for(self)
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
 
@@ -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
- module SongRepresenter
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
- SongRepresenter.module_eval do
92
- @representable_attrs = nil
93
- end
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.upper
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
- SongRepresenter.module_eval do
106
- @representable_attrs = nil
107
- end
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
- ######### r/w, conditions
178
- #########
179
- ######### polymorphic :extend and :class, instance context!, :instance
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