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.
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