representable 2.4.0.rc3 → 2.4.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +7 -2
  3. data/Rakefile +6 -0
  4. data/lib/representable.rb +21 -17
  5. data/lib/representable/binding.rb +3 -2
  6. data/lib/representable/definition.rb +1 -1
  7. data/lib/representable/deprecations.rb +31 -5
  8. data/lib/representable/deserializer.rb +24 -34
  9. data/lib/representable/hash/collection.rb +9 -2
  10. data/lib/representable/hash_methods.rb +2 -2
  11. data/lib/representable/parse_strategies.rb +6 -7
  12. data/lib/representable/pipeline.rb +12 -1
  13. data/lib/representable/pipeline_factories.rb +9 -2
  14. data/lib/representable/serializer.rb +3 -3
  15. data/lib/representable/version.rb +1 -1
  16. data/test-with-deprecations/as_test.rb +65 -0
  17. data/test-with-deprecations/benchmarking.rb +83 -0
  18. data/test-with-deprecations/binding_test.rb +46 -0
  19. data/test-with-deprecations/blaaaaaaaa_test.rb +69 -0
  20. data/test-with-deprecations/cached_test.rb +147 -0
  21. data/test-with-deprecations/class_test.rb +119 -0
  22. data/test-with-deprecations/coercion_test.rb +52 -0
  23. data/test-with-deprecations/config/inherit_test.rb +135 -0
  24. data/test-with-deprecations/config_test.rb +122 -0
  25. data/test-with-deprecations/decorator_scope_test.rb +28 -0
  26. data/test-with-deprecations/decorator_test.rb +96 -0
  27. data/test-with-deprecations/default_test.rb +34 -0
  28. data/test-with-deprecations/defaults_options_test.rb +93 -0
  29. data/test-with-deprecations/definition_test.rb +264 -0
  30. data/test-with-deprecations/example.rb +310 -0
  31. data/test-with-deprecations/examples/object.rb +31 -0
  32. data/test-with-deprecations/exec_context_test.rb +93 -0
  33. data/test-with-deprecations/features_test.rb +70 -0
  34. data/test-with-deprecations/filter_test.rb +57 -0
  35. data/test-with-deprecations/for_collection_test.rb +74 -0
  36. data/test-with-deprecations/generic_test.rb +116 -0
  37. data/test-with-deprecations/getter_setter_test.rb +21 -0
  38. data/test-with-deprecations/hash_bindings_test.rb +87 -0
  39. data/test-with-deprecations/hash_test.rb +160 -0
  40. data/test-with-deprecations/heritage_test.rb +62 -0
  41. data/test-with-deprecations/if_test.rb +79 -0
  42. data/test-with-deprecations/include_exclude_test.rb +88 -0
  43. data/test-with-deprecations/inherit_test.rb +159 -0
  44. data/test-with-deprecations/inline_test.rb +272 -0
  45. data/test-with-deprecations/instance_test.rb +266 -0
  46. data/test-with-deprecations/is_representable_test.rb +77 -0
  47. data/test-with-deprecations/json_test.rb +355 -0
  48. data/test-with-deprecations/lonely_test.rb +239 -0
  49. data/test-with-deprecations/mongoid_test.rb +31 -0
  50. data/test-with-deprecations/nested_test.rb +115 -0
  51. data/test-with-deprecations/object_test.rb +60 -0
  52. data/{test/---deserialize-pipeline_test.rb → test-with-deprecations/parse_pipeline_test.rb} +29 -2
  53. data/test-with-deprecations/parse_strategy_test.rb +279 -0
  54. data/{test → test-with-deprecations}/pass_options_test.rb +0 -0
  55. data/test-with-deprecations/pipeline_test.rb +277 -0
  56. data/test-with-deprecations/populator_test.rb +105 -0
  57. data/test-with-deprecations/prepare_test.rb +67 -0
  58. data/test-with-deprecations/private_options_test.rb +18 -0
  59. data/test-with-deprecations/reader_writer_test.rb +19 -0
  60. data/test-with-deprecations/realistic_benchmark.rb +115 -0
  61. data/test-with-deprecations/render_nil_test.rb +21 -0
  62. data/test-with-deprecations/represent_test.rb +88 -0
  63. data/test-with-deprecations/representable_test.rb +511 -0
  64. data/test-with-deprecations/schema_test.rb +148 -0
  65. data/test-with-deprecations/serialize_deserialize_test.rb +33 -0
  66. data/test-with-deprecations/skip_test.rb +81 -0
  67. data/test-with-deprecations/stringify_hash_test.rb +41 -0
  68. data/test-with-deprecations/test_helper.rb +135 -0
  69. data/test-with-deprecations/test_helper_test.rb +25 -0
  70. data/test-with-deprecations/uncategorized_test.rb +67 -0
  71. data/test-with-deprecations/user_options_test.rb +15 -0
  72. data/test-with-deprecations/wrap_test.rb +152 -0
  73. data/test-with-deprecations/xml_bindings_test.rb +62 -0
  74. data/test-with-deprecations/xml_test.rb +503 -0
  75. data/test-with-deprecations/yaml_test.rb +162 -0
  76. data/test/as_test.rb +3 -3
  77. data/test/cached_test.rb +2 -2
  78. data/test/class_test.rb +5 -5
  79. data/test/exec_context_test.rb +2 -2
  80. data/test/filter_test.rb +1 -1
  81. data/test/getter_setter_test.rb +4 -4
  82. data/test/if_test.rb +2 -2
  83. data/test/include_exclude_test.rb +88 -0
  84. data/test/instance_test.rb +15 -15
  85. data/test/lonely_test.rb +18 -2
  86. data/test/object_test.rb +4 -4
  87. data/test/parse_pipeline_test.rb +64 -0
  88. data/test/parse_strategy_test.rb +3 -3
  89. data/test/pipeline_test.rb +8 -12
  90. data/test/prepare_test.rb +2 -3
  91. data/test/reader_writer_test.rb +3 -3
  92. data/test/representable_test.rb +12 -48
  93. data/test/serialize_deserialize_test.rb +9 -9
  94. data/test/skip_test.rb +11 -11
  95. data/test/test_helper.rb +2 -0
  96. data/test/uncategorized_test.rb +10 -10
  97. data/test/user_options_test.rb +15 -0
  98. data/test/wrap_test.rb +1 -1
  99. metadata +65 -4
@@ -0,0 +1,83 @@
1
+ require 'test_helper'
2
+ require 'benchmark'
3
+
4
+ SONG_PROPERTIES = 1000.times.collect do |i|
5
+ "property_#{i}"
6
+ end
7
+
8
+
9
+ module SongRepresenter
10
+ include Representable::JSON
11
+
12
+ SONG_PROPERTIES.each { |p| property p }
13
+ end
14
+
15
+ class SongDecorator < Representable::Decorator
16
+ include Representable::JSON
17
+
18
+ SONG_PROPERTIES.each { |p| property p }
19
+ end
20
+
21
+ module AlbumRepresenter
22
+ include Representable::JSON
23
+
24
+ # collection :songs, extend: SongRepresenter
25
+ collection :songs, extend: SongDecorator
26
+ end
27
+
28
+ def random_song
29
+ attrs = Hash[SONG_PROPERTIES.collect { |n| [n,n] }]
30
+ OpenStruct.new(attrs)
31
+ end
32
+
33
+ times = []
34
+
35
+ 3.times.each do
36
+ album = OpenStruct.new(songs: 100.times.collect { random_song })
37
+
38
+ times << Benchmark.measure do
39
+ puts "================ next!"
40
+ album.extend(AlbumRepresenter).to_json
41
+ end
42
+ end
43
+
44
+ puts times.join("")
45
+
46
+ # 100 songs, 100 attrs
47
+ # 0.050000 0.000000 0.050000 ( 0.093157)
48
+
49
+ ## 100 songs, 1000 attrs
50
+ # 0.470000 0.010000 0.480000 ( 0.483708)
51
+
52
+
53
+ ### without binding cache:
54
+ # 2.790000 0.030000 2.820000 ( 2.820190)
55
+
56
+
57
+
58
+ ### with extend: on Song, with binding cache>
59
+ # 2.490000 0.030000 2.520000 ( 2.517433) 2.4-3.0
60
+ ### without skip?
61
+ # 2.030000 0.020000 2.050000 ( 2.050796) 2.1-2.3
62
+
63
+ ### without :writer
64
+ # 2.270000 0.010000 2.280000 ( 2.284530 1.9-2.2
65
+ ### without :render_filter
66
+ # 2.020000 0.000000 2.020000 ( 2.030234) 1.5-2.0
67
+ ###without default_for and skipable?
68
+ # 1.730000 0.010000 1.740000 ( 1.735597 1.4-1.7
69
+ ### without :serialize
70
+ # 1.780000 0.010000 1.790000 ( 1.786791) 1.4-1.7
71
+ ### using decorator
72
+ # 1.400000 0.030000 1.430000 ( 1.434206) 1.4-1.6
73
+ ### with prepare AFTER representable?
74
+ # 1.330000 0.010000 1.340000 ( 1.335900) 1.1-1.3
75
+
76
+
77
+ # representable 2.0
78
+ # 3.000000 0.020000 3.020000 ( 3.013031) 2.7-3.0
79
+
80
+ # no method missing
81
+ # 2.280000 0.030000 2.310000 ( 2.313522) 2.2-2.5
82
+ # no def_delegator in Definition
83
+ # 2.130000 0.010000 2.140000 ( 2.136115) 1.7-2.1
@@ -0,0 +1,46 @@
1
+ require 'test_helper'
2
+
3
+ class BindingTest < MiniTest::Spec
4
+ Binding = Representable::Binding
5
+ let (:render_nil_definition) { Representable::Definition.new(:song, :render_nil => true) }
6
+
7
+ describe "#skipable_empty_value?" do
8
+ let (:binding) { Binding.new(render_nil_definition) }
9
+
10
+ # don't skip when present.
11
+ it { binding.skipable_empty_value?("Disconnect, Disconnect").must_equal false }
12
+
13
+ # don't skip when it's nil and render_nil: true
14
+ it { binding.skipable_empty_value?(nil).must_equal false }
15
+
16
+ # skip when nil and :render_nil undefined.
17
+ it { Binding.new(Representable::Definition.new(:song)).skipable_empty_value?(nil).must_equal true }
18
+
19
+ # don't skip when nil and :render_nil undefined.
20
+ it { Binding.new(Representable::Definition.new(:song)).skipable_empty_value?("Fatal Flu").must_equal false }
21
+ end
22
+
23
+
24
+ describe "#default_for" do
25
+ let (:definition) { Representable::Definition.new(:song, :default => "Insider") }
26
+ let (:binding) { Binding.new(definition) }
27
+
28
+ # return value when value present.
29
+ it { binding.default_for("Black And Blue").must_equal "Black And Blue" }
30
+
31
+ # return false when value false.
32
+ it { binding.default_for(false).must_equal false }
33
+
34
+ # return default when value nil.
35
+ it { binding.default_for(nil).must_equal "Insider" }
36
+
37
+ # return nil when value nil and render_nil: true.
38
+ it { Binding.new(render_nil_definition).default_for(nil).must_equal nil }
39
+
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")).default_for(nil).must_equal nil }
42
+
43
+ # return nil if no :default
44
+ it { Binding.new(Representable::Definition.new(:song)).default_for(nil).must_equal nil }
45
+ end
46
+ end
@@ -0,0 +1,69 @@
1
+ require 'test_helper'
2
+
3
+
4
+
5
+
6
+ # Include Inherit Module And Decorator Test
7
+ class SchemaTest < MiniTest::Spec
8
+ module Genre
9
+ include Representable
10
+ property :genre
11
+ end
12
+
13
+ module LinkFeature
14
+ def self.included(base)
15
+ base.extend(Link)
16
+ end
17
+
18
+ module Link
19
+ def link
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ module Module
26
+ include Representable::Hash
27
+ feature LinkFeature
28
+
29
+ property :title
30
+ property :label do # extend: LabelModule
31
+ property :name
32
+ link # feature
33
+
34
+ property :location do
35
+ property :city
36
+ link # feature.
37
+ end
38
+ end
39
+
40
+ property :album, :extend => lambda { raise "don't manifest me!" } # this is not an inline decorator, don't manifest it.
41
+
42
+
43
+ include Genre # Schema::Included::included is called!
44
+ end
45
+
46
+
47
+ class InheritDecorator < Representable::Decorator
48
+ include Representable::Hash
49
+
50
+ include Module
51
+
52
+ property :label, inherit: true do # decorator.rb:27:in `initialize': superclass must be a Class (Module given)
53
+ property :city
54
+
55
+ property :location, :inherit => true do
56
+ property :city
57
+ end
58
+ end
59
+ end
60
+
61
+
62
+ class InheritFromDecorator < InheritDecorator
63
+ property :label, inherit: true do
64
+ collection :employees do
65
+ property :name
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,147 @@
1
+ require "test_helper"
2
+ require 'ruby-prof'
3
+
4
+ class CachedTest < MiniTest::Spec
5
+ # TODO: also test with feature(Cached)
6
+
7
+ module Model
8
+ Song = Struct.new(:title, :composer)
9
+ Album = Struct.new(:name, :songs, :artist)
10
+ Artist = Struct.new(:name, :hidden_taste)
11
+ end
12
+
13
+ class SongRepresenter < Representable::Decorator
14
+ include Representable::Hash
15
+ feature Representable::Cached
16
+
17
+ property :title, render_filter: lambda { |input, options| "#{input}:#{options[:options][:user_options]}" }
18
+ property :composer, class: Model::Artist do
19
+ property :name
20
+ end
21
+ end
22
+
23
+ class AlbumRepresenter < Representable::Decorator
24
+ include Representable::Hash
25
+ include Representable::Cached
26
+
27
+ property :name
28
+ collection :songs, decorator: SongRepresenter, class: Model::Song
29
+ end
30
+
31
+
32
+ describe "serialization" do
33
+ let (:album_hash) { {"name"=>"Louder And Even More Dangerous", "songs"=>[{"title"=>"Southbound:{:volume=>10}"}, {"title"=>"Jailbreak:{:volume=>10}"}]} }
34
+
35
+ let (:song) { Model::Song.new("Jailbreak") }
36
+ let (:song2) { Model::Song.new("Southbound") }
37
+ let (:album) { Model::Album.new("Live And Dangerous", [song, song2, Model::Song.new("Emerald")]) }
38
+ let (:representer) { AlbumRepresenter.new(album) }
39
+
40
+ it do
41
+ album2 = Model::Album.new("Louder And Even More Dangerous", [song2, song])
42
+
43
+ # makes sure options are passed correctly.
44
+ representer.to_hash(volume: 9).must_equal({"name"=>"Live And Dangerous",
45
+ "songs"=>[{"title"=>"Jailbreak:{:volume=>9}"}, {"title"=>"Southbound:{:volume=>9}"}, {"title"=>"Emerald:{:volume=>9}"}]}) # called in Deserializer/Serializer
46
+
47
+ # representer becomes reusable as it is stateless.
48
+ # representer.update!(album2)
49
+
50
+ # makes sure options are passed correctly.
51
+ # representer.to_hash(volume:10).must_equal(album_hash)
52
+ end
53
+
54
+ # profiling
55
+ it do
56
+ representer.to_hash
57
+
58
+ RubyProf.start
59
+ representer.to_hash
60
+ res = RubyProf.stop
61
+
62
+ printer = RubyProf::FlatPrinter.new(res)
63
+
64
+ data = StringIO.new
65
+ printer.print(data)
66
+ data = data.string
67
+
68
+ printer.print(STDOUT)
69
+
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"
76
+ # 2 mappers for Album, Song
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"
82
+ end
83
+ end
84
+
85
+
86
+ describe "deserialization" do
87
+ let (:album_hash) {
88
+ {
89
+ "name"=>"Louder And Even More Dangerous",
90
+ "songs"=>[
91
+ {"title"=>"Southbound", "composer"=>{"name"=>"Lynott"}},
92
+ {"title"=>"Jailbreak", "composer"=>{"name"=>"Phil Lynott"}},
93
+ {"title"=>"Emerald"}
94
+ ]
95
+ }
96
+ }
97
+
98
+ it do
99
+ album = Model::Album.new
100
+
101
+ AlbumRepresenter.new(album).from_hash(album_hash)
102
+
103
+ album.songs.size.must_equal 3
104
+ album.name.must_equal "Louder And Even More Dangerous"
105
+ album.songs[0].title.must_equal "Southbound"
106
+ album.songs[0].composer.name.must_equal "Lynott"
107
+ album.songs[1].title.must_equal "Jailbreak"
108
+ album.songs[1].composer.name.must_equal "Phil Lynott"
109
+ album.songs[2].title.must_equal "Emerald"
110
+ album.songs[2].composer.must_equal nil
111
+
112
+ # TODO: test options.
113
+ end
114
+
115
+ it "xxx" do
116
+ representer = AlbumRepresenter.new(Model::Album.new)
117
+ representer.from_hash(album_hash)
118
+
119
+ RubyProf.start
120
+ # puts "#{representer.class.representable_attrs.get(:songs).representer_module.representable_attrs.inspect}"
121
+ representer.from_hash(album_hash)
122
+ res = RubyProf.stop
123
+
124
+ printer = RubyProf::FlatPrinter.new(res)
125
+
126
+ data = StringIO.new
127
+ printer.print(data)
128
+ data = data.string
129
+
130
+ # only 2 nested decorators are instantiated, Song, and Artist.
131
+ data.must_match "5 <Class::Representable::Decorator>#prepare"
132
+ # a total of 5 properties in the object graph.
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"
138
+ # three mappers for Album, Song, composer
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"
144
+ # printer.print(STDOUT)
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,119 @@
1
+ require 'test_helper'
2
+
3
+ class ClassTest < BaseTest
4
+
5
+ class RepresentingSong
6
+ attr_reader :name
7
+
8
+ def from_hash(doc, *args)
9
+ @name = doc["__name__"]
10
+
11
+ self # DISCUSS: do we wanna be able to return whatever we want here? this is a trick to replace the actual object
12
+ end
13
+ end
14
+
15
+
16
+ describe "class: ClassName, only" do
17
+ representer! do
18
+ property :song, :class => RepresentingSong # supposed this class exposes #from_hash itself.
19
+ end
20
+
21
+ it "creates fresh instance and doesn't extend" do
22
+ song = representer.prepare(OpenStruct.new).from_hash({"song" => {"__name__" => "Captured"}}).song
23
+ song.must_be_instance_of RepresentingSong
24
+ song.name.must_equal "Captured"
25
+ end
26
+ end
27
+
28
+
29
+ describe "class: lambda, only" do
30
+ representer! do
31
+ property :song, :class => lambda { |*| RepresentingSong }
32
+ end
33
+
34
+ it "creates fresh instance and doesn't extend" do
35
+ song = representer.prepare(OpenStruct.new).from_hash({"song" => {"__name__" => "Captured"}}).song
36
+ song.must_be_instance_of RepresentingSong
37
+ song.name.must_equal "Captured"
38
+ end
39
+ end
40
+
41
+
42
+ # this throws a DeserializationError now.
43
+ describe "lambda { nil }" do
44
+ representer! do
45
+ property :title, :class => nil
46
+ end
47
+
48
+ it do
49
+ assert_raises Representable::DeserializeError do
50
+ OpenStruct.new.extend(representer).from_hash({"title" => {}})
51
+ end
52
+ end
53
+ end
54
+
55
+
56
+ describe "lambda receiving fragment and args" do
57
+ let (:klass) { Class.new do
58
+ class << self
59
+ attr_accessor :args
60
+ end
61
+
62
+ def from_hash(*)
63
+ self.class.new
64
+ end
65
+ end }
66
+
67
+ representer!(:inject => :klass) do
68
+ _klass = klass
69
+ property :song, :class => lambda { |fragment, args| _klass.args=([fragment,args]); _klass }
70
+ end
71
+
72
+ it { representer.prepare(OpenStruct.new).from_hash({"song" => {"name" => "Captured"}}, :volume => true).song.class.args.
73
+ must_equal([{"name"=>"Captured"}, {:volume=>true}]) }
74
+ end
75
+
76
+
77
+ describe "collection: lambda receiving fragment and args" do
78
+ let (:klass) { Class.new do
79
+ class << self
80
+ attr_accessor :args
81
+ end
82
+
83
+ def from_hash(*)
84
+ self.class.new
85
+ end
86
+ end }
87
+
88
+ representer!(:inject => :klass) do
89
+ _klass = klass
90
+ collection :songs, :class => lambda { |fragment, i, args| _klass.args=([fragment,i,args]); _klass }
91
+ end
92
+
93
+ it { representer.prepare(OpenStruct.new).from_hash({"songs" => [{"name" => "Captured"}]}, :volume => true).songs.first.class.args.
94
+ must_equal([{"name"=>"Captured"}, 0, {:volume=>true}]) }
95
+ end
96
+
97
+
98
+ describe "class: implementing #from_hash" do
99
+ let(:parser) do
100
+ Class.new do
101
+ def from_hash(*)
102
+ [1,2,3,4]
103
+ end
104
+ end
105
+ end
106
+
107
+ representer!(:inject => :parser) do
108
+ property :song, :class => parser # supposed this class exposes #from_hash itself.
109
+ end
110
+
111
+ it "allows returning arbitrary objects in #from_hash" do
112
+ representer.prepare(OpenStruct.new).from_hash({"song" => 1}).song.must_equal [1,2,3,4]
113
+ end
114
+ end
115
+ end
116
+
117
+ #TODO: test fragment,
118
+
119
+ # `class: Song` only, no :extend.