representable 1.5.3 → 1.6.0
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.
- 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
|