representable 2.4.0.rc3 → 2.4.0.rc4

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