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