representable 2.3.0 → 2.4.0.rc1
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 +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
|