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,58 +1,22 @@
1
1
  module Representable
2
2
  # Using this module only makes sense with Decorator representers.
3
3
  module Cached
4
- # The main point here is that the decorator instance simply saves its mapper. Since the mapper
5
- # in turn stores the bindings, we have a straight-forward way of "caching" the bindings without
6
- # having to mess around on the class level: this all happens in the decorator _instance_.
7
- #
8
- # Every binding in turn stores its nested representer (if it has one), implementing a recursive caching.
9
- #
10
- # Decorator -> Mapper -> [Binding->Decorator, Binding]
11
- def representable_mapper(format, options)
12
- @mapper ||= super.tap do |mapper|
13
- mapper.bindings(represented, options).each { |binding| binding.extend(Binding) }
14
- end
15
- end
16
-
17
- # replace represented for each property in this representer.
18
- def update!(represented)
19
- @represented = represented
20
- self
21
- end
22
-
23
- # TODO: also for deserializer.
24
- # TODO: create Populator in Binding, too (easier to override).
25
- module Binding
26
- def serializer
27
- @__serializer ||= super.tap do |serializer|
28
- serializer.extend(Serializer)
29
- end
30
- end
4
+ module Property
5
+ def property(*)
6
+ super.tap do |property|
7
+ binding_builder = format_engine::Binding
31
8
 
32
- def deserializer
33
- @__deserializer ||= super.tap do |deserializer|
34
- deserializer.extend(Serializer)
9
+ map << binding_builder.build(property)
35
10
  end
36
11
  end
37
12
  end
38
13
 
39
- module Serializer
40
- def prepare_for(mod, object)
41
- if representer = @binding.cached_representer
42
- return representer.update!(object)
43
- end
44
-
45
- # puts "--------> caching representer for #{object} in #{@binding.object_id}"
46
- @binding.cached_representer = super(mod, object)
47
- end
14
+ def self.included(includer)
15
+ includer.extend(Property)
16
+ end
48
17
 
49
- # for Deserializer::Collection.
50
- # TODO: this is a temporary solution.
51
- def item_deserializer
52
- @__item_deserializer ||= super.tap do |deserializer|
53
- deserializer.extend(Serializer)
54
- end
55
- end
18
+ def representable_map(*)
19
+ self.class.map
56
20
  end
57
21
  end
58
22
  end
@@ -9,8 +9,8 @@ module Representable
9
9
 
10
10
  # This gets called when the :render_filter or :parse_filter option is evaluated.
11
11
  # Usually the Coercer instance is an element in a Pipeline to allow >1 filters per property.
12
- def call(value, doc, options)
13
- Virtus::Attribute.build(@type).coerce(value)
12
+ def call(input, options)
13
+ Virtus::Attribute.build(@type).coerce(input)
14
14
  end
15
15
  end
16
16
 
@@ -24,13 +24,13 @@ module Representable
24
24
 
25
25
 
26
26
  module ClassMethods
27
- def build_definition(name, options, &block) # Representable::Declarative
28
- return super unless type = options[:type]
27
+ def property(name, options={}, &block)
28
+ super.tap do |definition|
29
+ return definition unless type = options[:type]
29
30
 
30
- options[:render_filter] << coercer = Coercer.new(type)
31
- options[:parse_filter] << coercer
32
-
33
- super
31
+ definition.merge!(render_filter: coercer = Coercer.new(type))
32
+ definition.merge!(parse_filter: coercer)
33
+ end
34
34
  end
35
35
  end
36
36
  end
@@ -1,91 +1,31 @@
1
- # Caching of Bindings
2
- # in Decorators, this could be an instance class var. when inherited, it is automatically busted.
3
- # however, in modules we can't do that. we never know when a module is "finalised", so we don't really know when to bust the cache.
4
-
5
1
  module Representable
6
- # Config contains three independent, inheritable directives: features, options and definitions.
7
- # It is a hash - just add directives if you need them.
8
- #
9
- # You may access/alter the property Definitions using #each, #collect, #add, #get.
2
+ # Stores Definitions from ::property. It preserves the adding order (1.9+).
3
+ # Same-named properties get overridden, just like in a Hash.
10
4
  #
11
- # * features, [options]: "horizontally"+"vertically" inherited values (inline representer)
12
- # * definitions, [options], wrap: "vertically" inherited (class inheritance, module inclusion)
13
- #
14
- # Inheritance works via Config#inherit!(parent).
15
- class Config < Inheritable::Hash
16
- # Keep in mind that performance doesn't matter here as 99.9% of all representers are created
17
- # at compile-time.
18
-
19
- # Stores Definitions from ::property. It preserves the adding order (1.9+).
20
- # Same-named properties get overridden, just like in a Hash.
21
- #
22
- # Overwrite definition_class if you need a custom Definition object (helpful when using
23
- # representable in other gems).
24
- class Definitions < Inheritable::Hash # TODO: cloneable!
25
- def initialize(definition_class)
26
- @definition_class = definition_class
27
- super()
28
- end
29
-
30
- def add(name, options, &block)
31
- if options[:inherit] and parent_property = get(name) # i like that: the :inherit shouldn't be handled outside.
32
- return parent_property.merge!(options, &block)
33
- end
34
- options.delete(:inherit) # TODO: can we handle the :inherit in one single place?
35
-
36
- self[name.to_s] = definition_class.new(name, options, &block)
37
- end
38
-
39
- def get(name)
40
- self[name.to_s]
41
- end
42
-
43
- def remove(name)
44
- delete(name.to_s)
45
- end
46
-
47
- extend Forwardable
48
- def_delegators :values, :each # so we look like an array. this is only used in Mapper. we could change that so we don't need to hide the hash.
49
-
50
- private
51
- attr_reader :definition_class
5
+ # Overwrite definition_class if you need a custom Definition object (helpful when using
6
+ # representable in other gems).
7
+ class Config < ::Declarative::Definitions
8
+ def remove(name)
9
+ delete(name.to_s)
52
10
  end
53
11
 
54
-
55
- def initialize(definition_class=Definition)
56
- super()
57
- merge!(
58
- :features => @features = Inheritable::Hash.new,
59
- :definitions => @definitions = Definitions.new(definition_class),
60
- :options => @options = Inheritable::Hash.new,
61
- :wrap => nil )
12
+ def options # FIXME: this is not inherited.
13
+ @options ||= {}
62
14
  end
63
- attr_reader :options
64
-
65
- def features
66
- @features.keys
67
- end
68
-
69
- # delegate #collect etc to Definitions instance.
70
- extend Forwardable
71
- def_delegators :@definitions, :get, :add, :each, :size, :remove
72
- # #collect comes from Hash and then gets delegated to @definitions. don't like that.
73
15
 
74
16
  def wrap=(value)
75
17
  value = value.to_s if value.is_a?(Symbol)
76
- self[:wrap] = Uber::Options::Value.new(value)
18
+ @wrap = Uber::Options::Value.new(value)
77
19
  end
78
20
 
79
21
  # Computes the wrap string or returns false.
80
- def wrap_for(name, context, *args, &block)
81
- return unless self[:wrap]
82
-
83
- value = self[:wrap].evaluate(context, *args)
22
+ def wrap_for(represented, *args, &block)
23
+ return unless @wrap
84
24
 
85
- name = yield if block_given? # DISCUSS: deprecate/restructure the entire wrapping flow.
25
+ value = @wrap.evaluate(represented, *args)
86
26
 
87
- return infer_name_for(name) if value === true
88
- value
27
+ return value if value != true
28
+ infer_name_for(represented.class.name)
89
29
  end
90
30
 
91
31
  private
@@ -1,36 +1,26 @@
1
1
  module Representable
2
2
  module Debug
3
- def self.extended(represented)
4
- represented.extend Representable
3
+ def update_properties_from(doc, options, format)
4
+ puts
5
+ puts "[Deserialize]........."
6
+ puts "[Deserialize] document #{doc.inspect}"
7
+ super
5
8
  end
6
9
 
7
- module Representable
8
- def update_properties_from(doc, options, format)
9
- puts
10
- puts "[Deserialize]........."
11
- puts "[Deserialize] document #{doc.inspect}"
12
- super
13
- end
14
-
15
- def create_representation_with(doc, options, format)
16
- puts
17
- puts "[Serialize]........."
18
- puts "[Serialize]"
19
- super
20
- end
10
+ def create_representation_with(doc, options, format)
11
+ puts
12
+ puts "[Serialize]........."
13
+ puts "[Serialize]"
14
+ super
15
+ end
21
16
 
22
- def representable_mapper(*args)
23
- super.extend(Mapper)
17
+ def representable_map(*)
18
+ super.tap do |arr|
19
+ arr.collect { |bin| bin.extend(Binding) }
24
20
  end
25
21
  end
26
22
 
27
23
  module Binding
28
- def read(doc)
29
- value = super
30
- puts " #read --> #{value.inspect}"
31
- value
32
- end
33
-
34
24
  def evaluate_option(name, *args, &block)
35
25
  puts "=====#{self[name]}" if name ==:prepare
36
26
  puts (evaled = self[name]) ?
@@ -42,54 +32,46 @@ module Representable
42
32
  value
43
33
  end
44
34
 
45
- def populator
46
- super.extend(Populator)
35
+ def parse_pipeline(*)
36
+ super.extend(Pipeline::Debug)
47
37
  end
48
38
 
49
- def serializer
50
- super.extend(Serializer)
39
+ def render_pipeline(*)
40
+ super.extend(Pipeline::Debug)
51
41
  end
52
42
  end
43
+ end
53
44
 
54
- module Populator
55
- def deserialize(fragment)
56
- puts " Populator#deserialize: #{fragment.inspect}"
57
- puts " : typed? is false, skipping Deserializer." if ! @binding.typed?
58
- super
59
- end
60
45
 
61
- def deserializer
62
- super.extend(Deserializer)
63
- end
46
+ module Pipeline::Debug
47
+ def call(input, options)
48
+ puts "Pipeline#call: #{inspect}"
49
+ puts " input: #{input.inspect}"
50
+ super
64
51
  end
65
52
 
66
- module Deserializer
67
- def create_object(fragment, *args)
68
- value = super
69
- puts " Deserializer#create_object: --> #{value.inspect}"
70
- value
71
- end
72
- end
53
+ def evaluate(block, memo, options)
54
+ block.extend(Pipeline::Debug) if block.is_a?(Collect)
73
55
 
74
- module Serializer
75
- def marshal(object, user_options)
76
- puts " Serializer#marshal: --> #{object.inspect}"
77
- super
56
+ puts " Pipeline : -> #{_inspect_function(block)} "
57
+ super.tap do |res|
58
+ puts " Pipeline : result: #{res.inspect}"
78
59
  end
79
60
  end
80
61
 
81
- module Mapper
82
- def uncompile_fragment(bin, doc)
83
- bin.extend(Binding)
84
- puts " uncompile_fragment: #{bin.name}"
85
- super
86
- end
62
+ def inspect
63
+ functions = collect do |func|
64
+ _inspect_function(func)
65
+ end.join(", ")
66
+ "#{self.class.to_s.split("::").last}[#{functions}]"
67
+ end
87
68
 
88
- def compile_fragment(bin, doc)
89
- bin.extend(Binding)
90
- puts " compile_fragment: #{bin.name}"
91
- super
92
- end
69
+ # prints SkipParse instead of <Proc>. i know, i can make this better, but not now.
70
+ def _inspect_function(func)
71
+ return func.extend(Pipeline::Debug).inspect if func.is_a?(Collect)
72
+ return func unless func.is_a?(Proc)
73
+ File.readlines(func.source_location[0])[func.source_location[1]-1].match(/^\s+(\w+)/)[1]
93
74
  end
94
75
  end
95
76
  end
77
+
@@ -1,16 +1,13 @@
1
1
  module Representable
2
2
  module Declarative
3
- def representable_attrs
4
- @representable_attrs ||= build_config
5
- end
6
-
7
3
  def representation_wrap=(name)
8
- representable_attrs.wrap = name
4
+ heritage.record(:representation_wrap=, name)
5
+
6
+ definitions.wrap = name
9
7
  end
10
8
 
11
9
  def collection(name, options={}, &block)
12
- options[:collection] = true # FIXME: don't override original.
13
- property(name, options, &block)
10
+ property(name, options.merge(collection: true), &block)
14
11
  end
15
12
 
16
13
  def hash(name=nil, options={}, &block)
@@ -20,67 +17,51 @@ module Representable
20
17
  property(name, options, &block)
21
18
  end
22
19
 
23
- # Allows you to nest a block of properties in a separate section while still mapping them to the outer object.
20
+ # Allows you to nest a block of properties in a separate section while still mapping
21
+ # them to the original object.
24
22
  def nested(name, options={}, &block)
25
23
  options = options.merge(
26
- :use_decorator => true,
27
- :getter => lambda { |*| self },
28
- :setter => lambda { |*| },
29
- :instance => lambda { |*| self }
30
- ) # DISCUSS: should this be a macro just as :parse_strategy?
24
+ getter: ->(options) { self },
25
+ setter: ->(options) { },
26
+ instance: ->(options) { self },
27
+ )
28
+
29
+ if block
30
+ options[:_nested_builder] = Decorator.nested_builder
31
+ options[:_base] = Decorator.default_nested_class
32
+ end
31
33
 
32
34
  property(name, options, &block)
33
35
  end
34
36
 
35
- def property(name, options={}, &block)
36
- representable_attrs.add(name, options) do |default_options| # handles :inherit.
37
- build_definition(name, default_options, &block)
38
- end
39
- end
40
37
 
41
- def build_inline(base, features, name, options, &block) # DISCUSS: separate module?
42
- Module.new do
43
- include Representable
44
- feature *features # Representable::JSON or similar.
45
- include base if base # base when :inherit, or in decorator.
38
+ include ::Declarative::Schema::DSL # ::property
39
+ include ::Declarative::Schema::Feature
40
+ include ::Declarative::Heritage::DSL
46
41
 
47
- module_eval &block
48
- end
42
+ def default_nested_class
43
+ Module.new # FIXME: make that unnecessary in Declarative
49
44
  end
50
45
 
51
- private
52
- # This method is meant to be overridden if you want to add or change DSL options.
53
- # The options hash is already initialized, add or change elements as you need.
54
- #
55
- # Example:
56
- #
57
- # def build_definition(name, options, &block)
58
- # options[:pass_options] = true
59
- # options[:render_filter] << Sanitizer.new if options[:sanitize]
60
- # options[:setter] << lambda { |fragment, options| send("set_#{options.binding.name}=", fragment) }
61
- # super # don't forget to call super
62
- # end
63
- def build_definition(name, options, &block)
64
- base = nil
65
-
66
- if options[:inherit] # TODO: move this to Definition.
67
- base = representable_attrs.get(name).representer_module
68
- end # FIXME: can we handle this in super/Definition.new ?
69
46
 
70
- if block
71
- options[:_inline] = true
72
- options[:extend] = inline_representer_for(base, representable_attrs.features, name, options, &block)
47
+ NestedBuilder = ->(options) do
48
+ Module.new do
49
+ include Representable # FIXME: do we really need this?
50
+ feature *options[:_features]
51
+ include *options[:_base] # base when :inherit, or in decorator.
52
+
53
+ module_eval &options[:_block]
73
54
  end
74
55
  end
75
56
 
76
- def inline_representer_for(base, features, name, options, &block)
77
- representer = options[:use_decorator] ? Decorator : self
78
-
79
- representer.build_inline(base, features, name, options, &block)
57
+ def nested_builder
58
+ NestedBuilder
80
59
  end
81
60
 
82
- def build_config
83
- Config.new
61
+ def definitions
62
+ @definitions ||= Config.new(Representable::Definition)
84
63
  end
85
- end # Declarations
64
+
65
+ alias_method :representable_attrs, :definitions
66
+ end
86
67
  end