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,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "2.3.0"
2
+ VERSION = "2.4.0.rc1"
3
3
  end
@@ -25,6 +25,10 @@ module Representable
25
25
  representable_attrs.options[:remove_namespaces] = true
26
26
  end
27
27
 
28
+ def format_engine
29
+ Representable::XML
30
+ end
31
+
28
32
  def collection_representer_class
29
33
  Collection
30
34
  end
@@ -4,34 +4,34 @@ require 'representable/hash/binding.rb'
4
4
  module Representable
5
5
  module XML
6
6
  class Binding < Representable::Binding
7
- def self.build_for(definition, *args)
8
- return Collection.new(definition, *args) if definition.array?
9
- return Hash.new(definition, *args) if definition.hash? and not definition[:use_attributes] # FIXME: hate this.
10
- return AttributeHash.new(definition, *args) if definition.hash? and definition[:use_attributes]
11
- return Attribute.new(definition, *args) if definition[:attribute]
12
- return Content.new(definition, *args) if definition[:content]
13
- new(definition, *args)
7
+ def self.build_for(definition)
8
+ return Collection.new(definition) if definition.array?
9
+ return Hash.new(definition) if definition.hash? and not definition[:use_attributes] # FIXME: hate this.
10
+ return AttributeHash.new(definition) if definition.hash? and definition[:use_attributes]
11
+ return Attribute.new(definition) if definition[:attribute]
12
+ return Content.new(definition) if definition[:content]
13
+ new(definition)
14
14
  end
15
15
 
16
- def write(parent, fragments)
16
+ def write(parent, fragments, as)
17
17
  wrap_node = parent
18
18
 
19
19
  if wrap = self[:wrap]
20
20
  parent << wrap_node = node_for(parent, wrap)
21
21
  end
22
22
 
23
- wrap_node << serialize_for(fragments, parent)
23
+ wrap_node << serialize_for(fragments, parent, as)
24
24
  end
25
25
 
26
- def read(node)
27
- nodes = find_nodes(node)
26
+ def read(node, as)
27
+ nodes = find_nodes(node, as)
28
28
  return FragmentNotFound if nodes.size == 0 # TODO: write dedicated test!
29
29
 
30
30
  deserialize_from(nodes)
31
31
  end
32
32
 
33
33
  # Creates wrapped node for the property.
34
- def serialize_for(value, parent)
34
+ def serialize_for(value, parent, as)
35
35
  node = node_for(parent, as)
36
36
  serialize_node(node, value)
37
37
  end
@@ -57,13 +57,9 @@ module Representable
57
57
  end
58
58
 
59
59
  private
60
- def xpath
61
- as
62
- end
63
-
64
- def find_nodes(doc)
65
- selector = xpath
66
- selector = "#{self[:wrap]}/#{xpath}" if self[:wrap]
60
+ def find_nodes(doc, as)
61
+ selector = as
62
+ selector = "#{self[:wrap]}/#{as}" if self[:wrap]
67
63
  nodes = doc.xpath(selector)
68
64
  end
69
65
 
@@ -81,9 +77,9 @@ module Representable
81
77
  class Collection < self
82
78
  include Representable::Binding::Collection
83
79
 
84
- def serialize_for(value, parent)
80
+ def serialize_for(value, parent, as)
85
81
  # return NodeSet so << works.
86
- set_for(parent, value.collect { |item| super(item, parent) })
82
+ set_for(parent, value.collect { |item| super(item, parent, as) })
87
83
  end
88
84
 
89
85
  def deserialize_from(nodes)
@@ -102,9 +98,7 @@ module Representable
102
98
 
103
99
 
104
100
  class Hash < Collection
105
- include Representable::Binding::Hash
106
-
107
- def serialize_for(value, parent)
101
+ def serialize_for(value, parent, as)
108
102
  set_for(parent, value.collect do |k, v|
109
103
  node = node_for(parent, k)
110
104
  serialize_node(node, v)
@@ -123,7 +117,7 @@ module Representable
123
117
 
124
118
  class AttributeHash < Collection
125
119
  # DISCUSS: use AttributeBinding here?
126
- def write(parent, value) # DISCUSS: is it correct overriding #write here?
120
+ def write(parent, value, as) # DISCUSS: is it correct overriding #write here?
127
121
  value.collect do |k, v|
128
122
  parent[k] = v.to_s
129
123
  end
@@ -139,22 +133,22 @@ module Representable
139
133
 
140
134
  # Represents a tag attribute. Currently this only works on the top-level tag.
141
135
  class Attribute < self
142
- def read(node)
136
+ def read(node, as)
143
137
  node[as]
144
138
  end
145
139
 
146
- def serialize_for(value, parent)
140
+ def serialize_for(value, parent, as)
147
141
  parent[as] = value.to_s
148
142
  end
149
143
 
150
- def write(parent, value)
151
- serialize_for(value, parent)
144
+ def write(parent, value, as)
145
+ serialize_for(value, parent, as)
152
146
  end
153
147
  end
154
148
 
155
149
  # Represents tag content.
156
150
  class Content < self
157
- def read(node)
151
+ def read(node, as)
158
152
  node.content
159
153
  end
160
154
 
@@ -162,7 +156,7 @@ module Representable
162
156
  parent.content = value.to_s
163
157
  end
164
158
 
165
- def write(parent, value)
159
+ def write(parent, value, as)
166
160
  serialize_for(value, parent)
167
161
  end
168
162
  end
@@ -1,15 +1,17 @@
1
1
  module Representable::XML
2
2
  module Collection
3
- include Representable::XML
4
-
5
3
  def self.included(base)
6
4
  base.send :include, Representable::XML
7
5
  base.send :include, Representable::Hash::Collection
8
6
  base.send :include, Methods
9
7
  end
10
8
 
11
-
12
9
  module Methods
10
+ def create_representation_with(doc, options, format)
11
+ bin = representable_map(options, format).first
12
+ bin.write(doc, super, bin.name)
13
+ end
14
+
13
15
  def update_properties_from(doc, *args)
14
16
  super(doc.search("./*"), *args) # pass the list of collection items to Hash::Collection#update_properties_from.
15
17
  end
@@ -10,7 +10,7 @@ module Representable::XML
10
10
  base.class_eval do
11
11
  include Representable
12
12
  extend ClassMethods
13
- representable_attrs.add(:_self, {:hash => true, :use_attributes => true})
13
+ property(:_self, hash: true, use_attributes: true)
14
14
  end
15
15
  end
16
16
 
@@ -20,6 +20,11 @@ module Representable::XML
20
20
  hash :_self, options.merge!(:use_attributes => true)
21
21
  end
22
22
  end
23
+
24
+ def create_representation_with(doc, options, format)
25
+ bin = representable_bindings_for(format, options).first
26
+ bin.write(doc, super, options)
27
+ end
23
28
  end
24
29
 
25
30
  module Hash
@@ -30,7 +35,7 @@ module Representable::XML
30
35
  base.class_eval do
31
36
  include Representable
32
37
  extend ClassMethods
33
- representable_attrs.add(:_self, {:hash => true})
38
+ property(:_self, {:hash => true})
34
39
  end
35
40
  end
36
41
 
@@ -8,11 +8,16 @@ module Representable
8
8
  def self.included(base)
9
9
  base.class_eval do
10
10
  include Representable
11
- #self.representation_wrap = true # let representable compute it.
12
11
  register_feature Representable::YAML
12
+ extend ClassMethods
13
13
  end
14
14
  end
15
15
 
16
+ module ClassMethods
17
+ def format_engine
18
+ Representable::YAML
19
+ end
20
+ end
16
21
 
17
22
  def from_yaml(doc, options={})
18
23
  hash = Psych.load(doc)
@@ -21,8 +26,6 @@ module Representable
21
26
 
22
27
  # Returns a Nokogiri::XML object representing this object.
23
28
  def to_ast(options={})
24
- #root_tag = options[:wrap] || representation_wrap
25
-
26
29
  Psych::Nodes::Mapping.new.tap do |map|
27
30
  create_representation_with(map, options, Binding)
28
31
  end
@@ -3,12 +3,12 @@ require 'representable/hash/binding'
3
3
  module Representable
4
4
  module YAML
5
5
  class Binding < Representable::Hash::Binding
6
- def self.build_for(definition, *args)
7
- return Collection.new(definition, *args) if definition.array?
8
- new(definition, *args)
6
+ def self.build_for(definition)
7
+ return Collection.new(definition) if definition.array?
8
+ new(definition)
9
9
  end
10
10
 
11
- def write(map, fragment)
11
+ def write(map, fragment, as)
12
12
  map.children << Psych::Nodes::Scalar.new(as)
13
13
  map.children << node_for(fragment) # FIXME: should be serialize.
14
14
  end
@@ -1,4 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
1
  lib = File.expand_path('../lib/', __FILE__)
3
2
  $:.unshift lib unless $:.include?(lib)
4
3
 
@@ -20,11 +19,12 @@ Gem::Specification.new do |s|
20
19
  s.require_paths = ["lib"]
21
20
  s.license = "MIT"
22
21
 
23
- s.add_dependency "uber", "~> 0.0.7"
22
+ s.add_dependency "uber", "~> 0.0.15"
23
+ s.add_dependency "declarative", "~> 0.0.4"
24
24
 
25
25
  s.add_development_dependency "rake"
26
26
  s.add_development_dependency "test_xml", "0.1.6"
27
- s.add_development_dependency "minitest", ">= 5.4.1"
27
+ s.add_development_dependency "minitest"
28
28
  s.add_development_dependency "mongoid"
29
29
  s.add_development_dependency "virtus"
30
30
  s.add_development_dependency "json", '>= 1.7.7'
@@ -0,0 +1,37 @@
1
+ require "test_helper"
2
+
3
+ class DeserializePipelineTest < MiniTest::Spec
4
+ Album = Struct.new(:artist, :songs)
5
+ Artist = Struct.new(:email)
6
+ Song = Struct.new(:title)
7
+
8
+ # tests [Collect[Instance, Prepare, Deserialize], Setter]
9
+ class Representer < Representable::Decorator
10
+ include Representable::Hash
11
+
12
+ # property :artist, populator: Uber::Options::Value.new(ArtistPopulator.new), pass_options:true do
13
+ # property :email
14
+ # end
15
+ # DISCUSS: rename to populator_pipeline ?
16
+ collection :songs, parse_pipeline: ->(*) { [Collect[Instance, Prepare, Deserialize], Setter] }, instance: :instance!, exec_context: :decorator, pass_options: true do
17
+ property :title
18
+ end
19
+
20
+ def instance!(*options)
21
+ puts "@@@@@ #{options.inspect}"
22
+ Song.new
23
+ end
24
+
25
+ def songs=(array)
26
+ represented.songs=array
27
+ end
28
+ end
29
+
30
+ it do
31
+ skip "TODO: implement :parse_pipeline and :render_pipeline, and before/after/replace semantics"
32
+ album = Album.new
33
+ Representer.new(album).from_hash({"artist"=>{"email"=>"yo"}, "songs"=>[{"title"=>"Affliction"}, {"title"=>"Dream Beater"}]})
34
+ album.songs.must_equal([Song.new("Affliction"), Song.new("Dream Beater")])
35
+ puts album.inspect
36
+ end
37
+ end
@@ -5,7 +5,7 @@ class BindingTest < MiniTest::Spec
5
5
  let (:render_nil_definition) { Representable::Definition.new(:song, :render_nil => true) }
6
6
 
7
7
  describe "#skipable_empty_value?" do
8
- let (:binding) { Binding.new(render_nil_definition, nil) }
8
+ let (:binding) { Binding.new(render_nil_definition) }
9
9
 
10
10
  # don't skip when present.
11
11
  it { binding.skipable_empty_value?("Disconnect, Disconnect").must_equal false }
@@ -14,16 +14,16 @@ class BindingTest < MiniTest::Spec
14
14
  it { binding.skipable_empty_value?(nil).must_equal false }
15
15
 
16
16
  # skip when nil and :render_nil undefined.
17
- it { Binding.new(Representable::Definition.new(:song), nil).skipable_empty_value?(nil).must_equal true }
17
+ it { Binding.new(Representable::Definition.new(:song)).skipable_empty_value?(nil).must_equal true }
18
18
 
19
19
  # don't skip when nil and :render_nil undefined.
20
- it { Binding.new(Representable::Definition.new(:song), nil).skipable_empty_value?("Fatal Flu").must_equal false }
20
+ it { Binding.new(Representable::Definition.new(:song)).skipable_empty_value?("Fatal Flu").must_equal false }
21
21
  end
22
22
 
23
23
 
24
24
  describe "#default_for" do
25
25
  let (:definition) { Representable::Definition.new(:song, :default => "Insider") }
26
- let (:binding) { Binding.new(definition, nil) }
26
+ let (:binding) { Binding.new(definition) }
27
27
 
28
28
  # return value when value present.
29
29
  it { binding.default_for("Black And Blue").must_equal "Black And Blue" }
@@ -35,12 +35,12 @@ class BindingTest < MiniTest::Spec
35
35
  it { binding.default_for(nil).must_equal "Insider" }
36
36
 
37
37
  # return nil when value nil and render_nil: true.
38
- it { Binding.new(render_nil_definition, nil).default_for(nil).must_equal nil }
38
+ it { Binding.new(render_nil_definition).default_for(nil).must_equal nil }
39
39
 
40
40
  # return nil when value nil and render_nil: true, even when :default is set" do
41
- it { Binding.new(Representable::Definition.new(:song, :render_nil => true, :default => "The Quest"), nil).default_for(nil).must_equal nil }
41
+ it { Binding.new(Representable::Definition.new(:song, :render_nil => true, :default => "The Quest")).default_for(nil).must_equal nil }
42
42
 
43
43
  # return nil if no :default
44
- it { Binding.new(Representable::Definition.new(:song), nil).default_for(nil).must_equal nil }
44
+ it { Binding.new(Representable::Definition.new(:song)).default_for(nil).must_equal nil }
45
45
  end
46
46
  end
@@ -14,7 +14,7 @@ class CachedTest < MiniTest::Spec
14
14
  include Representable::Hash
15
15
  feature Representable::Cached
16
16
 
17
- property :title, render_filter: lambda { |value, doc, options| "#{value}:#{options.user_options}" }, pass_options: true
17
+ property :title, render_filter: lambda { |input, options| "#{input}:#{options[:user_options]}" }, pass_options: true
18
18
  property :composer, class: Model::Artist do
19
19
  property :name
20
20
  end
@@ -45,14 +45,16 @@ class CachedTest < MiniTest::Spec
45
45
  "songs"=>[{"title"=>"Jailbreak:{:volume=>9}"}, {"title"=>"Southbound:{:volume=>9}"}, {"title"=>"Emerald:{:volume=>9}"}]}) # called in Deserializer/Serializer
46
46
 
47
47
  # representer becomes reusable as it is stateless.
48
- representer.update!(album2)
48
+ # representer.update!(album2)
49
49
 
50
50
  # makes sure options are passed correctly.
51
- representer.to_hash(volume:10).must_equal(album_hash)
51
+ # representer.to_hash(volume:10).must_equal(album_hash)
52
52
  end
53
53
 
54
54
  # profiling
55
- it "xx" do
55
+ it do
56
+ representer.to_hash
57
+
56
58
  RubyProf.start
57
59
  representer.to_hash
58
60
  res = RubyProf.stop
@@ -65,14 +67,18 @@ class CachedTest < MiniTest::Spec
65
67
 
66
68
  printer.print(STDOUT)
67
69
 
68
- # only 1 nested decorators are instantiated, Song.
69
- data.must_match "1 <Class::Representable::Decorator>#prepare"
70
- # a total of 4 properties in the object graph.
71
- data.must_match "4 Representable::Binding#initialize"
70
+ # 3 songs get decorated.
71
+ data.must_match "3 Representable::Function::Decorate#call"
72
+ # 3 nested decorator is instantiated for 3 Songs, though.
73
+ data.must_match "3 <Class::Representable::Decorator>#prepare"
74
+ # no Binding is instantiated at runtime.
75
+ data.wont_match "Representable::Binding#initialize"
72
76
  # 2 mappers for Album, Song
73
- data.must_match "2 Representable::Mapper::Methods#initialize"
74
- # 6 deserializers as the songs collection uses 2.
75
- data.must_match "3 Representable::Deserializer#initialize"
77
+ # data.must_match "2 Representable::Mapper::Methods#initialize"
78
+ # title, songs, 3x title, composer
79
+ data.must_match "8 Representable::Binding#render_pipeline"
80
+ data.wont_match "render_functions"
81
+ data.wont_match "Representable::Binding::Factories#render_functions"
76
82
  end
77
83
  end
78
84
 
@@ -106,10 +112,12 @@ class CachedTest < MiniTest::Spec
106
112
  # TODO: test options.
107
113
  end
108
114
 
109
- it do
115
+ it "xxx" do
110
116
  representer = AlbumRepresenter.new(Model::Album.new)
117
+ representer.from_hash(album_hash)
111
118
 
112
119
  RubyProf.start
120
+ # puts "#{representer.class.representable_attrs.get(:songs).representer_module.representable_attrs.inspect}"
113
121
  representer.from_hash(album_hash)
114
122
  res = RubyProf.stop
115
123
 
@@ -120,15 +128,19 @@ class CachedTest < MiniTest::Spec
120
128
  data = data.string
121
129
 
122
130
  # only 2 nested decorators are instantiated, Song, and Artist.
123
- data.must_match "2 <Class::Representable::Decorator>#prepare"
131
+ data.must_match "5 <Class::Representable::Decorator>#prepare"
124
132
  # a total of 5 properties in the object graph.
125
- data.must_match "5 Representable::Binding#initialize"
133
+ data.wont_match "Representable::Binding#initialize"
134
+
135
+
136
+ data.wont_match "parse_functions" # no pipeline creation.
137
+ data.must_match "10 Representable::Binding#parse_pipeline"
126
138
  # three mappers for Album, Song, composer
127
- data.must_match "3 Representable::Mapper::Methods#initialize"
128
- # 6 deserializers as the songs collection uses 2.
129
- data.must_match "6 Representable::Deserializer#initialize"
130
- # one populater for every property.
131
- data.must_match "5 Representable::Populator#initialize"
139
+ # data.must_match "3 Representable::Mapper::Methods#initialize"
140
+ # # 6 deserializers as the songs collection uses 2.
141
+ # data.must_match "6 Representable::Deserializer#initialize"
142
+ # # one populater for every property.
143
+ # data.must_match "5 Representable::Populator#initialize"
132
144
  # printer.print(STDOUT)
133
145
  end
134
146
  end
@@ -40,8 +40,8 @@ class VirtusCoercionTest < MiniTest::Spec
40
40
  include Representable::Coercion
41
41
 
42
42
  property :length, :type => Float,
43
- :parse_filter => lambda { |fragment, doc, options| "#{fragment}.1" }, # happens BEFORE coercer.
44
- :render_filter => lambda { |fragment, doc, options| "#{fragment}.1" }
43
+ :parse_filter => lambda { |input, options| "#{input}.1" }, # happens BEFORE coercer.
44
+ :render_filter => lambda { |fragment,*| "#{fragment}.1" }
45
45
  end
46
46
 
47
47
  # user's :parse_filter(s) are run before coercion.