representable 1.5.3 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/{CHANGES.textile → CHANGES.md} +17 -0
- data/Gemfile +1 -1
- data/README.md +24 -3
- data/TODO +11 -3
- data/lib/representable.rb +37 -117
- data/lib/representable/coercion.rb +29 -4
- data/lib/representable/config.rb +52 -0
- data/lib/representable/decorator.rb +8 -7
- data/lib/representable/decorator/coercion.rb +2 -57
- data/lib/representable/definition.rb +1 -0
- data/lib/representable/feature/readable_writeable.rb +1 -1
- data/lib/representable/hash.rb +5 -0
- data/lib/representable/hash/collection.rb +38 -0
- data/lib/representable/hash_methods.rb +2 -2
- data/lib/representable/json.rb +5 -0
- data/lib/representable/json/collection.rb +3 -28
- data/lib/representable/mapper.rb +78 -0
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +15 -10
- data/lib/representable/xml/collection.rb +7 -25
- data/lib/representable/yaml.rb +16 -11
- data/test/coercion_test.rb +95 -66
- data/test/decorator_test.rb +10 -0
- data/test/inheritance_test.rb +21 -0
- data/test/json_test.rb +7 -7
- data/test/representable_test.rb +146 -26
- data/test/test_helper.rb +1 -0
- data/test/yaml_test.rb +1 -2
- metadata +26 -44
@@ -1,59 +1,4 @@
|
|
1
1
|
require 'representable/coercion'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
def self.included(base)
|
6
|
-
base.class_eval do
|
7
|
-
# DISCUSS: this assumes we have a Representer included, yet.
|
8
|
-
alias_method :representable_initialize, :initialize
|
9
|
-
alias_method :representable_to_hash, :to_hash
|
10
|
-
|
11
|
-
# FIXME: allow including coercion only from virtus.
|
12
|
-
include Virtus
|
13
|
-
undef_method(:initialize)
|
14
|
-
undef_method(:to_hash)
|
15
|
-
|
16
|
-
extend Representable::Coercion::ClassMethods
|
17
|
-
extend ClassMethods
|
18
|
-
|
19
|
-
def initialize(*args) # override Virtus' #initialize.
|
20
|
-
representable_initialize(*args)
|
21
|
-
end
|
22
|
-
|
23
|
-
def to_hash(*args) # override Virtus' #to_hash.
|
24
|
-
representable_to_hash(*args)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
module ClassMethods
|
30
|
-
def property(name, options={})
|
31
|
-
if options[:type]
|
32
|
-
options[:decorator_scope] = true # call setter on decorator so coercion kicks in.
|
33
|
-
create_writer(name)
|
34
|
-
create_reader(name)
|
35
|
-
end
|
36
|
-
|
37
|
-
super # Representable::Coercion.
|
38
|
-
end
|
39
|
-
|
40
|
-
private
|
41
|
-
# FIXME: dear @solnic, please make this better!
|
42
|
-
def create_writer(name)
|
43
|
-
# the call to super makes the actual coercion, which is then delegated to the represented instance.
|
44
|
-
define_method "#{name}=" do |v|
|
45
|
-
coerced_value = super(v).get(self)
|
46
|
-
represented.send("#{name}=", coerced_value)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def create_reader(name)
|
51
|
-
# the call to super makes the actual coercion, which is then delegated to the represented instance.
|
52
|
-
define_method "#{name}" do
|
53
|
-
send("#{name}=", represented.send(name))
|
54
|
-
super()
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
3
|
+
# TODO: deprecate for 1.7.
|
4
|
+
Representable::Decorator::Coercion = Representable::Coercion
|
@@ -14,7 +14,7 @@ module Representable
|
|
14
14
|
end
|
15
15
|
|
16
16
|
# TODO: i hate monkey-patching Definition here since it globally adds this options. However, for now this should be ok :-)
|
17
|
-
|
17
|
+
Definition.class_eval do
|
18
18
|
# Checks and returns if the property is writeable
|
19
19
|
def writeable?
|
20
20
|
return options[:writeable] if options.has_key?(:writeable)
|
data/lib/representable/hash.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Representable::Hash
|
2
|
+
module Collection
|
3
|
+
include Representable::Hash
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.class_eval do
|
7
|
+
include Representable
|
8
|
+
extend ClassMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def items(options)
|
15
|
+
collection :_self, options
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def create_representation_with(doc, options, format)
|
21
|
+
bin = representable_mapper(format, options).bindings.first
|
22
|
+
bin.write(doc, represented)
|
23
|
+
end
|
24
|
+
|
25
|
+
def update_properties_from(doc, options, format)
|
26
|
+
bin = representable_mapper(format, options).bindings.first
|
27
|
+
value = bin.deserialize_from(doc)
|
28
|
+
represented.replace(value)
|
29
|
+
end
|
30
|
+
|
31
|
+
# FIXME: refactor Definition so we can simply add options in #items to existing definition.
|
32
|
+
def representable_attrs
|
33
|
+
attrs = super
|
34
|
+
attrs << Definition.new(:_self, :collection => true) if attrs.size == 0
|
35
|
+
attrs
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -8,13 +8,13 @@ module Representable
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def create_representation_with(doc, options, format)
|
11
|
-
bin =
|
11
|
+
bin = representable_mapper(format, options).bindings.first
|
12
12
|
hash = filter_keys_for(represented, options)
|
13
13
|
bin.write(doc, hash)
|
14
14
|
end
|
15
15
|
|
16
16
|
def update_properties_from(doc, options, format)
|
17
|
-
bin =
|
17
|
+
bin = representable_mapper(format, options).bindings.first
|
18
18
|
hash = filter_keys_for(doc, options)
|
19
19
|
value = bin.deserialize_from(hash)
|
20
20
|
represented.replace(value)
|
data/lib/representable/json.rb
CHANGED
@@ -1,38 +1,13 @@
|
|
1
|
+
require 'representable/hash/collection'
|
2
|
+
|
1
3
|
module Representable::JSON
|
2
4
|
module Collection
|
3
5
|
include Representable::JSON
|
4
6
|
|
5
7
|
def self.included(base)
|
6
8
|
base.class_eval do
|
7
|
-
include Representable
|
8
|
-
extend ClassMethods
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
|
13
|
-
module ClassMethods
|
14
|
-
def items(options)
|
15
|
-
collection :_self, options
|
9
|
+
include Representable::Hash::Collection
|
16
10
|
end
|
17
11
|
end
|
18
|
-
|
19
|
-
|
20
|
-
def create_representation_with(doc, options, format)
|
21
|
-
bin = representable_bindings_for(format, options).first
|
22
|
-
bin.serialize_for(represented)
|
23
|
-
end
|
24
|
-
|
25
|
-
def update_properties_from(doc, options, format)
|
26
|
-
bin = representable_bindings_for(format, options).first
|
27
|
-
value = bin.deserialize_from(doc)
|
28
|
-
represented.replace(value)
|
29
|
-
end
|
30
|
-
|
31
|
-
# FIXME: refactor Definition so we can simply add options in #items to existing definition.
|
32
|
-
def representable_attrs
|
33
|
-
attrs = super
|
34
|
-
attrs << Definition.new(:_self, :collection => true) if attrs.size == 0
|
35
|
-
attrs
|
36
|
-
end
|
37
12
|
end
|
38
13
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'representable/feature/readable_writeable'
|
2
|
+
|
3
|
+
module Representable
|
4
|
+
# Render and parse by looping over the representer's properties and dispatching to bindings.
|
5
|
+
# Conditionals are handled here, too.
|
6
|
+
class Mapper
|
7
|
+
# DISCUSS: we need @represented here for evaluating the :if blocks. this could be done in the bindings_for asset.
|
8
|
+
module Methods
|
9
|
+
def initialize(bindings, represented, options)
|
10
|
+
@represented = represented # the (extended) model.
|
11
|
+
@bindings = bindings
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :bindings
|
15
|
+
|
16
|
+
def deserialize(doc, options)
|
17
|
+
bindings.each do |bin|
|
18
|
+
deserialize_property(bin, doc, options)
|
19
|
+
end
|
20
|
+
represented
|
21
|
+
end
|
22
|
+
|
23
|
+
def serialize(doc, options)
|
24
|
+
bindings.each do |bin|
|
25
|
+
serialize_property(bin, doc, options)
|
26
|
+
end
|
27
|
+
doc
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
attr_reader :represented
|
32
|
+
|
33
|
+
def serialize_property(binding, doc, options)
|
34
|
+
return if skip_property?(binding, options)
|
35
|
+
compile_fragment(binding, doc)
|
36
|
+
end
|
37
|
+
|
38
|
+
def deserialize_property(binding, doc, options)
|
39
|
+
return if skip_property?(binding, options)
|
40
|
+
uncompile_fragment(binding, doc)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Checks and returns if the property should be included.
|
44
|
+
def skip_property?(binding, options)
|
45
|
+
return true if skip_excluded_property?(binding, options) # no need for further evaluation when :exclude'ed
|
46
|
+
|
47
|
+
skip_conditional_property?(binding)
|
48
|
+
end
|
49
|
+
|
50
|
+
def skip_excluded_property?(binding, options)
|
51
|
+
return unless props = options[:exclude] || options[:include]
|
52
|
+
res = props.include?(binding.name.to_sym)
|
53
|
+
options[:include] ? !res : res
|
54
|
+
end
|
55
|
+
|
56
|
+
def skip_conditional_property?(binding)
|
57
|
+
# TODO: move to Binding.
|
58
|
+
return unless condition = binding.options[:if]
|
59
|
+
|
60
|
+
args = []
|
61
|
+
args << binding.user_options if condition.arity > 0 # TODO: remove arity check. users should know whether they pass options or not.
|
62
|
+
|
63
|
+
not represented.instance_exec(*args, &condition)
|
64
|
+
end
|
65
|
+
|
66
|
+
def compile_fragment(bin, doc)
|
67
|
+
bin.compile_fragment(doc)
|
68
|
+
end
|
69
|
+
|
70
|
+
def uncompile_fragment(bin, doc)
|
71
|
+
bin.uncompile_fragment(doc)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
include Methods
|
76
|
+
include Feature::ReadableWriteable # DISCUSS: make this pluggable.
|
77
|
+
end
|
78
|
+
end
|
data/lib/representable/xml.rb
CHANGED
@@ -11,12 +11,12 @@ module Representable
|
|
11
11
|
self.representation_wrap = true # let representable compute it.
|
12
12
|
end
|
13
13
|
end
|
14
|
-
|
15
|
-
|
14
|
+
|
15
|
+
|
16
16
|
module ClassMethods
|
17
17
|
# Creates a new Ruby object from XML using mapping information declared in the class.
|
18
18
|
#
|
19
|
-
# Accepts a block yielding the currently iterated Definition. If the block returns false
|
19
|
+
# Accepts a block yielding the currently iterated Definition. If the block returns false
|
20
20
|
# the property is skipped.
|
21
21
|
#
|
22
22
|
# Example:
|
@@ -24,29 +24,34 @@ module Representable
|
|
24
24
|
def from_xml(*args, &block)
|
25
25
|
create_represented(*args, &block).from_xml(*args)
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
def from_node(*args, &block)
|
29
29
|
create_represented(*args, &block).from_node(*args)
|
30
30
|
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def representer_engine
|
34
|
+
Representable::XML
|
35
|
+
end
|
31
36
|
end
|
32
|
-
|
33
|
-
|
37
|
+
|
38
|
+
|
34
39
|
def from_xml(doc, *args)
|
35
40
|
node = Nokogiri::XML(doc).root
|
36
41
|
from_node(node, *args)
|
37
42
|
end
|
38
|
-
|
43
|
+
|
39
44
|
def from_node(node, options={})
|
40
45
|
update_properties_from(node, options, PropertyBinding)
|
41
46
|
end
|
42
|
-
|
47
|
+
|
43
48
|
# Returns a Nokogiri::XML object representing this object.
|
44
49
|
def to_node(options={})
|
45
50
|
root_tag = options[:wrap] || representation_wrap
|
46
|
-
|
51
|
+
|
47
52
|
create_representation_with(Nokogiri::XML::Node.new(root_tag.to_s, Nokogiri::XML::Document.new), options, PropertyBinding)
|
48
53
|
end
|
49
|
-
|
54
|
+
|
50
55
|
def to_xml(*args)
|
51
56
|
to_node(*args).to_s
|
52
57
|
end
|
@@ -1,38 +1,20 @@
|
|
1
|
+
require 'representable/hash/collection'
|
2
|
+
|
1
3
|
module Representable::XML
|
2
4
|
module Collection
|
3
5
|
include Representable::XML
|
4
6
|
|
5
7
|
def self.included(base)
|
6
8
|
base.class_eval do
|
7
|
-
include Representable
|
8
|
-
|
9
|
+
include Representable::Hash::Collection
|
10
|
+
include Methods
|
9
11
|
end
|
10
12
|
end
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
collection :_self, options
|
14
|
+
module Methods
|
15
|
+
def update_properties_from(doc, *args)
|
16
|
+
super(doc.search("./*"), *args) # pass the list of collection items to Hash::Collection#update_properties_from.
|
16
17
|
end
|
17
18
|
end
|
18
|
-
|
19
|
-
|
20
|
-
def create_representation_with(doc, options, format)
|
21
|
-
bin = representable_bindings_for(format, options).first
|
22
|
-
bin.write(doc, represented)
|
23
|
-
end
|
24
|
-
|
25
|
-
def update_properties_from(doc, options, format)
|
26
|
-
bin = representable_bindings_for(format, options).first
|
27
|
-
value = bin.deserialize_from(doc.search("./*")) # FIXME: use Binding#read.
|
28
|
-
represented.replace(value)
|
29
|
-
end
|
30
|
-
|
31
|
-
# FIXME: refactor Definition so we can simply add options in #items to existing definition.
|
32
|
-
def representable_attrs
|
33
|
-
attrs = super
|
34
|
-
attrs << Definition.new(:_self, :collection => true) if attrs.size == 0
|
35
|
-
attrs
|
36
|
-
end
|
37
19
|
end
|
38
20
|
end
|
data/lib/representable/yaml.rb
CHANGED
@@ -4,7 +4,7 @@ require 'representable/bindings/yaml_bindings'
|
|
4
4
|
module Representable
|
5
5
|
module YAML
|
6
6
|
include Hash
|
7
|
-
|
7
|
+
|
8
8
|
def self.included(base)
|
9
9
|
base.class_eval do
|
10
10
|
include Representable
|
@@ -12,12 +12,12 @@ module Representable
|
|
12
12
|
#self.representation_wrap = true # let representable compute it.
|
13
13
|
end
|
14
14
|
end
|
15
|
-
|
16
|
-
|
15
|
+
|
16
|
+
|
17
17
|
module ClassMethods
|
18
18
|
# Creates a new Ruby object from XML using mapping information declared in the class.
|
19
19
|
#
|
20
|
-
# Accepts a block yielding the currently iterated Definition. If the block returns false
|
20
|
+
# Accepts a block yielding the currently iterated Definition. If the block returns false
|
21
21
|
# the property is skipped.
|
22
22
|
#
|
23
23
|
# Example:
|
@@ -25,27 +25,32 @@ module Representable
|
|
25
25
|
def from_yaml(*args, &block)
|
26
26
|
create_represented(*args, &block).from_yaml(*args)
|
27
27
|
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def representer_engine
|
31
|
+
Representable::YAML
|
32
|
+
end
|
28
33
|
end
|
29
|
-
|
30
|
-
|
34
|
+
|
35
|
+
|
31
36
|
def from_yaml(doc, options={})
|
32
37
|
hash = Psych.load(doc)
|
33
38
|
from_hash(hash, options, PropertyBinding)
|
34
39
|
end
|
35
|
-
|
40
|
+
|
36
41
|
# Returns a Nokogiri::XML object representing this object.
|
37
42
|
def to_ast(options={})
|
38
43
|
#root_tag = options[:wrap] || representation_wrap
|
39
|
-
|
44
|
+
|
40
45
|
Psych::Nodes::Mapping.new.tap do |map|
|
41
46
|
create_representation_with(map, options, PropertyBinding)
|
42
47
|
end
|
43
48
|
end
|
44
|
-
|
49
|
+
|
45
50
|
def to_yaml(*args)
|
46
|
-
stream = Psych::Nodes::Stream.new
|
51
|
+
stream = Psych::Nodes::Stream.new
|
47
52
|
stream.children << doc = Psych::Nodes::Document.new
|
48
|
-
|
53
|
+
|
49
54
|
doc.children << to_ast(*args)
|
50
55
|
stream.to_yaml
|
51
56
|
end
|