representable 1.2.5 → 1.2.6

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.6
2
+
3
+ Extracted @HashRepresenter@ which operates on hash structures. This allows you to "parse" form data, e.g. as in Rails' @params@ hash. Internally, this is used by JSON and partly by YAML.
4
+
1
5
  h2. 1.2.5
2
6
 
3
7
  * Add support for YAML.
@@ -21,6 +25,7 @@ h2. 1.2.1
21
25
  * Deprecated @:represent_nil@ favor of @:render_nil@.
22
26
  * API change: if a property is missing in an incoming document and there is no default set it is completely ignored and *not* set in the represented object.
23
27
 
28
+
24
29
  h2. 1.2.0
25
30
 
26
31
  * Deprecated @:except@ in favor of @:exclude@.
@@ -80,7 +80,7 @@ Those two properties are considered when rendering in #to_json.
80
80
 
81
81
  == Parsing
82
82
 
83
- The cool thing about Representable is: it works bidirectional. By declaring properties you can not only render but also parse!
83
+ The cool thing about Representable is: it works bidirectional. The document definition in your representer module can also be used for parsing documents and assigning property values.
84
84
 
85
85
  hook = Hero.from_json('{"forename":"Captain","surename":"Hook"}')
86
86
  hook.forename #=> "Captain"
@@ -11,17 +11,13 @@ module Representable
11
11
  end
12
12
 
13
13
  # Main entry point for rendering/parsing a property object.
14
- module Hooks
15
- def serialize(value)
16
- value
17
- end
18
-
19
- def deserialize(fragment)
20
- fragment
21
- end
14
+ def serialize(value)
15
+ value
22
16
  end
23
17
 
24
- include Hooks
18
+ def deserialize(fragment)
19
+ fragment
20
+ end
25
21
 
26
22
 
27
23
  # Hooks into #serialize and #deserialize to extend typed properties
@@ -1,7 +1,7 @@
1
1
  require 'representable/binding'
2
2
 
3
3
  module Representable
4
- module JSON
4
+ module Hash
5
5
  module ObjectBinding
6
6
  include Binding::Object
7
7
 
@@ -15,7 +15,7 @@ module Representable
15
15
  end
16
16
 
17
17
 
18
- class JSONBinding < Representable::Binding
18
+ class PropertyBinding < Representable::Binding
19
19
  def initialize(definition) # FIXME. make generic.
20
20
  super
21
21
  extend ObjectBinding if definition.typed?
@@ -31,10 +31,7 @@ module Representable
31
31
  def write(hash, value)
32
32
  hash[definition.from] = serialize_for(value)
33
33
  end
34
- end
35
-
36
-
37
- class PropertyBinding < JSONBinding
34
+
38
35
  def serialize_for(value)
39
36
  serialize(value)
40
37
  end
@@ -45,7 +42,7 @@ module Representable
45
42
  end
46
43
 
47
44
 
48
- class CollectionBinding < JSONBinding
45
+ class CollectionBinding < PropertyBinding
49
46
  def serialize_for(value)
50
47
  value.collect { |obj| serialize(obj) }
51
48
  end
@@ -56,7 +53,7 @@ module Representable
56
53
  end
57
54
 
58
55
 
59
- class HashBinding < JSONBinding
56
+ class HashBinding < PropertyBinding
60
57
  def serialize_for(value)
61
58
  # requires value to respond to #each with two block parameters.
62
59
  {}.tap do |hash|
@@ -18,34 +18,20 @@ module Representable
18
18
  end
19
19
  end
20
20
 
21
- class YAMLBinding < Representable::Binding
21
+ class PropertyBinding < Representable::Hash::PropertyBinding
22
22
  def initialize(definition) # FIXME. make generic.
23
23
  super
24
24
  extend ObjectBinding if definition.typed?
25
25
  end
26
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
27
  def write(map, value)
35
28
  map.children << Psych::Nodes::Scalar.new(definition.from)
36
29
  map.children << serialize_for(value) # FIXME: should be serialize.
37
30
  end
38
- end
39
-
40
-
41
- class PropertyBinding < YAMLBinding
31
+
42
32
  def serialize_for(value)
43
33
  write_scalar serialize(value)
44
34
  end
45
-
46
- def deserialize_from(fragment)
47
- deserialize(fragment)
48
- end
49
35
 
50
36
  def write_scalar(value)
51
37
  Psych::Nodes::Scalar.new(value.to_s)
@@ -61,7 +47,7 @@ module Representable
61
47
  end
62
48
  end
63
49
 
64
- def deserialize_from(fragment)
50
+ def deserialize_from(fragment) # FIXME: redundant from Hash::Bindings
65
51
  fragment.collect { |item_fragment| deserialize(item_fragment) }
66
52
  end
67
53
  end
@@ -0,0 +1,47 @@
1
+ require 'representable'
2
+ require 'representable/bindings/hash_bindings'
3
+ require 'json'
4
+
5
+ module Representable
6
+ # The generic representer. Brings #to_hash and #from_hash to your object.
7
+ # If you plan to write your own representer for a new media type, try to use this module (e.g., check how JSON reuses Hash's internal
8
+ # architecture).
9
+ module Hash
10
+ def self.included(base)
11
+ base.class_eval do
12
+ include Representable # either in Hero or HeroRepresentation.
13
+ extend ClassMethods # DISCUSS: do that only for classes?
14
+ end
15
+ end
16
+
17
+
18
+ module ClassMethods
19
+ def binding_for_definition(definition)
20
+ return Representable::Hash::CollectionBinding.new(definition) if definition.array?
21
+ return Representable::Hash::HashBinding.new(definition) if definition.hash?
22
+ Representable::Hash::PropertyBinding.new(definition)
23
+ end
24
+
25
+ def from_hash(*args, &block)
26
+ create_represented(*args, &block).from_hash(*args)
27
+ end
28
+ end
29
+
30
+
31
+ def from_hash(data, options={})
32
+ if wrap = options[:wrap] || representation_wrap
33
+ data = data[wrap.to_s]
34
+ end
35
+
36
+ update_properties_from(data, options, JSON)
37
+ end
38
+
39
+ def to_hash(options={})
40
+ hash = create_representation_with({}, options, JSON)
41
+
42
+ return hash unless wrap = options[:wrap] || representation_wrap
43
+
44
+ {wrap => hash}
45
+ end
46
+ end
47
+ end
@@ -1,23 +1,17 @@
1
- require 'representable'
2
- require 'representable/bindings/json_bindings'
1
+ require 'representable/hash'
3
2
  require 'json'
4
3
 
5
4
  module Representable
6
- # Brings #to_xml, #to_hash, #from_xml and #from_hash to your object.
7
- #
8
- # Note: The authorative methods are #to_hash and #from_hash, if you override #to_json instead,
9
- # things might work as expected.
5
+ # Brings #to_json and #from_json to your object.
10
6
  module JSON
11
- def self.binding_for_definition(definition)
12
- return CollectionBinding.new(definition) if definition.array?
13
- return HashBinding.new(definition) if definition.hash?
14
- PropertyBinding.new(definition)
15
- end
16
-
7
+ extend Hash::ClassMethods
8
+ include Hash
9
+
17
10
  def self.included(base)
18
11
  base.class_eval do
19
12
  include Representable # either in Hero or HeroRepresentation.
20
13
  extend ClassMethods # DISCUSS: do that only for classes?
14
+ extend Representable::Hash::ClassMethods # DISCUSS: this is only for .from_hash, remove in 2.3?
21
15
  end
22
16
  end
23
17
 
@@ -27,10 +21,6 @@ module Representable
27
21
  def from_json(*args, &block)
28
22
  create_represented(*args, &block).from_json(*args)
29
23
  end
30
-
31
- def from_hash(*args, &block)
32
- create_represented(*args, &block).from_hash(*args)
33
- end
34
24
  end
35
25
 
36
26
 
@@ -40,22 +30,6 @@ module Representable
40
30
  from_hash(data, *args)
41
31
  end
42
32
 
43
- def from_hash(data, options={})
44
- if wrap = options[:wrap] || representation_wrap
45
- data = data[wrap.to_s]
46
- end
47
-
48
- update_properties_from(data, options, JSON)
49
- end
50
-
51
- def to_hash(options={})
52
- hash = create_representation_with({}, options, JSON)
53
-
54
- return hash unless wrap = options[:wrap] || representation_wrap
55
-
56
- {wrap => hash}
57
- end
58
-
59
33
  # Returns a JSON string representing this object.
60
34
  def to_json(*args)
61
35
  to_hash(*args).to_json
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.2.5"
2
+ VERSION = "1.2.6"
3
3
  end
@@ -1,8 +1,10 @@
1
- require 'representable'
1
+ require 'representable/hash'
2
2
  require 'representable/bindings/yaml_bindings'
3
3
 
4
4
  module Representable
5
5
  module YAML
6
+ include Hash
7
+
6
8
  def self.binding_for_definition(definition)
7
9
  return CollectionBinding.new(definition) if definition.array?
8
10
  #return HashBinding.new(definition) if definition.hash? and not definition.options[:use_attributes] # FIXME: hate this.
@@ -28,12 +30,8 @@ module Representable
28
30
  #
29
31
  # Example:
30
32
  # 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)
33
+ def from_yaml(*args, &block)
34
+ create_represented(*args, &block).from_yaml(*args)
37
35
  end
38
36
  end
39
37
 
@@ -43,10 +41,6 @@ module Representable
43
41
  from_hash(hash, *args)
44
42
  end
45
43
 
46
- def from_hash(hash, options={})
47
- update_properties_from(hash, options, YAML)
48
- end
49
-
50
44
  # Returns a Nokogiri::XML object representing this object.
51
45
  def to_ast(options={})
52
46
  #root_tag = options[:wrap] || representation_wrap
@@ -1,6 +1,6 @@
1
1
  require 'test_helper'
2
2
 
3
- class JSONBindingTest < MiniTest::Spec
3
+ class HashBindingTest < MiniTest::Spec
4
4
  module SongRepresenter
5
5
  include Representable::JSON
6
6
  property :name
@@ -15,7 +15,7 @@ class JSONBindingTest < MiniTest::Spec
15
15
  describe "PropertyBinding" do
16
16
  describe "#read" do
17
17
  before do
18
- @property = Representable::JSON::PropertyBinding.new(Representable::Definition.new(:song))
18
+ @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song))
19
19
  end
20
20
 
21
21
  it "returns fragment if present" do
@@ -32,7 +32,7 @@ class JSONBindingTest < MiniTest::Spec
32
32
 
33
33
  describe "with plain text" do
34
34
  before do
35
- @property = Representable::JSON::PropertyBinding.new(Representable::Definition.new(:song))
35
+ @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song))
36
36
  end
37
37
 
38
38
  it "extracts with #read" do
@@ -48,7 +48,7 @@ class JSONBindingTest < MiniTest::Spec
48
48
 
49
49
  describe "with an object" do
50
50
  before do
51
- @property = Representable::JSON::PropertyBinding.new(Representable::Definition.new(:song, :class => SongWithRepresenter))
51
+ @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song, :class => SongWithRepresenter))
52
52
  @doc = {}
53
53
  end
54
54
 
@@ -64,7 +64,7 @@ class JSONBindingTest < MiniTest::Spec
64
64
 
65
65
  describe "with an object and :extend" do
66
66
  before do
67
- @property = Representable::JSON::PropertyBinding.new(Representable::Definition.new(:song, :class => Song, :extend => SongRepresenter))
67
+ @property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song, :class => Song, :extend => SongRepresenter))
68
68
  @doc = {}
69
69
  end
70
70
 
@@ -83,7 +83,7 @@ class JSONBindingTest < MiniTest::Spec
83
83
  describe "CollectionBinding" do
84
84
  describe "with plain text items" do
85
85
  before do
86
- @property = Representable::JSON::CollectionBinding.new(Representable::Definition.new(:songs, :collection => true))
86
+ @property = Representable::Hash::CollectionBinding.new(Representable::Definition.new(:songs, :collection => true))
87
87
  end
88
88
 
89
89
  it "extracts with #read" do
@@ -104,7 +104,7 @@ class JSONBindingTest < MiniTest::Spec
104
104
  describe "HashBinding" do
105
105
  describe "with plain text items" do
106
106
  before do
107
- @property = Representable::JSON::HashBinding.new(Representable::Definition.new(:songs, :hash => true))
107
+ @property = Representable::Hash::HashBinding.new(Representable::Definition.new(:songs, :hash => true))
108
108
  end
109
109
 
110
110
  it "extracts with #read" do
@@ -120,7 +120,7 @@ class JSONBindingTest < MiniTest::Spec
120
120
 
121
121
  describe "with objects" do
122
122
  before do
123
- @property = Representable::JSON::HashBinding.new(Representable::Definition.new(:songs, :hash => true, :class => Song, :extend => SongRepresenter))
123
+ @property = Representable::Hash::HashBinding.new(Representable::Definition.new(:songs, :hash => true, :class => Song, :extend => SongRepresenter))
124
124
  end
125
125
 
126
126
  it "doesn't change the represented hash in #write" do
@@ -0,0 +1,117 @@
1
+ require 'test_helper'
2
+ require 'representable/hash'
3
+
4
+ class YamlTest < MiniTest::Spec
5
+ def self.hash_representer(&block)
6
+ Module.new do
7
+ include Representable::Hash
8
+ instance_exec &block
9
+ end
10
+ end
11
+
12
+ def hash_representer(&block)
13
+ self.class.hash_representer(&block)
14
+ end
15
+
16
+
17
+ describe "property" do
18
+ let (:hash) { hash_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_hash" do
25
+ it "renders plain property" do
26
+ album.extend(hash).to_hash.must_equal("best_song" => "Liar")
27
+ end
28
+ end
29
+
30
+
31
+ describe "#from_hash" do
32
+ it "parses plain property" do
33
+ album.extend(hash).from_hash("best_song" => "This Song Is Recycled").best_song.must_equal "This Song Is Recycled"
34
+ end
35
+ end
36
+
37
+
38
+ describe "with :class and :extend" do
39
+ hash_song = hash_representer do property :name end
40
+ let (:hash_album) { Module.new do
41
+ include Representable::Hash
42
+ property :best_song, :extend => hash_song, :class => Song
43
+ end }
44
+
45
+ let (:album) { Album.new.tap do |album|
46
+ album.best_song = Song.new("Liar")
47
+ end }
48
+
49
+
50
+ describe "#to_hash" do
51
+ it "renders embedded typed property" do
52
+ album.extend(hash_album).to_hash.must_equal("best_song" => {"name" => "Liar"})
53
+ end
54
+ end
55
+
56
+ describe "#from_hash" do
57
+ it "parses embedded typed property" do
58
+ album.extend(hash_album).from_hash("best_song" => {"name" => "Go With Me"}).must_equal Album.new(nil,Song.new("Go With Me"))
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+
65
+ describe "collection" do
66
+ let (:hash) { hash_representer do collection :songs end }
67
+
68
+ let (:album) { Album.new.tap do |album|
69
+ album.songs = ["Jackhammer", "Terrible Man"]
70
+ end }
71
+
72
+
73
+ describe "#to_hash" do
74
+ it "renders a block style list per default" do
75
+ album.extend(hash).to_hash.must_equal("songs" => ["Jackhammer", "Terrible Man"])
76
+ end
77
+ end
78
+
79
+
80
+ describe "#from_hash" do
81
+ it "parses a block style list" do
82
+ album.extend(hash).from_hash("songs" => ["Off Key Melody", "Sinking"]).must_equal Album.new(["Off Key Melody", "Sinking"])
83
+
84
+ end
85
+ end
86
+
87
+
88
+ describe "with :class and :extend" do
89
+ hash_song = hash_representer do
90
+ property :name
91
+ property :track
92
+ end
93
+ let (:hash_album) { Module.new do
94
+ include Representable::Hash
95
+ collection :songs, :extend => hash_song, :class => Song
96
+ end }
97
+
98
+ let (:album) { Album.new.tap do |album|
99
+ album.songs = [Song.new("Liar", 1), Song.new("What I Know", 2)]
100
+ end }
101
+
102
+
103
+ describe "#to_hash" do
104
+ it "renders collection of typed property" do
105
+ album.extend(hash_album).to_hash.must_equal("songs" => [{"name" => "Liar", "track" => 1}, {"name" => "What I Know", "track" => 2}])
106
+ end
107
+ end
108
+
109
+ describe "#from_hash" do
110
+ it "parses collection of typed property" do
111
+ album.extend(hash_album).from_hash("songs" => [{"name" => "One Shot Deal", "track" => 4},
112
+ {"name" => "Three Way Dance", "track" => 5}]).must_equal Album.new([Song.new("One Shot Deal", 4), Song.new("Three Way Dance", 5)])
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -128,19 +128,19 @@ module JsonTest
128
128
 
129
129
  describe "#binding_for_definition" do
130
130
  it "returns ObjectBinding" do
131
- assert_kind_of Json::ObjectBinding, Json.binding_for_definition(Def.new(:band, :class => Hash))
131
+ assert_kind_of Representable::Hash::ObjectBinding, Json.binding_for_definition(Def.new(:band, :class => Hash))
132
132
  end
133
133
 
134
134
  it "returns TextBinding" do
135
- assert_kind_of Json::PropertyBinding, Json.binding_for_definition(Def.new(:band))
135
+ assert_kind_of Representable::Hash::PropertyBinding, Json.binding_for_definition(Def.new(:band))
136
136
  end
137
137
 
138
138
  it "returns HashBinding" do
139
- assert_kind_of Json::HashBinding, Json.binding_for_definition(Def.new(:band, :hash => true))
139
+ assert_kind_of Representable::Hash::HashBinding, Json.binding_for_definition(Def.new(:band, :hash => true))
140
140
  end
141
141
 
142
142
  it "returns CollectionBinding" do
143
- assert_kind_of Json::CollectionBinding, Json.binding_for_definition(Def.new(:band, :collection => true))
143
+ assert_kind_of Representable::Hash::CollectionBinding, Json.binding_for_definition(Def.new(:band, :collection => true))
144
144
  end
145
145
  end
146
146
 
@@ -136,12 +136,6 @@ class RepresentableTest < MiniTest::Spec
136
136
 
137
137
 
138
138
  describe "#property" do
139
- it "creates accessors for the attribute" do
140
- @band = PunkBand.new
141
- assert @band.name = "Bad Religion"
142
- assert_equal "Bad Religion", @band.name
143
- end
144
-
145
139
  describe ":from" do
146
140
  # TODO: do this with all options.
147
141
  it "can be set explicitly" do
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.5
4
+ version: 1.2.6
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-27 00:00:00.000000000 Z
12
+ date: 2012-10-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
@@ -158,12 +158,13 @@ files:
158
158
  - gemfiles/Gemfile.mongoid-2.4
159
159
  - lib/representable.rb
160
160
  - lib/representable/binding.rb
161
- - lib/representable/bindings/json_bindings.rb
161
+ - lib/representable/bindings/hash_bindings.rb
162
162
  - lib/representable/bindings/xml_bindings.rb
163
163
  - lib/representable/bindings/yaml_bindings.rb
164
164
  - lib/representable/coercion.rb
165
165
  - lib/representable/definition.rb
166
166
  - lib/representable/deprecations.rb
167
+ - lib/representable/hash.rb
167
168
  - lib/representable/hash_methods.rb
168
169
  - lib/representable/json.rb
169
170
  - lib/representable/json/collection.rb
@@ -176,7 +177,8 @@ files:
176
177
  - representable.gemspec
177
178
  - test/coercion_test.rb
178
179
  - test/definition_test.rb
179
- - test/json_bindings_test.rb
180
+ - test/hash_bindings_test.rb
181
+ - test/hash_test.rb
180
182
  - test/json_test.rb
181
183
  - test/mongoid_test.rb
182
184
  - test/polymorphic_test.rb