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