representable 1.2.7 → 1.2.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,8 @@
1
+ h2. 1.2.8
2
+
3
+ * Reverting all the bullshit from 1.2.7 making it even better. @Binding@s now wrap their @Definition@ instance adopting its API. Moved the binding_for_definition mechanics to the respecting @Binding@ subclass.
4
+ * Added :readable and :writeable to #property: while @:readable => true@ renders the property into the document @:writeable => true@ allows updating the property's value when consuming a representation. Both default to @true@.
5
+
1
6
  h2. 1.2.7
2
7
 
3
8
  * Moving @Format.binding_for_definition@ to @Format#{format}_binding_for_definition@, making it an instance method in its own "namespace". This allows mixing in multiple representer engines into a user's representer module.
@@ -1,5 +1,6 @@
1
1
  require 'representable/deprecations'
2
2
  require 'representable/definition'
3
+ require 'representable/feature/readable_writeable'
3
4
 
4
5
  # Representable can be used in two ways.
5
6
  #
@@ -35,15 +36,14 @@ module Representable
35
36
  extend ClassMethods::Declarations
36
37
 
37
38
  include Deprecations
39
+ include Feature::ReadableWriteable
38
40
  end
39
41
  end
40
42
 
41
43
  # Reads values from +doc+ and sets properties accordingly.
42
44
  def update_properties_from(doc, options, format)
43
45
  representable_bindings_for(format).each do |bin|
44
- next if skip_property?(bin, options)
45
-
46
- uncompile_fragment(bin, doc)
46
+ deserialize_property(bin, doc, options)
47
47
  end
48
48
  self
49
49
  end
@@ -52,13 +52,21 @@ private
52
52
  # Compiles the document going through all properties.
53
53
  def create_representation_with(doc, options, format)
54
54
  representable_bindings_for(format).each do |bin|
55
- next if skip_property?(bin, options)
56
-
57
- compile_fragment(bin, doc)
55
+ serialize_property(bin, doc, options)
58
56
  end
59
57
  doc
60
58
  end
61
-
59
+
60
+ def serialize_property(binding, doc, options)
61
+ return if skip_property?(binding, options)
62
+ compile_fragment(binding, doc)
63
+ end
64
+
65
+ def deserialize_property(binding, doc, options)
66
+ return if skip_property?(binding, options)
67
+ uncompile_fragment(binding, doc)
68
+ end
69
+
62
70
  # Checks and returns if the property should be included.
63
71
  def skip_property?(binding, options)
64
72
  return true if skip_excluded_property?(binding, options) # no need for further evaluation when :exclude'ed
@@ -68,19 +76,19 @@ private
68
76
 
69
77
  def skip_excluded_property?(binding, options)
70
78
  return unless props = options[:exclude] || options[:include]
71
- res = props.include?(binding.definition.name.to_sym)
79
+ res = props.include?(binding.name.to_sym)
72
80
  options[:include] ? !res : res
73
81
  end
74
82
 
75
83
  def skip_conditional_property?(binding)
76
- return unless condition = binding.definition.options[:if]
84
+ return unless condition = binding.options[:if]
77
85
  not instance_exec(&condition)
78
86
  end
79
87
 
80
88
  # Retrieve value and write fragment to the doc.
81
89
  def compile_fragment(bin, doc)
82
- value = send(bin.definition.getter)
83
- value = bin.definition.default_for(value)
90
+ value = send(bin.getter)
91
+ value = bin.default_for(value)
84
92
 
85
93
  write_fragment_for(bin, value, doc)
86
94
  end
@@ -90,15 +98,15 @@ private
90
98
  value = read_fragment_for(bin, doc)
91
99
 
92
100
  if value == Binding::FragmentNotFound
93
- return unless bin.definition.has_default?
94
- value = bin.definition.default
101
+ return unless bin.has_default?
102
+ value = bin.default
95
103
  end
96
104
 
97
- send(bin.definition.setter, value)
105
+ send(bin.setter, value)
98
106
  end
99
107
 
100
108
  def write_fragment_for(bin, value, doc) # DISCUSS: move to Binding?
101
- return if bin.definition.skipable_nil_value?(value)
109
+ return if bin.skipable_nil_value?(value)
102
110
  bin.write(doc, value)
103
111
  end
104
112
 
@@ -111,7 +119,7 @@ private
111
119
  end
112
120
 
113
121
  def representable_bindings_for(format)
114
- representable_attrs.map {|attr| send("#{format}_binding_for_definition", attr) } # DISCUSS: call that on attr directly?
122
+ representable_attrs.map {|attr| format.build_for(attr) }
115
123
  end
116
124
 
117
125
  # Returns the wrapper for the representation. Mostly used in XML.
@@ -119,7 +127,6 @@ private
119
127
  representable_attrs.wrap_for(self.class.name)
120
128
  end
121
129
 
122
-
123
130
  module ClassInclusions
124
131
  def included(base)
125
132
  super
@@ -163,6 +170,8 @@ private
163
170
  # property :name, :class => Name
164
171
  # property :name, :default => "Mike"
165
172
  # property :name, :render_nil => true
173
+ # property :name, :readable => false
174
+ # property :name, :writeable => false
166
175
  def property(name, options={})
167
176
  representable_attrs << definition_class.new(name, options)
168
177
  end
@@ -1,13 +1,13 @@
1
+ require 'delegate'
2
+
1
3
  module Representable
2
- class Binding
4
+ # The Binding wraps the Definition instance for this property and provides methods to read/write fragments.
5
+ class Binding < SimpleDelegator
3
6
  class FragmentNotFound
4
7
  end
5
8
 
6
-
7
- attr_reader :definition # TODO: merge Binding and Definition.
8
-
9
- def initialize(definition)
10
- @definition = definition
9
+ def definition # TODO: remove in 1.4.
10
+ raise "Binding#definition is no longer supported as all Definition methods are now delegated automatically."
11
11
  end
12
12
 
13
13
  # Main entry point for rendering/parsing a property object.
@@ -33,7 +33,7 @@ module Representable
33
33
  end
34
34
 
35
35
  def extend_for(object)
36
- if mod = definition.representer_module
36
+ if mod = representer_module
37
37
  object.extend(*mod)
38
38
  end
39
39
 
@@ -56,7 +56,7 @@ module Representable
56
56
  end
57
57
 
58
58
  def create_object
59
- definition.sought_type.new
59
+ sought_type.new
60
60
  end
61
61
  end
62
62
 
@@ -16,20 +16,26 @@ module Representable
16
16
 
17
17
 
18
18
  class PropertyBinding < Representable::Binding
19
- def initialize(definition) # FIXME. make generic.
19
+ def self.build_for(definition)
20
+ return CollectionBinding.new(definition) if definition.array?
21
+ return HashBinding.new(definition) if definition.hash?
22
+ new(definition)
23
+ end
24
+
25
+ def initialize(*args) # FIXME. make generic.
20
26
  super
21
- extend ObjectBinding if definition.typed?
27
+ extend ObjectBinding if typed?
22
28
  end
23
29
 
24
30
  def read(hash)
25
- 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.
31
+ return FragmentNotFound unless hash.has_key?(from) # DISCUSS: put it all in #read for performance. not really sure if i like returning that special thing.
26
32
 
27
- fragment = hash[definition.from]
33
+ fragment = hash[from]
28
34
  deserialize_from(fragment)
29
35
  end
30
36
 
31
37
  def write(hash, value)
32
- hash[definition.from] = serialize_for(value)
38
+ hash[from] = serialize_for(value)
33
39
  end
34
40
 
35
41
  def serialize_for(value)
@@ -24,15 +24,23 @@ module Representable
24
24
 
25
25
 
26
26
  class PropertyBinding < Binding
27
- def initialize(definition)
27
+ def self.build_for(definition)
28
+ return CollectionBinding.new(definition) if definition.array?
29
+ return HashBinding.new(definition) if definition.hash? and not definition.options[:use_attributes] # FIXME: hate this.
30
+ return AttributeHashBinding.new(definition) if definition.hash? and definition.options[:use_attributes]
31
+ return AttributeBinding.new(definition) if definition.attribute
32
+ new(definition)
33
+ end
34
+
35
+ def initialize(*args)
28
36
  super
29
- extend ObjectBinding if definition.typed? # FIXME.
37
+ extend ObjectBinding if typed? # FIXME.
30
38
  end
31
39
 
32
40
  def write(parent, value)
33
41
  wrap_node = parent
34
42
 
35
- if wrap = definition.options[:wrap]
43
+ if wrap = options[:wrap]
36
44
  parent << wrap_node = node_for(parent, wrap)
37
45
  end
38
46
 
@@ -41,7 +49,7 @@ module Representable
41
49
 
42
50
  def read(node)
43
51
  selector = "./#{xpath}"
44
- selector = "./#{definition.options[:wrap]}/#{xpath}" if definition.options[:wrap]
52
+ selector = "./#{options[:wrap]}/#{xpath}" if options[:wrap]
45
53
  nodes = node.search(selector)
46
54
 
47
55
  return FragmentNotFound if nodes.size == 0 # TODO: write dedicated test!
@@ -52,7 +60,7 @@ module Representable
52
60
  # Creates wrapped node for the property.
53
61
  def serialize_for(value, parent)
54
62
  #def serialize_for(value, parent, tag_name=definition.from)
55
- node = node_for(parent, definition.from)
63
+ node = node_for(parent, from)
56
64
  serialize_node(node, value)
57
65
  end
58
66
 
@@ -72,7 +80,7 @@ module Representable
72
80
 
73
81
  private
74
82
  def xpath
75
- definition.from
83
+ from
76
84
  end
77
85
 
78
86
  def node_for(parent, name)
@@ -138,11 +146,11 @@ module Representable
138
146
  # Represents a tag attribute. Currently this only works on the top-level tag.
139
147
  class AttributeBinding < PropertyBinding
140
148
  def read(node)
141
- deserialize(node[definition.from])
149
+ deserialize(node[from])
142
150
  end
143
151
 
144
152
  def serialize_for(value, parent)
145
- parent[definition.from] = serialize(value.to_s)
153
+ parent[from] = serialize(value.to_s)
146
154
  end
147
155
 
148
156
  def write(parent, value)
@@ -19,13 +19,21 @@ module Representable
19
19
  end
20
20
 
21
21
  class PropertyBinding < Representable::Hash::PropertyBinding
22
- def initialize(definition) # FIXME. make generic.
22
+ def self.build_for(definition)
23
+ return CollectionBinding.new(definition) 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)
28
+ end
29
+
30
+ def initialize(*args) # FIXME. make generic.
23
31
  super
24
- extend ObjectBinding if definition.typed?
32
+ extend ObjectBinding if typed?
25
33
  end
26
34
 
27
35
  def write(map, value)
28
- map.children << Psych::Nodes::Scalar.new(definition.from)
36
+ map.children << Psych::Nodes::Scalar.new(from)
29
37
  map.children << serialize_for(value) # FIXME: should be serialize.
30
38
  end
31
39
 
@@ -42,7 +50,7 @@ module Representable
42
50
  class CollectionBinding < PropertyBinding
43
51
  def serialize_for(value)
44
52
  Psych::Nodes::Sequence.new.tap do |seq|
45
- seq.style = Psych::Nodes::Sequence::FLOW if definition.options[:style] == :flow
53
+ seq.style = Psych::Nodes::Sequence::FLOW if options[:style] == :flow
46
54
  value.each { |obj| seq.children << super(obj) }
47
55
  end
48
56
  end
@@ -8,7 +8,7 @@ module Representable
8
8
  @name = sym.to_s
9
9
  @options = options
10
10
 
11
- options[:default] ||= [] if array? # FIXME: move to CollectionBinding!
11
+ @options[:default] ||= [] if array? # FIXME: move to CollectionBinding!
12
12
  end
13
13
 
14
14
  def clone
@@ -0,0 +1,30 @@
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
+ # Checks and returns if the property is writeable
19
+ def writeable?
20
+ return options[:writeable] if options.has_key?(:writeable)
21
+ true
22
+ end
23
+
24
+ # Checks and returns if the property is readable
25
+ def readable?
26
+ return options[:readable] if options.has_key?(:readable)
27
+ true
28
+ end
29
+ end
30
+ end
@@ -22,28 +22,20 @@ module Representable
22
22
  end
23
23
 
24
24
 
25
- def from_hash(data, options={}, format=:hash)
25
+ def from_hash(data, options={}, binding_builder=PropertyBinding)
26
26
  if wrap = options[:wrap] || representation_wrap
27
27
  data = data[wrap.to_s]
28
28
  end
29
29
 
30
- update_properties_from(data, options, format)
30
+ update_properties_from(data, options, binding_builder)
31
31
  end
32
32
 
33
- def to_hash(options={}, format=:hash)
34
- hash = create_representation_with({}, options, format)
33
+ def to_hash(options={}, binding_builder=PropertyBinding)
34
+ hash = create_representation_with({}, options, binding_builder)
35
35
 
36
36
  return hash unless wrap = options[:wrap] || representation_wrap
37
37
 
38
38
  {wrap => hash}
39
39
  end
40
-
41
- private
42
-
43
- def hash_binding_for_definition(definition)
44
- return Representable::Hash::CollectionBinding.new(definition) if definition.array?
45
- return Representable::Hash::HashBinding.new(definition) if definition.hash?
46
- Representable::Hash::PropertyBinding.new(definition)
47
- end
48
40
  end
49
41
  end
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.2.7"
2
+ VERSION = "1.2.8"
3
3
  end
@@ -37,28 +37,18 @@ module Representable
37
37
  end
38
38
 
39
39
  def from_node(node, options={})
40
- update_properties_from(node, options, :xml)
40
+ update_properties_from(node, options, PropertyBinding)
41
41
  end
42
42
 
43
43
  # Returns a Nokogiri::XML object representing this object.
44
44
  def to_node(options={})
45
45
  root_tag = options[:wrap] || representation_wrap
46
46
 
47
- create_representation_with(Nokogiri::XML::Node.new(root_tag.to_s, Nokogiri::XML::Document.new), options, :xml)
47
+ create_representation_with(Nokogiri::XML::Node.new(root_tag.to_s, Nokogiri::XML::Document.new), options, PropertyBinding)
48
48
  end
49
49
 
50
50
  def to_xml(*args)
51
51
  to_node(*args).to_s
52
52
  end
53
-
54
- private
55
-
56
- def xml_binding_for_definition(definition)
57
- return CollectionBinding.new(definition) if definition.array?
58
- return HashBinding.new(definition) if definition.hash? and not definition.options[:use_attributes] # FIXME: hate this.
59
- return AttributeHashBinding.new(definition) if definition.hash? and definition.options[:use_attributes]
60
- return AttributeBinding.new(definition) if definition.attribute
61
- PropertyBinding.new(definition)
62
- end
63
53
  end
64
54
  end
@@ -30,7 +30,7 @@ module Representable
30
30
 
31
31
  def from_yaml(doc, options={})
32
32
  hash = Psych.load(doc)
33
- from_hash(hash, options, :yaml)
33
+ from_hash(hash, options, PropertyBinding)
34
34
  end
35
35
 
36
36
  # Returns a Nokogiri::XML object representing this object.
@@ -38,7 +38,7 @@ module Representable
38
38
  #root_tag = options[:wrap] || representation_wrap
39
39
 
40
40
  Psych::Nodes::Mapping.new.tap do |map|
41
- create_representation_with(map, options, :yaml)
41
+ create_representation_with(map, options, PropertyBinding)
42
42
  end
43
43
  end
44
44
 
@@ -49,15 +49,5 @@ module Representable
49
49
  doc.children << to_ast(*args)
50
50
  stream.to_yaml
51
51
  end
52
-
53
- private
54
-
55
- def yaml_binding_for_definition(definition)
56
- return CollectionBinding.new(definition) if definition.array?
57
- #return HashBinding.new(definition) if definition.hash? and not definition.options[:use_attributes] # FIXME: hate this.
58
- #return AttributeHashBinding.new(definition) if definition.hash? and definition.options[:use_attributes]
59
- #return AttributeBinding.new(definition) if definition.attribute
60
- PropertyBinding.new(definition)
61
- end
62
52
  end
63
53
  end
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gem 'representable', :path => "../"
@@ -10,34 +10,41 @@ class VirtusCoercionTest < MiniTest::Spec
10
10
  module SongRepresenter
11
11
  include Representable::JSON
12
12
  include Representable::Coercion
13
- property :composed_at, :type => DateTime
13
+ property :composed_at, :type => DateTime
14
+ property :track, :type => Integer
14
15
  end
15
16
 
16
17
  it "coerces properties in #from_json" do
17
- song = Song.new.extend(SongRepresenter).from_json("{\"composed_at\":\"November 18th, 1983\"}")
18
+ song = Song.new.extend(SongRepresenter).from_json("{\"composed_at\":\"November 18th, 1983\",\"track\":\"18\"}")
18
19
  assert_kind_of DateTime, song.composed_at
20
+ assert_equal 18, song.track
19
21
  assert_equal DateTime.parse("Fri, 18 Nov 1983 00:00:00 +0000"), song.composed_at
20
22
  end
21
23
  end
22
24
 
23
25
 
24
- class ImmigrantSong
25
- include Representable::JSON
26
- include Virtus
27
- include Representable::Coercion
26
+ describe "on class level" do
27
+ class ImmigrantSong
28
+ attr_accessor :track
29
+ include Representable::JSON
30
+ include Virtus
31
+ include Representable::Coercion
32
+
33
+ property :composed_at, :type => DateTime, :default => "May 12th, 2012"
34
+ property :track, :type => Integer
35
+ end
28
36
 
29
- property :composed_at, :type => DateTime, :default => "May 12th, 2012"
30
- end
31
-
32
- it "coerces into the provided type" do
33
- song = ImmigrantSong.new.from_json("{\"composed_at\":\"November 18th, 1983\"}")
34
- assert_equal DateTime.parse("Fri, 18 Nov 1983 00:00:00 +0000"), song.composed_at
35
- end
36
-
37
- it "respects the :default options" do
38
- song = ImmigrantSong.new.from_json("{}")
39
- assert_kind_of DateTime, song.composed_at
40
- assert_equal DateTime.parse("Mon, 12 May 2012 00:00:00 +0000"), song.composed_at
37
+ it "coerces into the provided type" do
38
+ song = ImmigrantSong.new.from_json("{\"composed_at\":\"November 18th, 1983\",\"track\":\"18\"}")
39
+ assert_equal DateTime.parse("Fri, 18 Nov 1983 00:00:00 +0000"), song.composed_at
40
+ assert_equal 18, song.track
41
+ end
42
+
43
+ it "respects the :default options" do
44
+ song = ImmigrantSong.new.from_json("{}")
45
+ assert_kind_of DateTime, song.composed_at
46
+ assert_equal DateTime.parse("Mon, 12 May 2012 00:00:00 +0000"), song.composed_at
47
+ end
41
48
  end
42
49
 
43
50
  end
@@ -128,6 +128,56 @@ class DefinitionTest < MiniTest::Spec
128
128
  end
129
129
  end
130
130
 
131
+
132
+ describe "#writeable?" do
133
+
134
+ it "returns true when :writeable is not given" do
135
+ @def = Representable::Definition.new(:song)
136
+ assert_equal true, @def.writeable?
137
+ end
138
+
139
+ it "returns true when :writeable => true" do
140
+ @def = Representable::Definition.new(:song, :writeable => true)
141
+ assert_equal true, @def.writeable?
142
+ end
143
+
144
+ it "returns false when :writeable => false" do
145
+ @def = Representable::Definition.new(:song, :writeable => false)
146
+ assert_equal false, @def.writeable?
147
+ end
148
+
149
+ it "returns nil when :writeable is nil" do
150
+ @def = Representable::Definition.new(:song, :writeable => nil)
151
+ assert_equal nil, @def.writeable?
152
+ end
153
+
154
+ end
155
+
156
+ describe "#readable?" do
157
+
158
+ it "returns true when :readable is not given" do
159
+ @def = Representable::Definition.new(:song)
160
+ assert_equal true, @def.readable?
161
+ end
162
+
163
+ it "returns true when :readable => true" do
164
+ @def = Representable::Definition.new(:song, :readable => true)
165
+ assert_equal true, @def.readable?
166
+ end
167
+
168
+ it "returns false when :readable => false" do
169
+ @def = Representable::Definition.new(:song, :readable => false)
170
+ assert_equal false, @def.readable?
171
+ end
172
+
173
+ it "returns nil when :readable is nil" do
174
+ @def = Representable::Definition.new(:song, :readable => nil)
175
+ assert_equal nil, @def.readable?
176
+ end
177
+
178
+ end
179
+
180
+
131
181
  describe ":collection => true" do
132
182
  before do
133
183
  @def = Representable::Definition.new(:songs, :collection => true, :tag => :song)
@@ -0,0 +1,159 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ require 'representable/yaml'
5
+ require 'ostruct'
6
+
7
+ class Song < OpenStruct
8
+ end
9
+
10
+ song = Song.new(:title => "Fallout", :track => 1)
11
+
12
+ require 'representable/json'
13
+ module SongRepresenter
14
+ include Representable::JSON
15
+
16
+ property :title
17
+ property :track
18
+ end
19
+
20
+ puts song.extend(SongRepresenter).to_json
21
+
22
+ rox = Song.new.extend(SongRepresenter).from_json(%{ {"title":"Roxanne"} })
23
+ puts rox.inspect
24
+
25
+ module SongRepresenter
26
+ include Representable::JSON
27
+
28
+ self.representation_wrap= :hit
29
+
30
+ property :title
31
+ property :track
32
+ end
33
+
34
+ puts song.extend(SongRepresenter).to_json
35
+
36
+
37
+
38
+ ######### collections
39
+
40
+ module SongRepresenter
41
+ include Representable::JSON
42
+
43
+ self.representation_wrap= false
44
+ end
45
+
46
+ module SongRepresenter
47
+ include Representable::JSON
48
+
49
+ property :title
50
+ property :track
51
+ collection :composers
52
+ end
53
+
54
+
55
+ song = Song.new(:title => "Fallout", :composers => ["Steward Copeland", "Sting"])
56
+ puts song.extend(SongRepresenter).to_json
57
+
58
+
59
+ ######### nesting types
60
+
61
+ class Album < OpenStruct
62
+ end
63
+
64
+ module AlbumRepresenter
65
+ include Representable::JSON
66
+
67
+ property :name
68
+ property :song, :extend => SongRepresenter, :class => Song
69
+ end
70
+
71
+ album = Album.new(:name => "The Police", :song => song)
72
+ puts album.extend(AlbumRepresenter).to_json
73
+
74
+
75
+ module AlbumRepresenter
76
+ include Representable::JSON
77
+
78
+ property :name
79
+ collection :songs, :extend => SongRepresenter, :class => Song
80
+ end
81
+
82
+ album = Album.new(:name => "The Police", :songs => [song, Song.new(:title => "Synchronicity")])
83
+ puts album.extend(AlbumRepresenter).to_json
84
+
85
+
86
+ SongRepresenter.module_eval do
87
+ @representable_attrs = nil
88
+ end
89
+
90
+
91
+ ######### inheritance
92
+ module SongRepresenter
93
+ include Representable::JSON
94
+
95
+ property :title
96
+ property :track
97
+ end
98
+
99
+ module CoverSongRepresenter
100
+ include Representable::JSON
101
+ include SongRepresenter
102
+
103
+ property :covered_by
104
+ end
105
+
106
+
107
+ song = Song.new(:title => "Truth Hits Everybody", :covered_by => "No Use For A Name")
108
+ puts song.extend(CoverSongRepresenter).to_json
109
+
110
+
111
+ ### XML
112
+ require 'representable/xml'
113
+ module SongRepresenter
114
+ include Representable::XML
115
+
116
+ property :title
117
+ property :track
118
+ collection :composers
119
+ end
120
+ song = Song.new(:title => "Fallout", :composers => ["Steward Copeland", "Sting"])
121
+ puts song.extend(SongRepresenter).to_xml
122
+
123
+
124
+ SongRepresenter.module_eval do
125
+ @representable_attrs = nil
126
+ end
127
+
128
+
129
+ ### YAML
130
+ require 'representable/yaml'
131
+ module SongRepresenter
132
+ include Representable::YAML
133
+
134
+ property :title
135
+ property :track
136
+ collection :composers
137
+ end
138
+ puts song.extend(SongRepresenter).to_yaml
139
+
140
+
141
+ SongRepresenter.module_eval do
142
+ @representable_attrs = nil
143
+ end
144
+
145
+
146
+ ### YAML
147
+ module SongRepresenter
148
+ include Representable::YAML
149
+
150
+ property :title
151
+ property :track
152
+ collection :composers, :style => :flow
153
+ end
154
+ puts song.extend(SongRepresenter).to_yaml
155
+
156
+
157
+ ######### custom methods in representer (using helpers)
158
+ ######### r/w, conditions
159
+ #########
@@ -114,4 +114,23 @@ class YamlTest < MiniTest::Spec
114
114
  end
115
115
  end
116
116
  end
117
+
118
+
119
+ class DefinitionTest < MiniTest::Spec
120
+ it "what" do
121
+ class Representable::Hash::Binding < SimpleDelegator
122
+
123
+ end
124
+
125
+ definition = Representable::Definition.new(:name)
126
+ wrapped = Representable::Hash::Binding.new(definition)
127
+
128
+ wrapped.name.must_equal "name"
129
+ wrapped.hash?.must_equal nil
130
+ wrapped.array?.must_equal nil
131
+ wrapped.options.must_equal({})
132
+
133
+ #wrapped.
134
+ end
135
+ end
117
136
  end
@@ -126,28 +126,28 @@ module JsonTest
126
126
  end
127
127
  end
128
128
 
129
- describe "#binding_for_definition" do
129
+ describe "#build_for" do
130
130
  it "returns ObjectBinding" do
131
- assert_kind_of Representable::Hash::ObjectBinding, @band.send(:hash_binding_for_definition, Def.new(:band, :class => Hash))
131
+ assert_kind_of Representable::Hash::ObjectBinding, Representable::Hash::PropertyBinding.build_for(Def.new(:band, :class => Hash))
132
132
  end
133
133
 
134
134
  it "returns TextBinding" do
135
- assert_kind_of Representable::Hash::PropertyBinding, @band.send(:hash_binding_for_definition, Def.new(:band))
135
+ assert_kind_of Representable::Hash::PropertyBinding, Representable::Hash::PropertyBinding.build_for(Def.new(:band))
136
136
  end
137
137
 
138
138
  it "returns HashBinding" do
139
- assert_kind_of Representable::Hash::HashBinding, @band.send(:hash_binding_for_definition, Def.new(:band, :hash => true))
139
+ assert_kind_of Representable::Hash::HashBinding, Representable::Hash::PropertyBinding.build_for(Def.new(:band, :hash => true))
140
140
  end
141
141
 
142
142
  it "returns CollectionBinding" do
143
- assert_kind_of Representable::Hash::CollectionBinding, @band.send(:hash_binding_for_definition, Def.new(:band, :collection => true))
143
+ assert_kind_of Representable::Hash::CollectionBinding, Representable::Hash::PropertyBinding.build_for(Def.new(:band, :collection => true))
144
144
  end
145
145
  end
146
146
 
147
- describe "#representable_bindings" do
147
+ describe "#representable_bindings_for" do
148
148
  it "returns bindings for each property" do
149
- assert_equal 2, @band.send(:representable_bindings_for, :hash).size
150
- assert_equal "name", @band.send(:representable_bindings_for, :hash).first.definition.name
149
+ assert_equal 2, @band.send(:representable_bindings_for, Representable::JSON::PropertyBinding).size
150
+ assert_equal "name", @band.send(:representable_bindings_for, Representable::JSON::PropertyBinding).first.name
151
151
  end
152
152
  end
153
153
  end
@@ -98,6 +98,25 @@ class RepresentableTest < MiniTest::Spec
98
98
 
99
99
 
100
100
  describe "Representable" do
101
+ describe "inheritance" do
102
+ class CoverSong < OpenStruct
103
+ end
104
+ module SongRepresenter
105
+ include Representable::Hash
106
+ property :name
107
+ end
108
+ module CoverSongRepresenter
109
+ include Representable::Hash
110
+ include SongRepresenter
111
+ property :by
112
+ end
113
+
114
+ it "merges properties from all ancestors" do
115
+ props = {"name"=>"The Brews", "by"=>"Nofx"}
116
+ assert_equal(props, CoverSong.new(props).extend(CoverSongRepresenter).to_hash)
117
+
118
+ end
119
+ end
101
120
  it "allows mixing in multiple representers" do
102
121
  require 'representable/json'
103
122
  require 'representable/xml'
@@ -218,39 +237,46 @@ class RepresentableTest < MiniTest::Spec
218
237
  end
219
238
 
220
239
  it "copies values from document to object" do
221
- @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {}, :hash)
240
+ @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {}, Representable::Hash::PropertyBinding)
222
241
  assert_equal "No One's Choice", @band.name
223
242
  assert_equal 2, @band.groupies
224
243
  end
225
244
 
226
245
  it "accepts :exclude option" do
227
- @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:exclude => [:groupies]}, :hash)
246
+ @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:exclude => [:groupies]}, Representable::Hash::PropertyBinding)
228
247
  assert_equal "No One's Choice", @band.name
229
248
  assert_equal nil, @band.groupies
230
249
  end
231
250
 
232
251
  it "still accepts deprecated :except option" do # FIXME: remove :except option.
233
- assert_equal @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:except => [:groupies]}, :hash), @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:exclude => [:groupies]}, :hash)
252
+ assert_equal @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:except => [:groupies]}, Representable::Hash::PropertyBinding), @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:exclude => [:groupies]}, Representable::Hash::PropertyBinding)
234
253
  end
235
254
 
236
255
  it "accepts :include option" do
237
- @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:include => [:groupies]}, :hash)
256
+ @band.update_properties_from({"name"=>"No One's Choice", "groupies"=>2}, {:include => [:groupies]}, Representable::Hash::PropertyBinding)
238
257
  assert_equal 2, @band.groupies
239
258
  assert_equal nil, @band.name
240
259
  end
260
+
261
+ it "ignores non-writeable properties" do
262
+ @band = Class.new(Band) { property :name; collection :founders, :writeable => false; attr_accessor :founders }.new
263
+ @band.update_properties_from({"name" => "Iron Maiden", "groupies" => 2, "founders" => [{ "name" => "Steve Harris" }] }, {}, Representable::Hash::PropertyBinding)
264
+ assert_equal "Iron Maiden", @band.name
265
+ assert_equal nil, @band.founders
266
+ end
241
267
 
242
268
  it "always returns self" do
243
- assert_equal @band, @band.update_properties_from({"name"=>"Nofx"}, {}, :hash)
269
+ assert_equal @band, @band.update_properties_from({"name"=>"Nofx"}, {}, Representable::Hash::PropertyBinding)
244
270
  end
245
271
 
246
272
  it "includes false attributes" do
247
- @band.update_properties_from({"groupies"=>false}, {}, :hash)
273
+ @band.update_properties_from({"groupies"=>false}, {}, Representable::Hash::PropertyBinding)
248
274
  assert_equal false, @band.groupies
249
275
  end
250
276
 
251
277
  it "ignores (no-default) properties not present in the incoming document" do
252
- { Representable::JSON => [{}, :hash],
253
- Representable::XML => [xml(%{<band/>}), :xml]
278
+ { Representable::JSON => [{}, Representable::Hash::PropertyBinding],
279
+ Representable::XML => [xml(%{<band/>}), Representable::XML::PropertyBinding]
254
280
  }.each do |format, config|
255
281
  nested_repr = Module.new do # this module is never applied.
256
282
  include format
@@ -277,31 +303,40 @@ class RepresentableTest < MiniTest::Spec
277
303
  end
278
304
 
279
305
  it "compiles document from properties in object" do
280
- assert_equal({"name"=>"No One's Choice", "groupies"=>2}, @band.send(:create_representation_with, {}, {}, :hash))
306
+ assert_equal({"name"=>"No One's Choice", "groupies"=>2}, @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding))
281
307
  end
282
308
 
283
309
  it "accepts :exclude option" do
284
- hash = @band.send(:create_representation_with, {}, {:exclude => [:groupies]}, :hash)
310
+ hash = @band.send(:create_representation_with, {}, {:exclude => [:groupies]}, Representable::Hash::PropertyBinding)
285
311
  assert_equal({"name"=>"No One's Choice"}, hash)
286
312
  end
287
313
 
288
314
  it "still accepts deprecated :except option" do # FIXME: remove :except option.
289
- assert_equal @band.send(:create_representation_with, {}, {:except => [:groupies]}, :hash), @band.send(:create_representation_with, {}, {:exclude => [:groupies]}, :hash)
315
+ assert_equal @band.send(:create_representation_with, {}, {:except => [:groupies]}, Representable::Hash::PropertyBinding), @band.send(:create_representation_with, {}, {:exclude => [:groupies]}, Representable::Hash::PropertyBinding)
290
316
  end
291
317
 
292
318
  it "accepts :include option" do
293
- hash = @band.send(:create_representation_with, {}, {:include => [:groupies]}, :hash)
319
+ hash = @band.send(:create_representation_with, {}, {:include => [:groupies]}, Representable::Hash::PropertyBinding)
294
320
  assert_equal({"groupies"=>2}, hash)
295
321
  end
296
322
 
323
+ it "ignores non-readable properties" do
324
+ @band = Class.new(Band) { property :name; collection :founder_ids, :readable => false; attr_accessor :founder_ids }.new
325
+ @band.name = "Iron Maiden"
326
+ @band.founder_ids = [1,2,3]
327
+
328
+ hash = @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding)
329
+ assert_equal({"name" => "Iron Maiden"}, hash)
330
+ end
331
+
297
332
  it "does not write nil attributes" do
298
333
  @band.groupies = nil
299
- assert_equal({"name"=>"No One's Choice"}, @band.send(:create_representation_with, {}, {}, :hash))
334
+ assert_equal({"name"=>"No One's Choice"}, @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding))
300
335
  end
301
336
 
302
337
  it "writes false attributes" do
303
338
  @band.groupies = false
304
- assert_equal({"name"=>"No One's Choice","groupies"=>false}, @band.send(:create_representation_with, {}, {}, :hash))
339
+ assert_equal({"name"=>"No One's Choice","groupies"=>false}, @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding))
305
340
  end
306
341
 
307
342
  describe "when :render_nil is true" do
@@ -314,7 +349,7 @@ class RepresentableTest < MiniTest::Spec
314
349
 
315
350
  @band.extend(mod) # FIXME: use clean object.
316
351
  @band.groupies = nil
317
- hash = @band.send(:create_representation_with, {}, {}, :hash)
352
+ hash = @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding)
318
353
  assert_equal({"name"=>"No One's Choice", "groupies" => nil}, hash)
319
354
  end
320
355
 
@@ -327,7 +362,7 @@ class RepresentableTest < MiniTest::Spec
327
362
 
328
363
  @band.extend(mod) # FIXME: use clean object.
329
364
  @band.groupies = nil
330
- hash = @band.send(:create_representation_with, {}, {}, :hash)
365
+ hash = @band.send(:create_representation_with, {}, {}, Representable::Hash::PropertyBinding)
331
366
  assert_equal({"name"=>"No One's Choice", "groupies" => nil}, hash)
332
367
  end
333
368
  end
@@ -341,21 +376,21 @@ class RepresentableTest < MiniTest::Spec
341
376
  it "respects property when condition true" do
342
377
  @pop.class_eval { property :fame, :if => lambda { true } }
343
378
  band = @pop.new
344
- band.update_properties_from({"fame"=>"oh yes"}, {}, :hash)
379
+ band.update_properties_from({"fame"=>"oh yes"}, {}, Representable::Hash::PropertyBinding)
345
380
  assert_equal "oh yes", band.fame
346
381
  end
347
382
 
348
383
  it "ignores property when condition false" do
349
384
  @pop.class_eval { property :fame, :if => lambda { false } }
350
385
  band = @pop.new
351
- band.update_properties_from({"fame"=>"oh yes"}, {}, :hash)
386
+ band.update_properties_from({"fame"=>"oh yes"}, {}, Representable::Hash::PropertyBinding)
352
387
  assert_equal nil, band.fame
353
388
  end
354
389
 
355
390
  it "ignores property when :exclude'ed even when condition is true" do
356
391
  @pop.class_eval { property :fame, :if => lambda { true } }
357
392
  band = @pop.new
358
- band.update_properties_from({"fame"=>"oh yes"}, {:exclude => [:fame]}, :hash)
393
+ band.update_properties_from({"fame"=>"oh yes"}, {:exclude => [:fame]}, Representable::Hash::PropertyBinding)
359
394
  assert_equal nil, band.fame
360
395
  end
361
396
 
@@ -364,7 +399,7 @@ class RepresentableTest < MiniTest::Spec
364
399
  @pop.class_eval { property :fame, :if => lambda { groupies } }
365
400
  band = @pop.new
366
401
  band.groupies = true
367
- band.update_properties_from({"fame"=>"oh yes"}, {}, :hash)
402
+ band.update_properties_from({"fame"=>"oh yes"}, {}, Representable::Hash::PropertyBinding)
368
403
  assert_equal "oh yes", band.fame
369
404
  end
370
405
  end
@@ -124,22 +124,22 @@ class XmlTest < MiniTest::Spec
124
124
  end
125
125
 
126
126
 
127
- describe "XML#binding_for_definition" do
127
+ describe "XML::Binding#build_for" do
128
128
  it "returns AttributeBinding" do
129
- assert_kind_of XML::AttributeBinding, @band.send(:xml_binding_for_definition, Def.new(:band, :from => "band", :attribute => true))
129
+ assert_kind_of XML::AttributeBinding, XML::PropertyBinding.build_for(Def.new(:band, :from => "band", :attribute => true))
130
130
  end
131
131
 
132
132
  it "returns PropertyBinding" do
133
- assert_kind_of XML::PropertyBinding, @band.send(:xml_binding_for_definition, Def.new(:band, :class => Hash))
134
- assert_kind_of XML::PropertyBinding, @band.send(:xml_binding_for_definition, Def.new(:band, :from => :content))
133
+ assert_kind_of XML::PropertyBinding, XML::PropertyBinding.build_for(Def.new(:band, :class => Hash))
134
+ assert_kind_of XML::PropertyBinding, XML::PropertyBinding.build_for(Def.new(:band, :from => :content))
135
135
  end
136
136
 
137
137
  it "returns CollectionBinding" do
138
- assert_kind_of XML::CollectionBinding, @band.send(:xml_binding_for_definition, Def.new(:band, :collection => :true))
138
+ assert_kind_of XML::CollectionBinding, XML::PropertyBinding.build_for(Def.new(:band, :collection => :true))
139
139
  end
140
140
 
141
141
  it "returns HashBinding" do
142
- assert_kind_of XML::HashBinding, @band.send(:xml_binding_for_definition, Def.new(:band, :hash => :true))
142
+ assert_kind_of XML::HashBinding, XML::PropertyBinding.build_for(Def.new(:band, :hash => :true))
143
143
  end
144
144
  end
145
145
 
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.7
4
+ version: 1.2.8
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-10-07 00:00:00.000000000 Z
12
+ date: 2012-11-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
@@ -164,6 +164,7 @@ files:
164
164
  - lib/representable/coercion.rb
165
165
  - lib/representable/definition.rb
166
166
  - lib/representable/deprecations.rb
167
+ - lib/representable/feature/readable_writeable.rb
167
168
  - lib/representable/hash.rb
168
169
  - lib/representable/hash_methods.rb
169
170
  - lib/representable/json.rb
@@ -175,8 +176,10 @@ files:
175
176
  - lib/representable/xml/hash.rb
176
177
  - lib/representable/yaml.rb
177
178
  - representable.gemspec
179
+ - test/Gemfile
178
180
  - test/coercion_test.rb
179
181
  - test/definition_test.rb
182
+ - test/example.rb
180
183
  - test/hash_bindings_test.rb
181
184
  - test/hash_test.rb
182
185
  - test/json_test.rb