disposable 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -5
  3. data/CHANGES.md +4 -0
  4. data/Gemfile +1 -1
  5. data/README.md +154 -1
  6. data/database.sqlite3 +0 -0
  7. data/disposable.gemspec +7 -7
  8. data/gemfiles/Gemfile.rails-3.0.lock +10 -8
  9. data/gemfiles/Gemfile.rails-3.2.lock +9 -7
  10. data/gemfiles/Gemfile.rails-4.0.lock +9 -7
  11. data/gemfiles/Gemfile.rails-4.1.lock +10 -8
  12. data/lib/disposable.rb +6 -7
  13. data/lib/disposable/callback.rb +174 -0
  14. data/lib/disposable/composition.rb +21 -58
  15. data/lib/disposable/expose.rb +49 -0
  16. data/lib/disposable/twin.rb +85 -38
  17. data/lib/disposable/twin/builder.rb +12 -30
  18. data/lib/disposable/twin/changed.rb +50 -0
  19. data/lib/disposable/twin/collection.rb +95 -0
  20. data/lib/disposable/twin/composition.rb +43 -15
  21. data/lib/disposable/twin/option.rb +1 -1
  22. data/lib/disposable/twin/persisted.rb +20 -0
  23. data/lib/disposable/twin/property_processor.rb +29 -0
  24. data/lib/disposable/twin/representer.rb +42 -14
  25. data/lib/disposable/twin/save.rb +19 -34
  26. data/lib/disposable/twin/schema.rb +31 -0
  27. data/lib/disposable/twin/setup.rb +38 -0
  28. data/lib/disposable/twin/sync.rb +114 -0
  29. data/lib/disposable/version.rb +1 -1
  30. data/test/api_semantics_test.rb +263 -0
  31. data/test/callback_group_test.rb +222 -0
  32. data/test/callbacks_test.rb +450 -0
  33. data/test/example.rb +40 -0
  34. data/test/expose_test.rb +92 -0
  35. data/test/persisted_test.rb +101 -0
  36. data/test/test_helper.rb +64 -0
  37. data/test/twin/benchmarking.rb +33 -0
  38. data/test/twin/builder_test.rb +32 -0
  39. data/test/twin/changed_test.rb +108 -0
  40. data/test/twin/collection_test.rb +223 -0
  41. data/test/twin/composition_test.rb +56 -25
  42. data/test/twin/expose_test.rb +73 -0
  43. data/test/twin/feature_test.rb +61 -0
  44. data/test/twin/from_test.rb +37 -0
  45. data/test/twin/inherit_test.rb +57 -0
  46. data/test/twin/option_test.rb +27 -0
  47. data/test/twin/readable_test.rb +57 -0
  48. data/test/twin/save_test.rb +192 -0
  49. data/test/twin/schema_test.rb +69 -0
  50. data/test/twin/setup_test.rb +139 -0
  51. data/test/twin/skip_unchanged_test.rb +64 -0
  52. data/test/twin/struct_test.rb +168 -0
  53. data/test/twin/sync_option_test.rb +228 -0
  54. data/test/twin/sync_test.rb +128 -0
  55. data/test/twin/twin_test.rb +49 -128
  56. data/test/twin/writeable_test.rb +56 -0
  57. metadata +106 -20
  58. data/STUFF +0 -4
  59. data/lib/disposable/twin/finders.rb +0 -29
  60. data/lib/disposable/twin/new.rb +0 -30
  61. data/lib/disposable/twin/save_.rb +0 -21
  62. data/test/composition_test.rb +0 -102
@@ -0,0 +1,37 @@
1
+ require 'test_helper'
2
+
3
+ class FromTest < MiniTest::Spec
4
+ module Model
5
+ Album = Struct.new(:name, :composer)
6
+ Artist = Struct.new(:realname)
7
+ end
8
+
9
+
10
+ module Twin
11
+ class Album < Disposable::Twin
12
+ feature Sync
13
+ feature Save
14
+ feature Disposable::Twin::Expose
15
+
16
+ property :full_name, from: :name
17
+
18
+ property :artist, from: :composer do
19
+ property :name, from: :realname
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ let (:composer) { Model::Artist.new("AFI").extend(Disposable::Saveable) }
26
+ let (:album) { Model::Album.new("Black Sails In The Sunset", composer).extend(Disposable::Saveable) }
27
+ let (:twin) { Twin::Album.new(album) }
28
+
29
+ it do
30
+ twin.full_name.must_equal "Black Sails In The Sunset"
31
+ twin.artist.name.must_equal "AFI"
32
+
33
+ twin.save
34
+
35
+
36
+ end
37
+ end
@@ -0,0 +1,57 @@
1
+ require "test_helper"
2
+
3
+ class InheritTest < MiniTest::Spec
4
+ module Model
5
+ Song = Struct.new(:title, :album)
6
+ Album = Struct.new(:name, :songs, :artist)
7
+ Artist = Struct.new(:name)
8
+ end
9
+
10
+ module Twin
11
+ class Album < Disposable::Twin
12
+ feature Setup
13
+
14
+ property :name, fromage: :_name
15
+
16
+ collection :songs do
17
+ property :name
18
+ end
19
+
20
+ property :artist do
21
+ property :name
22
+
23
+ def artist_id
24
+ 1
25
+ end
26
+ end
27
+ end
28
+
29
+ class EmptyCompilation < Album
30
+ end
31
+
32
+ class Compilation < Album
33
+ property :name, writeable: false, inherit: true
34
+
35
+ property :artist, inherit: true do
36
+
37
+ end
38
+ end
39
+ end
40
+
41
+ # definitions are not shared.
42
+ it do
43
+ Twin::Album.representer_class.representable_attrs.get(:name).inspect.must_equal "#<Representable::Definition ==>name @options={:fromage=>:_name, :private_name=>:name, :pass_options=>true, :parse_filter=>[], :render_filter=>[], :as=>\"name\"}>"
44
+ Twin::Compilation.representer_class.representable_attrs.get(:name).inspect.must_equal "#<Representable::Definition ==>name @options={:fromage=>:_name, :private_name=>:name, :pass_options=>true, :parse_filter=>[], :render_filter=>[], :as=>\"name\", :writeable=>false, :inherit=>true}>"
45
+ end
46
+
47
+
48
+ let (:album) { Model::Album.new("In The Meantime And Inbetween Time", [], Model::Artist.new) }
49
+
50
+ it { Twin::Album.new(album).artist.artist_id.must_equal 1 }
51
+
52
+ # inherit inline twins when not overriding.
53
+ it { Twin::EmptyCompilation.new(album).artist.artist_id.must_equal 1 }
54
+
55
+ # inherit inline twins when overriding.
56
+ it { Twin::Compilation.new(album).artist.artist_id.must_equal 1 }
57
+ end
@@ -0,0 +1,27 @@
1
+ # require "test_helper"
2
+
3
+ # class TwinOptionTest < TwinTest
4
+ # class Song < Disposable::Twin
5
+ # property :id # DISCUSS: needed for #save.
6
+ # property :title
7
+
8
+ # option :preview?
9
+ # option :highlight?
10
+ # end
11
+
12
+ # let (:song) { Model::Song.new(1, "Broken") }
13
+ # let (:twin) { Song.new(song, :preview? => false) }
14
+
15
+
16
+ # # properties are read from model.
17
+ # it { twin.id.must_equal 1 }
18
+ # it { twin.title.must_equal "Broken" }
19
+
20
+ # # option is not delegated to model.
21
+ # it { twin.preview?.must_equal false }
22
+ # # not passing option means zero.
23
+ # it { twin.highlight?.must_equal nil }
24
+
25
+ # # passing both options.
26
+ # it { Song.new(song, preview?: true, highlight?: false).preview?.must_equal true }
27
+ # end
@@ -0,0 +1,57 @@
1
+ require 'test_helper'
2
+
3
+ class ReadableTest < MiniTest::Spec
4
+ Credentials = Struct.new(:password, :credit_card) do
5
+ def password
6
+ raise "don't call me!"
7
+ end
8
+ end
9
+
10
+ CreditCard = Struct.new(:name, :number) do
11
+ def number
12
+ raise "don't call me!"
13
+ end
14
+ end
15
+
16
+ class PasswordForm < Disposable::Twin
17
+ feature Setup
18
+ feature Sync
19
+
20
+ property :password, readable: false
21
+
22
+ property :credit_card do
23
+ property :name
24
+ property :number, readable: false
25
+ end
26
+ end
27
+
28
+ let (:cred) { Credentials.new("secret", CreditCard.new("Jonny", "0987654321")) }
29
+
30
+ let (:twin) { PasswordForm.new(cred) }
31
+
32
+ it {
33
+ twin.password.must_equal nil # not readable.
34
+ twin.credit_card.name.must_equal "Jonny"
35
+ twin.credit_card.number.must_equal nil # not readable.
36
+
37
+ # manual setting on the twin works.
38
+ twin.password = "123"
39
+ twin.password.must_equal "123"
40
+
41
+ twin.credit_card.number = "456"
42
+ twin.credit_card.number.must_equal "456"
43
+
44
+ twin.sync
45
+
46
+ # it writes, but does not read.
47
+ cred.inspect.must_equal '#<struct ReadableTest::Credentials password="123", credit_card=#<struct ReadableTest::CreditCard name="Jonny", number="456">>'
48
+
49
+ # test sync{}.
50
+ hash = {}
51
+ twin.sync do |nested|
52
+ hash = nested
53
+ end
54
+
55
+ hash.must_equal("password"=> "123", "credit_card"=>{"name"=>"Jonny", "number"=>"456"})
56
+ }
57
+ end
@@ -0,0 +1,192 @@
1
+ require 'test_helper'
2
+
3
+ class SaveTest < MiniTest::Spec
4
+ module Model
5
+ Song = Struct.new(:title, :composer)
6
+ Album = Struct.new(:name, :songs, :artist)
7
+ Artist = Struct.new(:name)
8
+ end
9
+
10
+
11
+ module Twin
12
+ class Album < Disposable::Twin
13
+ feature Setup
14
+ feature Sync
15
+ feature Save
16
+
17
+ property :name
18
+
19
+ collection :songs do
20
+ property :title
21
+
22
+ property :composer do
23
+ property :name
24
+ end
25
+ end
26
+
27
+ property :artist do
28
+ property :name
29
+ end
30
+ end
31
+ end
32
+
33
+
34
+ let (:song) { Model::Song.new().extend(Disposable::Saveable) }
35
+ let (:composer) { Model::Artist.new(nil).extend(Disposable::Saveable) }
36
+ let (:song_with_composer) { Model::Song.new(nil, composer).extend(Disposable::Saveable) }
37
+ let (:artist) { Model::Artist.new(nil).extend(Disposable::Saveable) }
38
+
39
+
40
+ let (:album) { Model::Album.new(nil, [song, song_with_composer], artist).extend(Disposable::Saveable) }
41
+
42
+ let (:twin) { Twin::Album.new(album) }
43
+
44
+ # with populated model.
45
+ it do
46
+ fill_out!(twin)
47
+
48
+ twin.save
49
+
50
+ # sync happened.
51
+ album.name.must_equal "Live And Dangerous"
52
+ album.songs[0].must_be_instance_of Model::Song
53
+ album.songs[1].must_be_instance_of Model::Song
54
+ album.songs[0].title.must_equal "Southbound"
55
+ album.songs[1].title.must_equal "The Boys Are Back In Town"
56
+ album.songs[1].composer.must_be_instance_of Model::Artist
57
+ album.songs[1].composer.name.must_equal "Lynott"
58
+ album.artist.must_be_instance_of Model::Artist
59
+ album.artist.name.must_equal "Thin Lizzy"
60
+
61
+ # saved?
62
+ album.saved?.must_equal true
63
+ album.songs[0].saved?.must_equal true
64
+ album.songs[1].saved?.must_equal true
65
+ album.songs[1].composer.saved?.must_equal true
66
+ album.artist.saved?.must_equal true
67
+ end
68
+
69
+ #save returns result.
70
+ it { twin.save.must_equal true }
71
+ it do
72
+ album.instance_eval { def save; false; end }
73
+ twin.save.must_equal false
74
+ end
75
+
76
+ # with save{}.
77
+ it do
78
+ twin = Twin::Album.new(album)
79
+
80
+ # this usually happens in Contract::Validate or in from_* in a representer
81
+ fill_out!(twin)
82
+
83
+ nested_hash = nil
84
+ twin.save do |hash|
85
+ nested_hash = hash
86
+ end
87
+
88
+ nested_hash.must_equal({"name"=>"Live And Dangerous", "songs"=>[{"title"=>"Southbound"}, {"title"=>"The Boys Are Back In Town", "composer"=>{"name"=>"Lynott"}}], "artist"=>{"name"=>"Thin Lizzy"}})
89
+
90
+ # nothing written to model.
91
+ album.name.must_equal nil
92
+ album.songs[0].title.must_equal nil
93
+ album.songs[1].title.must_equal nil
94
+ album.songs[1].composer.name.must_equal nil
95
+ album.artist.name.must_equal nil
96
+
97
+ # nothing saved.
98
+ # saved?
99
+ album.saved?.must_equal nil
100
+ album.songs[0].saved?.must_equal nil
101
+ album.songs[1].saved?.must_equal nil
102
+ album.songs[1].composer.saved?.must_equal nil
103
+ album.artist.saved?.must_equal nil
104
+ end
105
+
106
+
107
+ # save: false
108
+ module Twin
109
+ class AlbumWithSaveFalse < Disposable::Twin
110
+ feature Setup
111
+ feature Sync
112
+ feature Save
113
+
114
+ property :name
115
+
116
+ collection :songs, save: false do
117
+ property :title
118
+
119
+ property :composer do
120
+ property :name
121
+ end
122
+ end
123
+
124
+ property :artist do
125
+ property :name
126
+ end
127
+ end
128
+ end
129
+
130
+ # with save: false.
131
+ it do
132
+ twin = Twin::AlbumWithSaveFalse.new(album)
133
+
134
+ fill_out!(twin)
135
+
136
+ twin.save
137
+
138
+ # sync happened.
139
+ album.name.must_equal "Live And Dangerous"
140
+ album.songs[0].must_be_instance_of Model::Song
141
+ album.songs[1].must_be_instance_of Model::Song
142
+ album.songs[0].title.must_equal "Southbound"
143
+ album.songs[1].title.must_equal "The Boys Are Back In Town"
144
+ album.songs[1].composer.must_be_instance_of Model::Artist
145
+ album.songs[1].composer.name.must_equal "Lynott"
146
+ album.artist.must_be_instance_of Model::Artist
147
+ album.artist.name.must_equal "Thin Lizzy"
148
+
149
+ # saved?
150
+ album.saved?.must_equal true
151
+ album.songs[0].saved?.must_equal nil
152
+ album.songs[1].saved?.must_equal nil
153
+ album.songs[1].composer.saved?.must_equal nil # doesn't get saved.
154
+ album.artist.saved?.must_equal true
155
+ end
156
+
157
+ def fill_out!(twin)
158
+ twin.name = "Live And Dangerous"
159
+ twin.songs[0].title = "Southbound"
160
+ twin.songs[1].title = "The Boys Are Back In Town"
161
+ twin.songs[1].composer.name = "Lynott"
162
+ twin.artist.name = "Thin Lizzy"
163
+ end
164
+ end
165
+
166
+
167
+ # TODO: with block
168
+
169
+ # class SaveWithDynamicOptionsTest < MiniTest::Spec
170
+ # Song = Struct.new(:id, :title, :length) do
171
+ # include Disposable::Saveable
172
+ # end
173
+
174
+ # class SongForm < Reform::Form
175
+ # property :title#, save: false
176
+ # property :length, virtual: true
177
+ # end
178
+
179
+ # let (:song) { Song.new }
180
+ # let (:form) { SongForm.new(song) }
181
+
182
+ # # we have access to original input value and outside parameters.
183
+ # it "xxx" do
184
+ # form.validate("title" => "A Poor Man's Memory", "length" => 10)
185
+ # length_seconds = 120
186
+ # form.save(length: lambda { |value, options| form.model.id = "#{value}: #{length_seconds}" })
187
+
188
+ # song.title.must_equal "A Poor Man's Memory"
189
+ # song.length.must_equal nil
190
+ # song.id.must_equal "10: 120"
191
+ # end
192
+ # end
@@ -0,0 +1,69 @@
1
+ require "test_helper"
2
+
3
+ require "disposable/twin/schema"
4
+
5
+ class SchemaTest < MiniTest::Spec
6
+ module Representer
7
+ include Representable
8
+
9
+ property :id
10
+ property :title, writeable: false, deserializer: {skip_parse: "skip lambda"}
11
+ property :songs, readable: false, deserializer: {skip_parse: "another lambda", music: true, writeable: false} do
12
+ property :name, as: "Name", deserializer: {skip_parse: "a crazy cool instance method"}
13
+ end
14
+ end
15
+
16
+ module Hello
17
+ def hello
18
+ "hello"
19
+ end
20
+ end
21
+
22
+ module Ciao
23
+ def ciao
24
+ "ciao"
25
+ end
26
+ end
27
+
28
+ module Gday
29
+ def hello
30
+ "G'day"
31
+ end
32
+ end
33
+
34
+ it do
35
+ decorator = Disposable::Twin::Schema.from(Representer, superclass: Representable::Decorator,
36
+ include: [Hello, Gday, Ciao], # Hello will win over Gday.
37
+ options_from: :deserializer,
38
+ representer_from: lambda { |nested| nested }
39
+ )
40
+
41
+ # include: works.
42
+ decorator.new(nil).hello.must_equal "hello"
43
+ decorator.new(nil).ciao.must_equal "ciao"
44
+
45
+ decorator.representable_attrs.get(:id).inspect.must_equal "#<Representable::Definition ==>id @options={:parse_filter=>[], :render_filter=>[], :as=>\"id\"}>"
46
+ decorator.representable_attrs.get(:title).inspect.must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :deserializer=>{:skip_parse=>\"skip lambda\"}, :parse_filter=>[], :render_filter=>[], :as=>\"title\", :skip_parse=>\"skip lambda\"}>"
47
+
48
+ songs = decorator.representable_attrs.get(:songs)
49
+ options = songs.instance_variable_get(:@options)
50
+ nested_extend = options.delete(:extend)
51
+ options.inspect.must_equal "{:readable=>false, :deserializer=>{:skip_parse=>\"another lambda\", :music=>true, :writeable=>false}, :parse_filter=>[], :render_filter=>[], :as=>\"songs\", :_inline=>true, :skip_parse=>\"another lambda\", :music=>true, :writeable=>false}"
52
+
53
+ # nested works.
54
+ nested_extend.new(nil).hello.must_equal "hello"
55
+ nested_extend.new(nil).ciao.must_equal "ciao"
56
+
57
+ nested_extend.representable_attrs.get(:name).inspect.must_equal "#<Representable::Definition ==>name @options={:as=>\"Name\", :deserializer=>{:skip_parse=>\"a crazy cool instance method\"}, :parse_filter=>[], :render_filter=>[], :skip_parse=>\"a crazy cool instance method\"}>"
58
+ end
59
+
60
+ # :options_from and :include is optional
61
+ it do
62
+ decorator = Disposable::Twin::Schema.from(Representer, superclass: Representable::Decorator,
63
+ representer_from: lambda { |nested| nested }
64
+ )
65
+
66
+ decorator.representable_attrs.get(:id).inspect.must_equal "#<Representable::Definition ==>id @options={:parse_filter=>[], :render_filter=>[], :as=>\"id\"}>"
67
+ decorator.representable_attrs.get(:title).inspect.must_equal "#<Representable::Definition ==>title @options={:writeable=>false, :deserializer=>{:skip_parse=>\"skip lambda\"}, :parse_filter=>[], :render_filter=>[], :as=>\"title\"}>"
68
+ end
69
+ end
@@ -0,0 +1,139 @@
1
+ require "test_helper"
2
+
3
+ class TwinSetupTest < MiniTest::Spec
4
+ module Model
5
+ Song = Struct.new(:id, :title, :album, :composer)
6
+ Album = Struct.new(:id, :name, :songs, :artist)
7
+ Artist = Struct.new(:id)
8
+ end
9
+
10
+
11
+ module Twin
12
+ class Album < Disposable::Twin
13
+ property :id
14
+ property :name
15
+ collection :songs, twin: lambda { |*| Song }
16
+ property :artist, twin: lambda { |*| Artist }
17
+
18
+ include Setup
19
+ end
20
+
21
+ class Song < Disposable::Twin
22
+ property :id
23
+ property :composer, twin: lambda { |*| Artist }
24
+
25
+ include Setup
26
+ end
27
+
28
+ class Artist < Disposable::Twin
29
+ property :id
30
+
31
+ include Setup
32
+ end
33
+ end
34
+
35
+
36
+ let (:song) { Model::Song.new(1, "Broken", nil) }
37
+ let (:composer) { Model::Artist.new(2) }
38
+ let (:song_with_composer) { Model::Song.new(1, "Broken", nil, composer) }
39
+ let (:artist) { Model::Artist.new(9) }
40
+
41
+ describe "with songs: [song, song{composer}]" do
42
+ let (:album) { Model::Album.new(1, "The Rest Is Silence", [song, song_with_composer], artist) }
43
+
44
+ it do
45
+ twin = Twin::Album.new(album)
46
+
47
+ twin.songs.size.must_equal 2
48
+ twin.songs.must_be_instance_of Disposable::Twin::Collection
49
+
50
+ twin.songs[0].must_be_instance_of Twin::Song
51
+ twin.songs[0].id.must_equal 1
52
+
53
+ twin.songs[1].must_be_instance_of Twin::Song
54
+ twin.songs[1].id.must_equal 1
55
+ twin.songs[1].composer.must_be_instance_of Twin::Artist
56
+ twin.songs[1].composer.id.must_equal 2
57
+ end
58
+ end
59
+
60
+ describe "with songs: [] and artist: nil" do
61
+ let (:album) { Model::Album.new(1, "The Rest Is Silence", [], nil) }
62
+
63
+ it do
64
+ twin = Twin::Album.new(album)
65
+
66
+ twin.songs.size.must_equal 0
67
+ twin.songs.must_be_instance_of Disposable::Twin::Collection
68
+ end
69
+ end
70
+
71
+ # DISCUSS: do we need to cover that (songs: nil in model)?
72
+ # describe "with non-existent :songs" do
73
+ # let (:album) { Model::Album.new(1, "The Rest Is Silence", nil) }
74
+
75
+ # it do
76
+ # twin = Twin::Album.new(album)
77
+
78
+ # twin.songs.size.must_equal 0
79
+ # twin.songs.must_be_instance_of Disposable::Twin::Collection
80
+ # end
81
+ # end
82
+ end
83
+
84
+ # test inline twin building and setup.
85
+ class TwinSetupWithInlineTwinsTest < MiniTest::Spec
86
+ module Model
87
+ Song = Struct.new(:id, :composer)
88
+ Album = Struct.new(:id, :name, :songs, :artist)
89
+ Artist = Struct.new(:id)
90
+ end
91
+
92
+ class AlbumForm < Disposable::Twin
93
+ feature Setup
94
+
95
+ property :id
96
+ property :name
97
+
98
+ collection :songs do # default_inline_class: Disposable::Twin
99
+ property :id
100
+
101
+ property :composer do
102
+ property :id
103
+ end
104
+ end
105
+
106
+ property :artist do
107
+ property :id
108
+ end
109
+ end
110
+
111
+ let (:song) { Model::Song.new(1) }
112
+ let (:composer) { Model::Artist.new(2) }
113
+ let (:song_with_composer) { Model::Song.new(3, composer) }
114
+ let (:artist) { Model::Artist.new(9) }
115
+ let (:album) { Model::Album.new(0, "Toto Live", [song, song_with_composer], artist) }
116
+
117
+ it do
118
+ twin = AlbumForm.new(album)
119
+ # pp twin
120
+
121
+ twin.id.must_equal 0
122
+ twin.name.must_equal "Toto Live"
123
+
124
+ twin.artist.must_be_kind_of Disposable::Twin
125
+ twin.artist.id.must_equal 9
126
+
127
+ twin.songs.must_be_instance_of Disposable::Twin::Collection
128
+
129
+ # nil nested objects work (no composer)
130
+ twin.songs[0].must_be_kind_of Disposable::Twin
131
+ twin.songs[0].id.must_equal 1
132
+
133
+ twin.songs[1].must_be_kind_of Disposable::Twin
134
+ twin.songs[1].id.must_equal 3
135
+
136
+ twin.songs[1].composer.must_be_kind_of Disposable::Twin
137
+ twin.songs[1].composer.id.must_equal 2
138
+ end
139
+ end