representable 2.3.0 → 2.4.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +47 -0
- data/Gemfile +5 -0
- data/README.md +33 -0
- data/lib/representable.rb +60 -73
- data/lib/representable/binding.rb +37 -194
- data/lib/representable/cached.rb +10 -46
- data/lib/representable/coercion.rb +8 -8
- data/lib/representable/config.rb +15 -75
- data/lib/representable/debug.rb +41 -59
- data/lib/representable/declarative.rb +34 -53
- data/lib/representable/decorator.rb +11 -40
- data/lib/representable/definition.rb +14 -15
- data/lib/representable/deprecations.rb +90 -0
- data/lib/representable/deserializer.rb +87 -82
- data/lib/representable/for_collection.rb +5 -3
- data/lib/representable/hash.rb +5 -3
- data/lib/representable/hash/binding.rb +6 -15
- data/lib/representable/hash/collection.rb +10 -6
- data/lib/representable/hash_methods.rb +5 -5
- data/lib/representable/insert.rb +31 -0
- data/lib/representable/json.rb +7 -3
- data/lib/representable/json/hash.rb +1 -1
- data/lib/representable/object/binding.rb +5 -5
- data/lib/representable/parse_strategies.rb +37 -3
- data/lib/representable/pipeline.rb +37 -5
- data/lib/representable/pipeline_factories.rb +88 -0
- data/lib/representable/serializer.rb +38 -44
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +4 -0
- data/lib/representable/xml/binding.rb +25 -31
- data/lib/representable/xml/collection.rb +5 -3
- data/lib/representable/xml/hash.rb +7 -2
- data/lib/representable/yaml.rb +6 -3
- data/lib/representable/yaml/binding.rb +4 -4
- data/representable.gemspec +3 -3
- data/test/---deserialize-pipeline_test.rb +37 -0
- data/test/binding_test.rb +7 -7
- data/test/cached_test.rb +31 -19
- data/test/coercion_test.rb +2 -2
- data/test/config/inherit_test.rb +13 -12
- data/test/config_test.rb +12 -67
- data/test/decorator_test.rb +4 -5
- data/test/default_test.rb +34 -0
- data/test/defaults_options_test.rb +93 -0
- data/test/definition_test.rb +19 -39
- data/test/exec_context_test.rb +1 -1
- data/test/filter_test.rb +18 -20
- data/test/getter_setter_test.rb +1 -8
- data/test/hash_bindings_test.rb +13 -13
- data/test/heritage_test.rb +62 -0
- data/test/if_test.rb +1 -0
- data/test/inherit_test.rb +5 -3
- data/test/instance_test.rb +3 -4
- data/test/json_test.rb +3 -59
- data/test/lonely_test.rb +47 -3
- data/test/nested_test.rb +8 -2
- data/test/pipeline_test.rb +259 -0
- data/test/populator_test.rb +76 -0
- data/test/realistic_benchmark.rb +39 -7
- data/test/render_nil_test.rb +21 -0
- data/test/represent_test.rb +2 -2
- data/test/representable_test.rb +33 -103
- data/test/schema_test.rb +5 -15
- data/test/serialize_deserialize_test.rb +2 -2
- data/test/skip_test.rb +1 -1
- data/test/test_helper.rb +6 -0
- data/test/uncategorized_test.rb +67 -0
- data/test/xml_bindings_test.rb +6 -6
- data/test/xml_test.rb +6 -6
- metadata +33 -13
- data/lib/representable/apply.rb +0 -13
- data/lib/representable/inheritable.rb +0 -71
- data/lib/representable/mapper.rb +0 -83
- data/lib/representable/populator.rb +0 -56
- data/test/inheritable_test.rb +0 -97
data/lib/representable/apply.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
module Representable
|
2
|
-
module Apply
|
3
|
-
# Iterates over all property/collection definitions and yields the Definition instance.
|
4
|
-
def apply(&block)
|
5
|
-
representable_attrs.each do |dfn|
|
6
|
-
block.call(dfn)
|
7
|
-
dfn.representer_module.extend(Apply).apply(&block) if dfn.representer_module # nested.
|
8
|
-
end
|
9
|
-
|
10
|
-
self
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
@@ -1,71 +0,0 @@
|
|
1
|
-
module Representable
|
2
|
-
# Objects marked cloneable will be cloned in #inherit!.
|
3
|
-
module Cloneable
|
4
|
-
# Implements recursive cloning for Hash.
|
5
|
-
# Values to clone have to include Cloneable.
|
6
|
-
class Hash < ::Hash
|
7
|
-
include Cloneable # This class is marked as Cloneable itself.
|
8
|
-
|
9
|
-
module Clone
|
10
|
-
def clone
|
11
|
-
self.class[ collect { |k,v| [k, clone_value(v)] } ]
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
def clone_value(value)
|
16
|
-
return value.clone if value.is_a?(Cloneable)
|
17
|
-
value
|
18
|
-
end
|
19
|
-
end
|
20
|
-
include Clone
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# Objects marked cloneable will be inherit!ed in #inherit! when available in parent and child.
|
25
|
-
# TODO: #inherit! will be removed in future versions of Representable in favor of #clone, only. manually merging objects sucks.
|
26
|
-
module Inheritable
|
27
|
-
include Cloneable # all Inheritable are also Cloneable since #clone is one step of our inheritance.
|
28
|
-
|
29
|
-
class Array < ::Array
|
30
|
-
include Inheritable
|
31
|
-
|
32
|
-
def inherit!(parent)
|
33
|
-
push(*parent.clone)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# TODO: remove me.
|
38
|
-
class Hash < ::Hash
|
39
|
-
include Inheritable
|
40
|
-
|
41
|
-
module InstanceMethods
|
42
|
-
# FIXME: this method is currently run exactly once, for representable_attrs.inherit!(parent). it is only used for Config.
|
43
|
-
def inherit!(parent)
|
44
|
-
#merge!(parent.clone)
|
45
|
-
for key in (parent.keys + keys).uniq
|
46
|
-
next unless parent_value = parent[key]
|
47
|
-
|
48
|
-
self[key].inherit!(parent_value) and next if self[key].is_a?(Inheritable)
|
49
|
-
self[key] = parent_value.clone and next if parent_value.is_a?(Cloneable)
|
50
|
-
|
51
|
-
self[key] = parent_value # merge! behaviour
|
52
|
-
end
|
53
|
-
|
54
|
-
self
|
55
|
-
end
|
56
|
-
|
57
|
-
def clone
|
58
|
-
self.class[ collect { |k,v| [k, clone_value(v)] } ]
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
def clone_value(value)
|
63
|
-
return value.clone if value.is_a?(Cloneable)
|
64
|
-
value
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
include InstanceMethods
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
data/lib/representable/mapper.rb
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
module Representable
|
2
|
-
# Render and parse by looping over the representer's properties and dispatching to bindings.
|
3
|
-
# Conditionals are handled here, too.
|
4
|
-
class Mapper
|
5
|
-
module Methods
|
6
|
-
def initialize(bindings)
|
7
|
-
@bindings = bindings
|
8
|
-
end
|
9
|
-
|
10
|
-
def bindings(represented, options)
|
11
|
-
@bindings.each do |binding|
|
12
|
-
binding.update!(represented, options)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def deserialize(represented, doc, options, private_options)
|
17
|
-
bindings(represented, options).each do |bin|
|
18
|
-
deserialize_property(bin, doc, options, private_options)
|
19
|
-
end
|
20
|
-
represented
|
21
|
-
end
|
22
|
-
|
23
|
-
def serialize(represented, doc, options, private_options)
|
24
|
-
bindings(represented, options).each do |bin|
|
25
|
-
serialize_property(bin, doc, options, private_options)
|
26
|
-
end
|
27
|
-
doc
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
def serialize_property(binding, doc, options, private_options)
|
32
|
-
return if skip_property?(binding, private_options.merge(:action => :serialize))
|
33
|
-
compile_fragment(binding, doc)
|
34
|
-
end
|
35
|
-
|
36
|
-
def deserialize_property(binding, doc, options, private_options)
|
37
|
-
return if skip_property?(binding, private_options.merge(:action => :deserialize))
|
38
|
-
uncompile_fragment(binding, doc)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Checks and returns if the property should be included.
|
42
|
-
# 1.78 0.107 0.025 0.000 0.081 30002 Representable::Mapper::Methods#skip_property?
|
43
|
-
# 0.96 0.013 0.013 0.000 0.000 30002 Representable::Mapper::Methods#skip_property? hash only
|
44
|
-
# 1.15 0.025 0.016 0.000 0.009 30002 Representable::Mapper::Methods#skip_property?
|
45
|
-
|
46
|
-
def skip_property?(binding, private_options)
|
47
|
-
return unless private_options[:include] || private_options[:exclude] || binding.skip_filters?
|
48
|
-
|
49
|
-
return true if skip_excluded_property?(binding, private_options) # no need for further evaluation when :exclude'ed
|
50
|
-
return true if skip_protected_property(binding, private_options)
|
51
|
-
|
52
|
-
skip_conditional_property?(binding)
|
53
|
-
end
|
54
|
-
|
55
|
-
def skip_excluded_property?(binding, private_options)
|
56
|
-
return unless props = private_options[:exclude] || private_options[:include]
|
57
|
-
res = props.include?(binding.name.to_sym)
|
58
|
-
private_options[:include] ? !res : res
|
59
|
-
end
|
60
|
-
|
61
|
-
def skip_conditional_property?(binding)
|
62
|
-
return unless condition = binding[:if]
|
63
|
-
|
64
|
-
not binding.evaluate_option(:if)
|
65
|
-
end
|
66
|
-
|
67
|
-
# DISCUSS: this could be just another :if option in a Pipeline?
|
68
|
-
def skip_protected_property(binding, private_options)
|
69
|
-
private_options[:action] == :serialize ? binding[:readable] == false : binding[:writeable] == false
|
70
|
-
end
|
71
|
-
|
72
|
-
def compile_fragment(bin, doc)
|
73
|
-
bin.compile_fragment(doc)
|
74
|
-
end
|
75
|
-
|
76
|
-
def uncompile_fragment(bin, doc)
|
77
|
-
bin.uncompile_fragment(doc)
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
include Methods
|
82
|
-
end
|
83
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
module Representable
|
2
|
-
#
|
3
|
-
# populator
|
4
|
-
# skip_parse? --> return
|
5
|
-
# deserialize (this is where additional logic can happen, e.g. Object-HAL's collection semantics).
|
6
|
-
# parse_filter
|
7
|
-
# set
|
8
|
-
class Populator # rename to Deserializer?
|
9
|
-
def initialize(binding)
|
10
|
-
@binding = binding
|
11
|
-
end
|
12
|
-
|
13
|
-
# goal of this is to have this workflow apply-able to collections AND to items per collection, or for items in hashes.
|
14
|
-
def call(fragment, doc)
|
15
|
-
# the rest should be applied per item (collection) or per fragment (collection and property)
|
16
|
-
if fragment == Binding::FragmentNotFound
|
17
|
-
return unless @binding.has_default?
|
18
|
-
value = @binding[:default]
|
19
|
-
else
|
20
|
-
# DISCUSS: should we return a Skip object instead of this block trick? (same in Binding#serialize?)
|
21
|
-
value = deserialize(fragment) { return } # stop here if skip_parse.
|
22
|
-
end
|
23
|
-
|
24
|
-
value = @binding.parse_filter(value, doc)
|
25
|
-
# parse_filter
|
26
|
-
# set
|
27
|
-
@binding.set(value)
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
def deserialize(fragment)
|
32
|
-
return yield if @binding.evaluate_option(:skip_parse, fragment) # TODO: move this into Deserializer.
|
33
|
-
|
34
|
-
# use a Deserializer to transform fragment to/into object.
|
35
|
-
deserializer.call(fragment) # CollectionDeserializer/HashDeserializer/etc.
|
36
|
-
end
|
37
|
-
|
38
|
-
def deserializer
|
39
|
-
@binding.deserializer
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
|
-
# A separated collection deserializer/populator allows us better dealing with populating/modifying
|
44
|
-
# collections of models. (e.g. replace, update, push, etc.).
|
45
|
-
# That also gives us a place to apply options like :parse_filter, etc. per item.
|
46
|
-
class Collection < self
|
47
|
-
private
|
48
|
-
def deserialize(fragment)
|
49
|
-
return deserializer.call(fragment)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
class Hash < self
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
data/test/inheritable_test.rb
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
# tests Inheritable:: classes (the #inherit! method). This can be moved to uber if necessary.
|
4
|
-
|
5
|
-
class ConfigInheritableTest < MiniTest::Spec
|
6
|
-
class CloneableObject
|
7
|
-
include Representable::Cloneable
|
8
|
-
|
9
|
-
# same instance returns same clone.
|
10
|
-
def clone
|
11
|
-
@clone ||= super
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
|
-
# Inheritable::Array
|
17
|
-
it do
|
18
|
-
parent = Representable::Inheritable::Array.new([1,2,3])
|
19
|
-
child = Representable::Inheritable::Array.new([4])
|
20
|
-
|
21
|
-
child.inherit!(parent).must_equal([4,1,2,3])
|
22
|
-
end
|
23
|
-
|
24
|
-
# Inheritable::Hash
|
25
|
-
Inheritable = Representable::Inheritable
|
26
|
-
describe "Inheritable::Hash" do
|
27
|
-
it do
|
28
|
-
parent = Inheritable::Hash[
|
29
|
-
:volume => volume = Uber::Options::Value.new(9),
|
30
|
-
:genre => "Powermetal",
|
31
|
-
:only_parent => only_parent = Representable::Inheritable::Array["Pumpkin Box"],
|
32
|
-
:in_both => in_both = Representable::Inheritable::Array["Roxanne"],
|
33
|
-
:hash => {:type => :parent},
|
34
|
-
:clone => parent_clone = CloneableObject.new # cloneable is in both hashes.
|
35
|
-
]
|
36
|
-
child = Inheritable::Hash[
|
37
|
-
:genre => "Metal",
|
38
|
-
:pitch => 99,
|
39
|
-
:in_both => Representable::Inheritable::Array["Generator"],
|
40
|
-
:hash => {:type => :child},
|
41
|
-
:clone => child_clone = CloneableObject.new
|
42
|
-
]
|
43
|
-
|
44
|
-
child.inherit!(parent)
|
45
|
-
|
46
|
-
# order:
|
47
|
-
child.to_a.must_equal [
|
48
|
-
[:genre, "Powermetal"], # parent overrides child
|
49
|
-
[:pitch, 99], # parent doesn't define pitch
|
50
|
-
[:in_both, ["Generator", "Roxanne"]], # Inheritable array gets "merged".
|
51
|
-
[:hash, {:type => :parent}], # normal hash merge: parent overwrites child value.
|
52
|
-
[:clone, parent_clone.clone],
|
53
|
-
[:volume, volume],
|
54
|
-
[:only_parent, ["Pumpkin Box"]],
|
55
|
-
]
|
56
|
-
|
57
|
-
# clone
|
58
|
-
child[:only_parent].object_id.wont_equal parent[:only_parent].object_id
|
59
|
-
child[:clone].object_id.wont_equal parent[:clone].object_id
|
60
|
-
|
61
|
-
# still a hash:
|
62
|
-
child.must_equal(
|
63
|
-
:genre => "Powermetal",
|
64
|
-
:pitch => 99,
|
65
|
-
:in_both => ["Generator", "Roxanne"],
|
66
|
-
:hash => {:type => :parent},
|
67
|
-
:clone => parent_clone.clone,
|
68
|
-
:volume => volume,
|
69
|
-
:only_parent => ["Pumpkin Box"]
|
70
|
-
)
|
71
|
-
end
|
72
|
-
|
73
|
-
# nested:
|
74
|
-
it do
|
75
|
-
parent = Inheritable::Hash[
|
76
|
-
:details => Inheritable::Hash[
|
77
|
-
:title => title = "Man Of Steel",
|
78
|
-
:length => length = Representable::Definition.new(:length) # Cloneable.
|
79
|
-
]]
|
80
|
-
|
81
|
-
child = Inheritable::Hash[].inherit!(parent)
|
82
|
-
child[:details][:track] = 1
|
83
|
-
|
84
|
-
parent.must_equal({:details => {:title => "Man Of Steel", :length => length}})
|
85
|
-
|
86
|
-
child.keys.must_equal [:details]
|
87
|
-
child[:details].keys.must_equal [:title, :length, :track]
|
88
|
-
child[:details][:title].must_equal "Man Of Steel"
|
89
|
-
child[:details][:track].must_equal 1
|
90
|
-
child[:details][:length].name.must_equal "length"
|
91
|
-
|
92
|
-
# clone
|
93
|
-
child[:details][:title].object_id.must_equal title.object_id
|
94
|
-
child[:details][:length].object_id.wont_equal length.object_id
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|