representable 1.2.4 → 1.2.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.2.5
2
+
3
+ * Add support for YAML.
4
+
1
5
  h2. 1.2.4
2
6
 
3
7
  * ObjectBinding no longer tries to extend nil values when rendering and @:render_nil@ is set.
@@ -14,7 +14,7 @@ This keeps your representation knowledge in one place when implementing REST ser
14
14
 
15
15
  * Bidirectional - rendering and parsing
16
16
  * OOP access to documents
17
- * Support for JSON and XML
17
+ * Support for JSON, XML and YAML.
18
18
  * Coercion support with virtus[https://github.com/solnic/virtus]
19
19
 
20
20
 
@@ -279,7 +279,7 @@ There's no need to specify a representer for the +origin+ property since the +Lo
279
279
 
280
280
  == XML support
281
281
 
282
- Representable allows declaring a document's syntax and structure while having different formats. Currently, it ships with JSON and XML bindings.
282
+ Representable allows declaring a document's syntax and structure while having different formats. Currently, it ships with JSON, XML and YAML bindings.
283
283
 
284
284
  class Hero
285
285
  include Representable::XML
@@ -338,6 +338,50 @@ Note that +:wrap+ defines the container tag name.
338
338
  </songs>
339
339
  </album>
340
340
 
341
+
342
+ == YAML Support
343
+
344
+ Representers also come in handy if you need to render or parse YAML. The YAML module works exactly like the others.
345
+
346
+ module HotBandsRepresenter
347
+ include Representable::YAML
348
+
349
+ property :for
350
+ collection :names
351
+ end
352
+
353
+ Now, just call #to_yaml to render or #from_yaml to parse.
354
+
355
+ HotBands.new(:for => "Nick", :names => ["Bad Religion", "Van Halen", "Mozart"]).
356
+ extend(HotBandsRepresenter).
357
+ to_yaml
358
+
359
+ #=> ---
360
+ for: Nick
361
+ names:
362
+ - Bad Religion
363
+ - Van Halen
364
+ - Mozart
365
+
366
+ === Nested Objects
367
+
368
+ The YAML parser does handle nested objects just like JSON and XML does it.
369
+
370
+ === Flow Style Lists
371
+
372
+ If you want flow style (aka inline style) lists, use the :style option. See http://www.yaml.org/spec/1.2/spec.html#id2790088 for more infos on flow sequences.
373
+
374
+ module HotBandsRepresenter
375
+ include Representable::YAML
376
+
377
+ collection :names, :style => :flow
378
+ end
379
+
380
+ #=> ---
381
+ names: [Bad Religion, Van Halen, Mozart]
382
+
383
+ Need anything else for YAML? Let me know.
384
+
341
385
  == Coercion
342
386
 
343
387
  If you fancy coercion when parsing a document you can use the Coercion module which uses virtus[https://github.com/solnic/virtus] for type conversion.
@@ -4,3 +4,4 @@ source "http://rubygems.org"
4
4
  gemspec :path => '../'
5
5
 
6
6
  gem 'mongoid', '~> 2.4.0'
7
+ gem 'psych'
@@ -0,0 +1,69 @@
1
+ require 'representable/binding'
2
+
3
+ module Representable
4
+ module YAML
5
+ module ObjectBinding
6
+ include Binding::Object
7
+
8
+ def serialize_method
9
+ :to_ast
10
+ end
11
+
12
+ def deserialize_method
13
+ :from_hash
14
+ end
15
+
16
+ def write_scalar(value)
17
+ value
18
+ end
19
+ end
20
+
21
+ class YAMLBinding < Representable::Binding
22
+ def initialize(definition) # FIXME. make generic.
23
+ super
24
+ extend ObjectBinding if definition.typed?
25
+ end
26
+
27
+ def read(hash)
28
+ return FragmentNotFound unless hash.has_key?(definition.from) # DISCUSS: put it all in #read for performance. not really sure if i like returning that special thing.
29
+
30
+ fragment = hash[definition.from]
31
+ deserialize_from(fragment)
32
+ end
33
+
34
+ def write(map, value)
35
+ map.children << Psych::Nodes::Scalar.new(definition.from)
36
+ map.children << serialize_for(value) # FIXME: should be serialize.
37
+ end
38
+ end
39
+
40
+
41
+ class PropertyBinding < YAMLBinding
42
+ def serialize_for(value)
43
+ write_scalar serialize(value)
44
+ end
45
+
46
+ def deserialize_from(fragment)
47
+ deserialize(fragment)
48
+ end
49
+
50
+ def write_scalar(value)
51
+ Psych::Nodes::Scalar.new(value.to_s)
52
+ end
53
+ end
54
+
55
+
56
+ class CollectionBinding < PropertyBinding
57
+ def serialize_for(value)
58
+ Psych::Nodes::Sequence.new.tap do |seq|
59
+ seq.style = Psych::Nodes::Sequence::FLOW if definition.options[:style] == :flow
60
+ value.each { |obj| seq.children << super(obj) }
61
+ end
62
+ end
63
+
64
+ def deserialize_from(fragment)
65
+ fragment.collect { |item_fragment| deserialize(item_fragment) }
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.2.4"
2
+ VERSION = "1.2.5"
3
3
  end
@@ -0,0 +1,67 @@
1
+ require 'representable'
2
+ require 'representable/bindings/yaml_bindings'
3
+
4
+ module Representable
5
+ module YAML
6
+ def self.binding_for_definition(definition)
7
+ return CollectionBinding.new(definition) if definition.array?
8
+ #return HashBinding.new(definition) if definition.hash? and not definition.options[:use_attributes] # FIXME: hate this.
9
+ #return AttributeHashBinding.new(definition) if definition.hash? and definition.options[:use_attributes]
10
+ #return AttributeBinding.new(definition) if definition.attribute
11
+ PropertyBinding.new(definition)
12
+ end
13
+
14
+ def self.included(base)
15
+ base.class_eval do
16
+ include Representable
17
+ extend ClassMethods
18
+ #self.representation_wrap = true # let representable compute it.
19
+ end
20
+ end
21
+
22
+
23
+ module ClassMethods
24
+ # Creates a new Ruby object from XML using mapping information declared in the class.
25
+ #
26
+ # Accepts a block yielding the currently iterated Definition. If the block returns false
27
+ # the property is skipped.
28
+ #
29
+ # Example:
30
+ # band.from_xml("<band><name>Nofx</name></band>")
31
+ def from_xml(*args, &block)
32
+ create_represented(*args, &block).from_xml(*args)
33
+ end
34
+
35
+ def from_node(*args, &block)
36
+ create_represented(*args, &block).from_node(*args)
37
+ end
38
+ end
39
+
40
+
41
+ def from_yaml(doc, *args)
42
+ hash = Psych.load(doc)
43
+ from_hash(hash, *args)
44
+ end
45
+
46
+ def from_hash(hash, options={})
47
+ update_properties_from(hash, options, YAML)
48
+ end
49
+
50
+ # Returns a Nokogiri::XML object representing this object.
51
+ def to_ast(options={})
52
+ #root_tag = options[:wrap] || representation_wrap
53
+
54
+ Psych::Nodes::Mapping.new.tap do |map|
55
+ create_representation_with(map, options, YAML)
56
+ end
57
+ end
58
+
59
+ def to_yaml(*args)
60
+ stream = Psych::Nodes::Stream.new
61
+ stream.children << doc = Psych::Nodes::Document.new
62
+
63
+ doc.children << to_ast(*args)
64
+ stream.to_yaml
65
+ end
66
+ end
67
+ end
@@ -185,7 +185,7 @@ module JsonTest
185
185
  end
186
186
 
187
187
  it "extends contained models when serializing" do
188
- @album = Album.new(Song.new("I Hate My Brain"), Song.new("Mr. Charisma"))
188
+ @album = Album.new([Song.new("I Hate My Brain"), mr=Song.new("Mr. Charisma")], mr)
189
189
  @album.extend(AlbumRepresenter)
190
190
 
191
191
  assert_json "{\"best_song\":{\"name\":\"Mr. Charisma\"},\"songs\":[{\"name\":\"I Hate My Brain\"},{\"name\":\"Mr. Charisma\"}]}", @album.to_json
@@ -13,20 +13,25 @@ require 'mocha'
13
13
 
14
14
  class Album
15
15
  attr_accessor :songs, :best_song
16
- def initialize(*songs)
16
+ def initialize(songs=nil, best_song=nil)
17
17
  @songs = songs
18
- @best_song = songs.last
18
+ @best_song = best_song
19
+ end
20
+
21
+ def ==(other)
22
+ songs == other.songs and best_song == other.best_song
19
23
  end
20
24
  end
21
25
 
22
26
  class Song
23
- attr_accessor :name
24
- def initialize(name=nil)
25
- @name = name
27
+ attr_accessor :name, :track
28
+ def initialize(name=nil, track=nil)
29
+ @name = name
30
+ @track = track
26
31
  end
27
32
 
28
33
  def ==(other)
29
- name == other.name
34
+ name == other.name and track == other.track
30
35
  end
31
36
  end
32
37
 
@@ -178,7 +178,7 @@ class XmlTest < MiniTest::Spec
178
178
  end
179
179
 
180
180
  it "extends contained models when serializing" do
181
- @album = Album.new(Song.new("I Hate My Brain"), Song.new("Mr. Charisma"))
181
+ @album = Album.new([Song.new("I Hate My Brain"), mr=Song.new("Mr. Charisma")], mr)
182
182
  @album.extend(AlbumRepresenter)
183
183
 
184
184
  assert_xml_equal "<album>
@@ -0,0 +1,166 @@
1
+ require 'test_helper'
2
+ require 'representable/yaml'
3
+
4
+ class YamlTest < MiniTest::Spec
5
+ def self.yaml_representer(&block)
6
+ Module.new do
7
+ include Representable::YAML
8
+ instance_exec &block
9
+ end
10
+ end
11
+
12
+ def yaml_representer(&block)
13
+ self.class.yaml_representer(&block)
14
+ end
15
+
16
+
17
+ describe "property" do
18
+ let (:yaml) { yaml_representer do property :best_song end }
19
+
20
+ let (:album) { Album.new.tap do |album|
21
+ album.best_song = "Liar"
22
+ end }
23
+
24
+ describe "#to_yaml" do
25
+ it "renders plain property" do
26
+ album.extend(yaml).to_yaml.must_equal(
27
+ "---
28
+ best_song: Liar
29
+ ")
30
+ end
31
+
32
+ it "always renders values into strings" do
33
+ Album.new.tap { |a| a.best_song = 8675309 }.extend(yaml).to_yaml.must_equal(
34
+ "---
35
+ best_song: 8675309
36
+ "
37
+ )
38
+ end
39
+ end
40
+
41
+
42
+ describe "#from_yaml" do
43
+ it "parses plain property" do
44
+ album.extend(yaml).from_yaml(
45
+ "---
46
+ best_song: This Song Is Recycled
47
+ ").best_song.must_equal "This Song Is Recycled"
48
+ end
49
+ end
50
+
51
+
52
+ describe "with :class and :extend" do
53
+ yaml_song = yaml_representer do property :name end
54
+ let (:yaml_album) { Module.new do
55
+ include Representable::YAML
56
+ property :best_song, :extend => yaml_song, :class => Song
57
+ end }
58
+
59
+ let (:album) { Album.new.tap do |album|
60
+ album.best_song = Song.new("Liar")
61
+ end }
62
+
63
+
64
+ describe "#to_yaml" do
65
+ it "renders embedded typed property" do
66
+ album.extend(yaml_album).to_yaml.must_equal "---
67
+ best_song:
68
+ name: Liar
69
+ "
70
+ end
71
+ end
72
+
73
+ describe "#from_yaml" do
74
+ it "parses embedded typed property" do
75
+ album.extend(yaml_album).from_yaml("---
76
+ best_song:
77
+ name: Go With Me
78
+ ").must_equal Album.new(nil,Song.new("Go With Me"))
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+
85
+ describe "collection" do
86
+ let (:yaml) { yaml_representer do collection :songs end }
87
+
88
+ let (:album) { Album.new.tap do |album|
89
+ album.songs = ["Jackhammer", "Terrible Man"]
90
+ end }
91
+
92
+
93
+ describe "#to_yaml" do
94
+ it "renders a block style list per default" do
95
+ album.extend(yaml).to_yaml.must_equal "---
96
+ songs:
97
+ - Jackhammer
98
+ - Terrible Man
99
+ "
100
+ end
101
+
102
+ it "renders a flow style list when :style => :flow set" do
103
+ yaml = yaml_representer { collection :songs, :style => :flow }
104
+ album.extend(yaml).to_yaml.must_equal "---
105
+ songs: [Jackhammer, Terrible Man]
106
+ "
107
+ end
108
+ end
109
+
110
+
111
+ describe "#from_yaml" do
112
+ it "parses a block style list" do
113
+ album.extend(yaml).from_yaml("---
114
+ songs:
115
+ - Off Key Melody
116
+ - Sinking").must_equal Album.new(["Off Key Melody", "Sinking"])
117
+
118
+ end
119
+
120
+ it "parses a flow style list" do
121
+ album.extend(yaml).from_yaml("---
122
+ songs: [Off Key Melody, Sinking]").must_equal Album.new(["Off Key Melody", "Sinking"])
123
+ end
124
+ end
125
+
126
+
127
+ describe "with :class and :extend" do
128
+ yaml_song = yaml_representer do
129
+ property :name
130
+ property :track
131
+ end
132
+ let (:yaml_album) { Module.new do
133
+ include Representable::YAML
134
+ collection :songs, :extend => yaml_song, :class => Song
135
+ end }
136
+
137
+ let (:album) { Album.new.tap do |album|
138
+ album.songs = [Song.new("Liar", 1), Song.new("What I Know", 2)]
139
+ end }
140
+
141
+
142
+ describe "#to_yaml" do
143
+ it "renders collection of typed property" do
144
+ album.extend(yaml_album).to_yaml.must_equal "---
145
+ songs:
146
+ - name: Liar
147
+ track: 1
148
+ - name: What I Know
149
+ track: 2
150
+ "
151
+ end
152
+ end
153
+
154
+ describe "#from_yaml" do
155
+ it "parses collection of typed property" do
156
+ album.extend(yaml_album).from_yaml("---
157
+ songs:
158
+ - name: One Shot Deal
159
+ track: 4
160
+ - name: Three Way Dance
161
+ track: 5").must_equal Album.new([Song.new("One Shot Deal", 4), Song.new("Three Way Dance", 5)])
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
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.2.4
4
+ version: 1.2.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: 2012-08-25 00:00:00.000000000 Z
12
+ date: 2012-08-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
@@ -160,6 +160,7 @@ files:
160
160
  - lib/representable/binding.rb
161
161
  - lib/representable/bindings/json_bindings.rb
162
162
  - lib/representable/bindings/xml_bindings.rb
163
+ - lib/representable/bindings/yaml_bindings.rb
163
164
  - lib/representable/coercion.rb
164
165
  - lib/representable/definition.rb
165
166
  - lib/representable/deprecations.rb
@@ -171,6 +172,7 @@ files:
171
172
  - lib/representable/xml.rb
172
173
  - lib/representable/xml/collection.rb
173
174
  - lib/representable/xml/hash.rb
175
+ - lib/representable/yaml.rb
174
176
  - representable.gemspec
175
177
  - test/coercion_test.rb
176
178
  - test/definition_test.rb
@@ -183,6 +185,7 @@ files:
183
185
  - test/test_helper_test.rb
184
186
  - test/xml_bindings_test.rb
185
187
  - test/xml_test.rb
188
+ - test/yaml_test.rb
186
189
  homepage: http://representable.apotomo.de
187
190
  licenses: []
188
191
  post_install_message: