representable 1.2.7 → 1.2.8

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,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