representable 1.8.5 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGES.md +36 -0
- data/Gemfile +3 -3
- data/README.md +62 -2
- data/Rakefile +1 -1
- data/lib/representable.rb +32 -109
- data/lib/representable/TODO.getting_serious +7 -1
- data/lib/representable/autoload.rb +10 -0
- data/lib/representable/binding.rb +20 -16
- data/lib/representable/bindings/xml_bindings.rb +0 -1
- data/lib/representable/coercion.rb +23 -31
- data/lib/representable/config.rb +45 -46
- data/lib/representable/declarative.rb +78 -0
- data/lib/representable/decorator.rb +39 -10
- data/lib/representable/definition.rb +40 -33
- data/lib/representable/deserializer.rb +2 -0
- data/lib/representable/for_collection.rb +25 -0
- data/lib/representable/hash.rb +4 -8
- data/lib/representable/hash/collection.rb +2 -9
- data/lib/representable/hash_methods.rb +0 -7
- data/lib/representable/inheritable.rb +50 -0
- data/lib/representable/json.rb +3 -9
- data/lib/representable/json/collection.rb +1 -3
- data/lib/representable/json/hash.rb +4 -9
- data/lib/representable/mapper.rb +8 -5
- data/lib/representable/parse_strategies.rb +1 -0
- data/lib/representable/pipeline.rb +14 -0
- data/lib/representable/represent.rb +6 -0
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +3 -18
- data/lib/representable/xml/collection.rb +2 -4
- data/lib/representable/xml/hash.rb +2 -10
- data/lib/representable/yaml.rb +1 -20
- data/representable.gemspec +2 -2
- data/test/class_test.rb +5 -10
- data/test/coercion_test.rb +31 -92
- data/test/config/inherit_test.rb +128 -0
- data/test/config_test.rb +114 -80
- data/test/definition_test.rb +107 -64
- data/test/features_test.rb +41 -0
- data/test/filter_test.rb +59 -0
- data/test/for_collection_test.rb +74 -0
- data/test/inherit_test.rb +44 -3
- data/test/inheritable_test.rb +97 -0
- data/test/inline_test.rb +0 -18
- data/test/instance_test.rb +0 -19
- data/test/json_test.rb +9 -44
- data/test/lonely_test.rb +1 -0
- data/test/parse_strategy_test.rb +30 -0
- data/test/represent_test.rb +88 -0
- data/test/representable_test.rb +3 -50
- data/test/schema_test.rb +123 -0
- data/test/test_helper.rb +1 -1
- data/test/xml_test.rb +34 -38
- metadata +25 -15
- data/lib/representable/decorator/coercion.rb +0 -4
- data/lib/representable/readable_writeable.rb +0 -29
- data/test/inheritance_test.rb +0 -22
@@ -5,6 +5,8 @@ module Representable
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def deserialize(fragment)
|
8
|
+
# puts "deserialize #{@binding.name}" # TODO: introduce Representable::Debug.
|
9
|
+
|
8
10
|
# next step: get rid of collect.
|
9
11
|
fragment.enum_for(:each_with_index).collect do |item_fragment, i|
|
10
12
|
@deserializer = ObjectDeserializer.new(@binding)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Representable
|
2
|
+
# Gives us Representer::for_collection and its configuration directive
|
3
|
+
# ::collection_representer.
|
4
|
+
module ForCollection
|
5
|
+
def for_collection
|
6
|
+
# this is done at run-time, not a big fan of this. however, it saves us from inheritance/self problems.
|
7
|
+
@collection_representer ||= collection_representer!({}) # DON'T make it inheritable as it would inherit the wrong singular.
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def collection_representer!(options)
|
12
|
+
singular = self
|
13
|
+
|
14
|
+
# what happens here is basically
|
15
|
+
# Module.new { include Representable::JSON::Collection; ... }
|
16
|
+
build_inline(nil, [singular.collection_representer_class], "", {}) {
|
17
|
+
items options.merge(:extend => singular)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def collection_representer(options={})
|
22
|
+
@collection_representer = collection_representer!(options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/representable/hash.rb
CHANGED
@@ -9,19 +9,15 @@ module Representable
|
|
9
9
|
def self.included(base)
|
10
10
|
base.class_eval do
|
11
11
|
include Representable # either in Hero or HeroRepresentation.
|
12
|
-
extend ClassMethods
|
12
|
+
extend ClassMethods
|
13
|
+
register_feature Representable::Hash
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
17
|
|
17
18
|
module ClassMethods
|
18
|
-
def
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
def representer_engine
|
24
|
-
Representable::Hash
|
19
|
+
def collection_representer_class
|
20
|
+
Collection
|
25
21
|
end
|
26
22
|
end
|
27
23
|
|
@@ -4,9 +4,9 @@ module Representable::Hash
|
|
4
4
|
|
5
5
|
def self.included(base)
|
6
6
|
base.class_eval do
|
7
|
-
include Representable
|
7
|
+
include Representable::Hash
|
8
8
|
extend ClassMethods
|
9
|
-
|
9
|
+
representable_attrs.add(:_self, {:collection => true})
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
@@ -28,12 +28,5 @@ module Representable::Hash
|
|
28
28
|
value = bin.deserialize_from(doc)
|
29
29
|
represented.replace(value)
|
30
30
|
end
|
31
|
-
|
32
|
-
# FIXME: refactor Definition so we can simply add options in #items to existing definition.
|
33
|
-
def representable_attrs
|
34
|
-
attrs = super
|
35
|
-
attrs << Definition.new(:_self, :collection => true) if attrs.size == 0
|
36
|
-
attrs
|
37
|
-
end
|
38
31
|
end
|
39
32
|
end
|
@@ -1,12 +1,5 @@
|
|
1
1
|
module Representable
|
2
2
|
module HashMethods
|
3
|
-
# FIXME: refactor Definition so we can simply add options in #items to existing definition.
|
4
|
-
def representable_attrs
|
5
|
-
attrs = super
|
6
|
-
attrs << Definition.new(*definition_opts) if attrs.size == 0
|
7
|
-
attrs
|
8
|
-
end
|
9
|
-
|
10
3
|
def create_representation_with(doc, options, format)
|
11
4
|
bin = representable_mapper(format, options).bindings.first
|
12
5
|
hash = filter_keys_for(represented, options)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Representable
|
2
|
+
# Objects marked cloneable will be cloned in #inherit!.
|
3
|
+
module Cloneable
|
4
|
+
end
|
5
|
+
|
6
|
+
# Objects marked cloneable will be inherit!ed in #inherit! when available in parent and child.
|
7
|
+
module Inheritable
|
8
|
+
include Cloneable # all Inheritable are also Cloneable since #clone is one step of our inheritance.
|
9
|
+
|
10
|
+
class Array < ::Array
|
11
|
+
include Inheritable
|
12
|
+
|
13
|
+
def inherit!(parent)
|
14
|
+
push(*parent.clone)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Hash < ::Hash
|
19
|
+
include Inheritable
|
20
|
+
|
21
|
+
module InstanceMethods
|
22
|
+
def inherit!(parent)
|
23
|
+
#merge!(parent.clone)
|
24
|
+
for key in (parent.keys + keys).uniq
|
25
|
+
next unless parent_value = parent[key]
|
26
|
+
|
27
|
+
self[key].inherit!(parent_value) and next if self[key].is_a?(Inheritable)
|
28
|
+
self[key] = parent_value.clone and next if parent_value.is_a?(Cloneable)
|
29
|
+
|
30
|
+
self[key] = parent_value # merge! behaviour
|
31
|
+
end
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def clone
|
37
|
+
self.class[ collect { |k,v| [k, clone_value(v)] } ]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def clone_value(value)
|
42
|
+
return value.clone if value.is_a?(Cloneable)
|
43
|
+
value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
include InstanceMethods
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/representable/json.rb
CHANGED
@@ -11,20 +11,14 @@ module Representable
|
|
11
11
|
base.class_eval do
|
12
12
|
include Representable # either in Hero or HeroRepresentation.
|
13
13
|
extend ClassMethods # DISCUSS: do that only for classes?
|
14
|
-
|
14
|
+
register_feature Representable::JSON
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
|
19
19
|
module ClassMethods
|
20
|
-
|
21
|
-
|
22
|
-
create_represented(*args, &block).from_json(*args)
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
def representer_engine
|
27
|
-
Representable::JSON
|
20
|
+
def collection_representer_class
|
21
|
+
JSON::Collection
|
28
22
|
end
|
29
23
|
end
|
30
24
|
|
@@ -2,15 +2,15 @@ require 'representable/json'
|
|
2
2
|
require 'representable/hash_methods'
|
3
3
|
|
4
4
|
module Representable::JSON
|
5
|
+
# "Lonely Hash" support.
|
5
6
|
module Hash
|
6
|
-
include Representable::JSON
|
7
|
-
include Representable::HashMethods
|
8
|
-
|
9
7
|
def self.included(base)
|
10
8
|
base.class_eval do
|
11
9
|
include Representable
|
12
10
|
extend ClassMethods
|
13
|
-
|
11
|
+
include Representable::JSON
|
12
|
+
include Representable::HashMethods
|
13
|
+
representable_attrs.add(:_self, {:hash => true})
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
@@ -20,10 +20,5 @@ module Representable::JSON
|
|
20
20
|
hash(:_self, options, &block)
|
21
21
|
end
|
22
22
|
end
|
23
|
-
|
24
|
-
|
25
|
-
def definition_opts
|
26
|
-
[:_self, {:hash => true, :use_attributes => true}]
|
27
|
-
end
|
28
23
|
end
|
29
24
|
end
|
data/lib/representable/mapper.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'representable/readable_writeable'
|
2
|
-
|
3
1
|
module Representable
|
4
2
|
# Render and parse by looping over the representer's properties and dispatching to bindings.
|
5
3
|
# Conditionals are handled here, too.
|
@@ -28,18 +26,19 @@ module Representable
|
|
28
26
|
|
29
27
|
private
|
30
28
|
def serialize_property(binding, doc, options)
|
31
|
-
return if skip_property?(binding, options)
|
29
|
+
return if skip_property?(binding, options.merge(:action => :serialize))
|
32
30
|
compile_fragment(binding, doc)
|
33
31
|
end
|
34
32
|
|
35
33
|
def deserialize_property(binding, doc, options)
|
36
|
-
return if skip_property?(binding, options)
|
34
|
+
return if skip_property?(binding, options.merge(:action => :deserialize))
|
37
35
|
uncompile_fragment(binding, doc)
|
38
36
|
end
|
39
37
|
|
40
38
|
# Checks and returns if the property should be included.
|
41
39
|
def skip_property?(binding, options)
|
42
40
|
return true if skip_excluded_property?(binding, options) # no need for further evaluation when :exclude'ed
|
41
|
+
return true if skip_protected_property(binding, options)
|
43
42
|
|
44
43
|
skip_conditional_property?(binding)
|
45
44
|
end
|
@@ -56,6 +55,11 @@ module Representable
|
|
56
55
|
not binding.send(:evaluate_option, :if)
|
57
56
|
end
|
58
57
|
|
58
|
+
# DISCUSS: this could be just another :if option in a Pipeline?
|
59
|
+
def skip_protected_property(binding, options)
|
60
|
+
options[:action] == :serialize ? binding[:readable] == false : binding[:writeable] == false
|
61
|
+
end
|
62
|
+
|
59
63
|
def compile_fragment(bin, doc)
|
60
64
|
bin.compile_fragment(doc)
|
61
65
|
end
|
@@ -66,6 +70,5 @@ module Representable
|
|
66
70
|
end
|
67
71
|
|
68
72
|
include Methods
|
69
|
-
include ReadableWriteable # DISCUSS: make this pluggable.
|
70
73
|
end
|
71
74
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Representable
|
2
|
+
# Allows to implement a pipeline of filters where a value gets passed in and the result gets
|
3
|
+
# passed to the next callable object.
|
4
|
+
#
|
5
|
+
# Note: this is still experimental.
|
6
|
+
class Pipeline < Array
|
7
|
+
include Uber::Callable
|
8
|
+
# include Representable::Cloneable
|
9
|
+
|
10
|
+
def call(context, value, *args)
|
11
|
+
inject(value) { |memo, block| block.call(memo, *args) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/representable/xml.rb
CHANGED
@@ -9,6 +9,7 @@ module Representable
|
|
9
9
|
include Representable
|
10
10
|
extend ClassMethods
|
11
11
|
self.representation_wrap = true # let representable compute it.
|
12
|
+
register_feature Representable::XML
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
@@ -18,24 +19,8 @@ module Representable
|
|
18
19
|
representable_attrs.options[:remove_namespaces] = true
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
# Accepts a block yielding the currently iterated Definition. If the block returns false
|
24
|
-
# the property is skipped.
|
25
|
-
#
|
26
|
-
# Example:
|
27
|
-
# band.from_xml("<band><name>Nofx</name></band>")
|
28
|
-
def from_xml(*args, &block)
|
29
|
-
create_represented(*args, &block).from_xml(*args)
|
30
|
-
end
|
31
|
-
|
32
|
-
def from_node(*args, &block)
|
33
|
-
create_represented(*args, &block).from_node(*args)
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
def representer_engine
|
38
|
-
Representable::XML
|
22
|
+
def collection_representer_class
|
23
|
+
Collection
|
39
24
|
end
|
40
25
|
end
|
41
26
|
|
@@ -3,10 +3,8 @@ module Representable::XML
|
|
3
3
|
include Representable::XML
|
4
4
|
|
5
5
|
def self.included(base)
|
6
|
-
base.
|
7
|
-
|
8
|
-
include Methods
|
9
|
-
end
|
6
|
+
base.send :include, Representable::Hash::Collection
|
7
|
+
base.send :include, Methods
|
10
8
|
end
|
11
9
|
|
12
10
|
module Methods
|
@@ -10,6 +10,7 @@ module Representable::XML
|
|
10
10
|
base.class_eval do
|
11
11
|
include Representable
|
12
12
|
extend ClassMethods
|
13
|
+
representable_attrs.add(:_self, {:hash => true, :use_attributes => true})
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
@@ -19,11 +20,6 @@ module Representable::XML
|
|
19
20
|
hash :_self, options.merge!(:use_attributes => true)
|
20
21
|
end
|
21
22
|
end
|
22
|
-
|
23
|
-
|
24
|
-
def definition_opts
|
25
|
-
[:_self, {:hash => true, :use_attributes => true}]
|
26
|
-
end
|
27
23
|
end
|
28
24
|
|
29
25
|
module Hash
|
@@ -34,6 +30,7 @@ module Representable::XML
|
|
34
30
|
base.class_eval do
|
35
31
|
include Representable
|
36
32
|
extend ClassMethods
|
33
|
+
representable_attrs.add(:_self, {:hash => true})
|
37
34
|
end
|
38
35
|
end
|
39
36
|
|
@@ -43,10 +40,5 @@ module Representable::XML
|
|
43
40
|
hash :_self, options
|
44
41
|
end
|
45
42
|
end
|
46
|
-
|
47
|
-
|
48
|
-
def definition_opts
|
49
|
-
[:_self, {:hash => true}]
|
50
|
-
end
|
51
43
|
end
|
52
44
|
end
|
data/lib/representable/yaml.rb
CHANGED
@@ -7,27 +7,8 @@ module Representable
|
|
7
7
|
def self.included(base)
|
8
8
|
base.class_eval do
|
9
9
|
include Representable
|
10
|
-
extend ClassMethods
|
11
10
|
#self.representation_wrap = true # let representable compute it.
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
|
-
module ClassMethods
|
17
|
-
# Creates a new Ruby object from XML using mapping information declared in the class.
|
18
|
-
#
|
19
|
-
# Accepts a block yielding the currently iterated Definition. If the block returns false
|
20
|
-
# the property is skipped.
|
21
|
-
#
|
22
|
-
# Example:
|
23
|
-
# band.from_xml("<band><name>Nofx</name></band>")
|
24
|
-
def from_yaml(*args, &block)
|
25
|
-
create_represented(*args, &block).from_yaml(*args)
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
def representer_engine
|
30
|
-
Representable::YAML
|
11
|
+
register_feature Representable::YAML
|
31
12
|
end
|
32
13
|
end
|
33
14
|
|
data/representable.gemspec
CHANGED
@@ -21,13 +21,13 @@ Gem::Specification.new do |s|
|
|
21
21
|
|
22
22
|
s.add_dependency "nokogiri"
|
23
23
|
s.add_dependency "multi_json"
|
24
|
-
s.add_dependency "uber"
|
24
|
+
s.add_dependency "uber", "~> 0.0.7"
|
25
25
|
|
26
26
|
s.add_development_dependency "rake"
|
27
27
|
s.add_development_dependency "test_xml", ">= 0.1.6"
|
28
28
|
s.add_development_dependency "minitest", "~> 5.0.0"
|
29
29
|
s.add_development_dependency "mocha", ">= 0.13.0"
|
30
30
|
s.add_development_dependency "mongoid"
|
31
|
-
s.add_development_dependency "virtus"
|
31
|
+
s.add_development_dependency "virtus"
|
32
32
|
s.add_development_dependency "json", '~> 1.7.7'
|
33
33
|
end
|
data/test/class_test.rb
CHANGED
@@ -39,21 +39,16 @@ class ClassTest < BaseTest
|
|
39
39
|
end
|
40
40
|
|
41
41
|
|
42
|
-
|
42
|
+
# this throws a DeserializationError now.
|
43
|
+
describe "lambda { nil }" do
|
43
44
|
representer! do
|
44
45
|
property :title, :class => nil
|
45
46
|
end
|
46
47
|
|
47
|
-
it
|
48
|
-
|
49
|
-
|
50
|
-
def from_hash(hash, *args)
|
51
|
-
hash
|
52
|
-
end
|
48
|
+
it do
|
49
|
+
assert_raises Representable::DeserializeError do
|
50
|
+
OpenStruct.new.extend(representer).from_hash({"title" => {}})
|
53
51
|
end
|
54
|
-
|
55
|
-
song = OpenStruct.new.extend(representer).from_hash(hash = {"title" => object})
|
56
|
-
song.title.must_equal object
|
57
52
|
end
|
58
53
|
end
|
59
54
|
|