representable 1.3.4 → 1.3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ h2. 1.3.5
2
+
3
+ * Added `:reader` and `:writer` to allow overriding rendering/parsing of a property fragment and to give the user access to the entire document.
4
+
1
5
  h2. 1.3.4
2
6
 
3
7
  * Replacing `json` gem with `multi_json` hoping not to cause trouble.
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 )?
@@ -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
- # Retrieve value and write fragment to the doc.
92
+
93
+ # TODO: remove in 1.4.
94
94
  def compile_fragment(bin, doc)
95
- value = bin.get
96
-
97
- bin.write_fragment(doc, value)
95
+ bin.compile_fragment(doc)
98
96
  end
99
-
100
- # Parse value from doc and update the model property.
97
+
98
+ # TODO: remove in 1.4.
101
99
  def uncompile_fragment(bin, doc)
102
- bin.read_fragment(doc) do |value|
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 represented.instance_exec(user_options, &options[:getter]) if options[:getter]
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 = represented.instance_exec(value, user_options, &options[:setter]) if options[:setter]
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
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.3.4"
2
+ VERSION = "1.3.5"
3
3
  end
@@ -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
 
@@ -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
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-13 00:00:00.000000000 Z
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