representable 1.2.4 → 1.2.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.
@@ -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: