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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +47 -0
  3. data/Gemfile +5 -0
  4. data/README.md +33 -0
  5. data/lib/representable.rb +60 -73
  6. data/lib/representable/binding.rb +37 -194
  7. data/lib/representable/cached.rb +10 -46
  8. data/lib/representable/coercion.rb +8 -8
  9. data/lib/representable/config.rb +15 -75
  10. data/lib/representable/debug.rb +41 -59
  11. data/lib/representable/declarative.rb +34 -53
  12. data/lib/representable/decorator.rb +11 -40
  13. data/lib/representable/definition.rb +14 -15
  14. data/lib/representable/deprecations.rb +90 -0
  15. data/lib/representable/deserializer.rb +87 -82
  16. data/lib/representable/for_collection.rb +5 -3
  17. data/lib/representable/hash.rb +5 -3
  18. data/lib/representable/hash/binding.rb +6 -15
  19. data/lib/representable/hash/collection.rb +10 -6
  20. data/lib/representable/hash_methods.rb +5 -5
  21. data/lib/representable/insert.rb +31 -0
  22. data/lib/representable/json.rb +7 -3
  23. data/lib/representable/json/hash.rb +1 -1
  24. data/lib/representable/object/binding.rb +5 -5
  25. data/lib/representable/parse_strategies.rb +37 -3
  26. data/lib/representable/pipeline.rb +37 -5
  27. data/lib/representable/pipeline_factories.rb +88 -0
  28. data/lib/representable/serializer.rb +38 -44
  29. data/lib/representable/version.rb +1 -1
  30. data/lib/representable/xml.rb +4 -0
  31. data/lib/representable/xml/binding.rb +25 -31
  32. data/lib/representable/xml/collection.rb +5 -3
  33. data/lib/representable/xml/hash.rb +7 -2
  34. data/lib/representable/yaml.rb +6 -3
  35. data/lib/representable/yaml/binding.rb +4 -4
  36. data/representable.gemspec +3 -3
  37. data/test/---deserialize-pipeline_test.rb +37 -0
  38. data/test/binding_test.rb +7 -7
  39. data/test/cached_test.rb +31 -19
  40. data/test/coercion_test.rb +2 -2
  41. data/test/config/inherit_test.rb +13 -12
  42. data/test/config_test.rb +12 -67
  43. data/test/decorator_test.rb +4 -5
  44. data/test/default_test.rb +34 -0
  45. data/test/defaults_options_test.rb +93 -0
  46. data/test/definition_test.rb +19 -39
  47. data/test/exec_context_test.rb +1 -1
  48. data/test/filter_test.rb +18 -20
  49. data/test/getter_setter_test.rb +1 -8
  50. data/test/hash_bindings_test.rb +13 -13
  51. data/test/heritage_test.rb +62 -0
  52. data/test/if_test.rb +1 -0
  53. data/test/inherit_test.rb +5 -3
  54. data/test/instance_test.rb +3 -4
  55. data/test/json_test.rb +3 -59
  56. data/test/lonely_test.rb +47 -3
  57. data/test/nested_test.rb +8 -2
  58. data/test/pipeline_test.rb +259 -0
  59. data/test/populator_test.rb +76 -0
  60. data/test/realistic_benchmark.rb +39 -7
  61. data/test/render_nil_test.rb +21 -0
  62. data/test/represent_test.rb +2 -2
  63. data/test/representable_test.rb +33 -103
  64. data/test/schema_test.rb +5 -15
  65. data/test/serialize_deserialize_test.rb +2 -2
  66. data/test/skip_test.rb +1 -1
  67. data/test/test_helper.rb +6 -0
  68. data/test/uncategorized_test.rb +67 -0
  69. data/test/xml_bindings_test.rb +6 -6
  70. data/test/xml_test.rb +6 -6
  71. metadata +33 -13
  72. data/lib/representable/apply.rb +0 -13
  73. data/lib/representable/inheritable.rb +0 -71
  74. data/lib/representable/mapper.rb +0 -83
  75. data/lib/representable/populator.rb +0 -56
  76. data/test/inheritable_test.rb +0 -97
@@ -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
@@ -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
@@ -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