big_door 0.0.1

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 (73) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +72 -0
  3. data/README.rdoc +170 -0
  4. data/Rakefile +32 -0
  5. data/autotest/discover.rb +1 -0
  6. data/big_door.gemspec +56 -0
  7. data/bin/example.rb +93 -0
  8. data/features/resources/attribute.feature +41 -0
  9. data/features/resources/currency_type.feature +12 -0
  10. data/features/resources/end_user.feature +92 -0
  11. data/features/resources/named_award_collection.feature +42 -0
  12. data/features/resources/named_good_collection.feature +40 -0
  13. data/features/resources/named_level_collection.feature +44 -0
  14. data/features/resources/url.feature +41 -0
  15. data/features/step_definitions/resources_steps.rb +370 -0
  16. data/features/support/env.rb +9 -0
  17. data/lib/big_door/attribute.rb +21 -0
  18. data/lib/big_door/award.rb +24 -0
  19. data/lib/big_door/client.rb +281 -0
  20. data/lib/big_door/currency.rb +26 -0
  21. data/lib/big_door/currency_balance.rb +27 -0
  22. data/lib/big_door/currency_type.rb +29 -0
  23. data/lib/big_door/end_user.rb +32 -0
  24. data/lib/big_door/good.rb +32 -0
  25. data/lib/big_door/leaderboard.rb +41 -0
  26. data/lib/big_door/level.rb +25 -0
  27. data/lib/big_door/named_award.rb +24 -0
  28. data/lib/big_door/named_award_collection.rb +21 -0
  29. data/lib/big_door/named_good.rb +24 -0
  30. data/lib/big_door/named_good_collection.rb +22 -0
  31. data/lib/big_door/named_level.rb +26 -0
  32. data/lib/big_door/named_level_collection.rb +23 -0
  33. data/lib/big_door/named_transaction.rb +21 -0
  34. data/lib/big_door/named_transaction_group.rb +74 -0
  35. data/lib/big_door/profile.rb +28 -0
  36. data/lib/big_door/resource.rb +204 -0
  37. data/lib/big_door/resource_end_user.rb +84 -0
  38. data/lib/big_door/resource_with_association.rb +37 -0
  39. data/lib/big_door/resource_with_parent.rb +43 -0
  40. data/lib/big_door/url.rb +21 -0
  41. data/lib/big_door.rb +40 -0
  42. data/script/console +10 -0
  43. data/script/destroy +14 -0
  44. data/script/generate +14 -0
  45. data/spec/big_door/attribute_spec.rb +18 -0
  46. data/spec/big_door/award_spec.rb +19 -0
  47. data/spec/big_door/client_spec.rb +163 -0
  48. data/spec/big_door/currency_balance_spec.rb +14 -0
  49. data/spec/big_door/currency_spec.rb +81 -0
  50. data/spec/big_door/currency_type_spec.rb +21 -0
  51. data/spec/big_door/end_user_spec.rb +23 -0
  52. data/spec/big_door/good_spec.rb +14 -0
  53. data/spec/big_door/leaderboard_spec.rb +15 -0
  54. data/spec/big_door/level_spec.rb +19 -0
  55. data/spec/big_door/named_award_collection_spec.rb +23 -0
  56. data/spec/big_door/named_award_spec.rb +23 -0
  57. data/spec/big_door/named_good_collection_spec.rb +23 -0
  58. data/spec/big_door/named_good_spec.rb +23 -0
  59. data/spec/big_door/named_level_collection_spec.rb +23 -0
  60. data/spec/big_door/named_level_spec.rb +24 -0
  61. data/spec/big_door/named_transaction_group_spec.rb +29 -0
  62. data/spec/big_door/named_transaction_spec.rb +23 -0
  63. data/spec/big_door/profile_spec.rb +19 -0
  64. data/spec/big_door/resource_end_user_spec.rb +22 -0
  65. data/spec/big_door/resource_spec.rb +22 -0
  66. data/spec/big_door/resource_with_association_spec.rb +23 -0
  67. data/spec/big_door/resource_with_parent_spec.rb +22 -0
  68. data/spec/big_door/url_spec.rb +23 -0
  69. data/spec/spec.opts +1 -0
  70. data/spec/spec_helper.rb +17 -0
  71. data/tasks/cucumber.rake +5 -0
  72. data/tasks/rspec.rake +29 -0
  73. metadata +263 -0
@@ -0,0 +1,44 @@
1
+ @passing
2
+ Feature: named_level_collection API endpoint
3
+
4
+ In order to operate with named level collections
5
+ As object-oriented API client user
6
+ I want to use NamedLevelCollection object
7
+
8
+ @local
9
+ Scenario: get list of all NamedLevelCollections
10
+ Given low-level client
11
+ When I call it to list all "NamedLevelCollection" objects
12
+ Then I should get list of "NamedLevelCollection" objects
13
+
14
+ @local
15
+ Scenario: create a new NamedLevelCollection
16
+ Given low-level client
17
+ When I create a new "NamedLevelCollection" object
18
+ Then I should get a "NamedLevelCollection" object
19
+
20
+ @remote
21
+ Scenario: create and save a new NamedLevelCollection
22
+ Given low-level client
23
+ And some Currency
24
+ When I create a new "NamedLevelCollection" object
25
+ And assign some NamedLevelCollection data to object
26
+ And save object
27
+ Then I should get a "NamedLevelCollection" object
28
+ And object should has resource_id defined
29
+ And I should be able to remove object
30
+ And Currency should be removed
31
+
32
+ @remote
33
+ Scenario: add NamedLevel to NamedLevelCollection
34
+ Given low-level client
35
+ And some Currency
36
+ And new empty NamedLevelCollection
37
+ When I create a new "NamedLevel" object
38
+ And assign NamedLevel data to object
39
+ And save object
40
+ Then I should get a "NamedLevel" object
41
+ And object should has resource_id defined
42
+ And I should be able to remove object
43
+ And collection should be removed too
44
+ And Currency should be removed
@@ -0,0 +1,41 @@
1
+ @passing
2
+ Feature: url API endpoint
3
+
4
+ In order to operate with url objects
5
+ As object-oriented API client user
6
+ I want to use URL object
7
+
8
+ @remote
9
+ Scenario: get list of all URL
10
+ Given low-level client
11
+ When I call it to list all "URL" objects
12
+ Then I should get list of "URL" objects
13
+
14
+ @local
15
+ Scenario: create a new URL
16
+ Given low-level client
17
+ When I create a new "URL" object
18
+ Then I should get a "URL" object
19
+
20
+ @remote
21
+ Scenario: create and save a new URL
22
+ Given low-level client
23
+ When I create a new "URL" object
24
+ And assign "URL" data to object
25
+ And save object
26
+ Then I should get a "URL" object
27
+ And object should has resource_id defined
28
+ And I should be able to remove object
29
+
30
+ @remote
31
+ Scenario: create, save and associate a new URL
32
+ Given low-level client
33
+ And some Currency
34
+ When I create a new "URL" object
35
+ And assign "URL" data to object
36
+ And save object
37
+ And associate "URL" with "Currency"
38
+ Then I should get a "URL" object
39
+ And object should has resource_id defined
40
+ And I should be able to remove object
41
+ And Currency should be removed
@@ -0,0 +1,370 @@
1
+
2
+ Given /^low\-level client$/ do
3
+ @client = BigDoor::Client.new( TEST_APP_SECRET, TEST_APP_KEY )
4
+ end
5
+
6
+ Given /^new empty "NamedAwardCollection" collection$/ do
7
+ @collection = BigDoor::NamedAwardCollection.new
8
+ @collection.pub_title = 'application achievements'
9
+ @collection.pub_description = 'a set of achievements that the user can earn'
10
+ @collection.end_user_title = 'achievements'
11
+ @collection.end_user_description = 'things you can get'
12
+ @collection.save( @client )
13
+ end
14
+
15
+ Given /^new empty NamedGoodCollection$/ do
16
+ @collection = BigDoor::NamedGoodCollection.new
17
+ @collection.pub_title = 'Test Named Good Collection'
18
+ @collection.pub_description = 'test description'
19
+ @collection.end_user_title = 'test user title'
20
+ @collection.end_user_description = 'test user description'
21
+ @collection.save( @client )
22
+ end
23
+
24
+ Given /^new empty NamedLevelCollection$/ do
25
+ @collection = BigDoor::NamedLevelCollection.new({
26
+ 'pub_title' => 'Test Named Level Collection',
27
+ 'pub_description' => 'test description',
28
+ 'end_user_title' => 'test user title',
29
+ 'end_user_description' => 'test user description',
30
+ 'currency_id' => @currency.resource_id,
31
+ })
32
+
33
+ @collection.save( @client )
34
+ end
35
+
36
+ Given /^some Currency$/ do
37
+ @currency = BigDoor::Currency.new({
38
+ 'pub_title' => 'Coins',
39
+ 'pub_description' => 'an example of the Purchase currency type',
40
+ 'end_user_title' => 'Coins',
41
+ 'end_user_description' => 'can only be purchased',
42
+ 'currency_type_id' => '1',
43
+ 'currency_type_title' => 'Purchase',
44
+ 'exchange_rate' => 900.00,
45
+ 'relative_weight' => 2,
46
+ })
47
+ @currency.save( @client )
48
+ end
49
+
50
+ When /^I call it to list all "(\w+)" objects$/ do |class_name|
51
+ eval(" @list_all = BigDoor::#{class_name}.all( @client ) ")
52
+ end
53
+
54
+ When /^I call it to list all "(\w+)" objects from "EndUser"$/ do |class_name|
55
+ eval(" @list_all = BigDoor::#{class_name}.all( @username, @client ) ")
56
+ end
57
+
58
+ When /^I create a new "(\w+)" object$/ do |class_name|
59
+ eval(" @object = BigDoor::#{class_name}.new")
60
+ end
61
+
62
+ When /^assign some NamedAwardCollection data to object$/ do
63
+ @object.pub_title = 'application achievements'
64
+ @object.pub_description = 'a set of achievements that the user can earn'
65
+ @object.end_user_title = 'achievements'
66
+ @object.end_user_description = 'things you can get'
67
+ end
68
+
69
+ When /^assign some NamedGoodCollection data to object$/ do
70
+ @object.pub_title = 'Test Named Good Collection title'
71
+ @object.pub_description = 'test named good collection description'
72
+ @object.end_user_title = 'test named good collection end user title'
73
+ @object.end_user_description = 'test named good collection end user description'
74
+ end
75
+
76
+ When /^assign some NamedLevelCollection data to object$/ do
77
+ @object.pub_title = 'Test Named Level Collection title'
78
+ @object.pub_description = 'test named Level collection description'
79
+ @object.end_user_title = 'test named Level collection end user title'
80
+ @object.end_user_description = 'test named Level collection end user description'
81
+ @object.currency_id = @currency.resource_id
82
+ end
83
+
84
+ When /^assign NamedAward data to object$/ do
85
+ @object.pub_title = 'obligatory early achievement '
86
+ @object.pub_description = 'the sort of achievement you get when you can turn on an xbox'
87
+ @object.end_user_title = 'just breath'
88
+ @object.end_user_description = 'congratulations you rock so hard; keep on breathing'
89
+ @object.relative_weight = 1
90
+ @object.named_award_collection_id = @collection.resource_id
91
+ end
92
+
93
+ When /^assign NamedGood data to object$/ do
94
+ @object.pub_title = 'Named Good pub title'
95
+ @object.pub_description = 'Named Good pub description'
96
+ @object.end_user_title = 'Named Good end user title'
97
+ @object.end_user_description = 'Named Good end user description'
98
+ @object.relative_weight = 1
99
+ @object.named_good_collection_id = @collection.resource_id
100
+ end
101
+
102
+ When /^assign NamedLevel data to object$/ do
103
+ @object.pub_title = 'Named Level pub title'
104
+ @object.pub_description = 'Named Level pub description'
105
+ @object.end_user_title = 'Named Level end user title'
106
+ @object.end_user_description = 'Named Level end user description'
107
+ # @object.relative_weight = 1
108
+ # @object.threshold = 100
109
+ @object.named_level_collection_id = @collection.resource_id
110
+ end
111
+
112
+ When /^assign "Attribute" data to object$/ do
113
+ @object.pub_title = 'Attribute pub title'
114
+ @object.pub_description = 'Attribute pub description'
115
+ @object.end_user_title = 'Attribute end user title'
116
+ @object.end_user_description = 'Attribute end user description'
117
+ end
118
+
119
+ When /^assign "URL" data to object$/ do
120
+ @object.pub_title = 'URL pub title'
121
+ @object.pub_description = 'URL pub description'
122
+ @object.end_user_title = 'URL end user title'
123
+ @object.end_user_description = 'URL end user description'
124
+ @object.url = 'http://example.org'
125
+ end
126
+
127
+ When /^associate "([^"]*)" with "([^"]*)"$/ do |arg1, arg2|
128
+ @object.associate_with( @currency, @client)
129
+
130
+ end
131
+
132
+ Given /^some NamedLevelCollection with some NamedLevel$/ do
133
+ @nlc = BigDoor::NamedLevelCollection.new({
134
+ 'pub_title' => 'Test Named Level Collection title',
135
+ 'pub_description' => 'test named Level collection description',
136
+ 'end_user_title' => 'test named Level collection end user title',
137
+ 'end_user_description' => 'test named Level collection end user description',
138
+ 'currency_id' => @currency.resource_id,
139
+ })
140
+ @nlc.save( @client )
141
+
142
+ @nl = BigDoor::NamedLevel.new({
143
+ 'pub_title' => 'Named Level pub title',
144
+ 'pub_description' => 'Named Level pub description',
145
+ 'end_user_title' => 'Named Level end user title',
146
+ 'end_user_description' => 'Named Level end user description',
147
+ 'relative_weight' => 1,
148
+ 'threshold' => 10,
149
+ 'named_level_collection_id' => @nlc.resource_id,
150
+ })
151
+ @nl.save( @client )
152
+ end
153
+
154
+ Given /^some NamedAwardCollection with some NamedAward$/ do
155
+ @nac = BigDoor::NamedAwardCollection.new({
156
+ 'pub_title' => 'Test Named Award Collection title',
157
+ 'pub_description' => 'test named Award collection description',
158
+ 'end_user_title' => 'test named Award collection end user title',
159
+ 'end_user_description' => 'test named Award collection end user description',
160
+ })
161
+ @nac.save( @client )
162
+
163
+ @na = BigDoor::NamedAward.new({
164
+ 'pub_title' => 'Named Award pub title',
165
+ 'pub_description' => 'Named Award pub description',
166
+ 'end_user_title' => 'Named Award end user title',
167
+ 'end_user_description' => 'Named Award end user description',
168
+ 'relative_weight' => 1,
169
+ 'named_award_collection_id' => @nac.resource_id,
170
+ })
171
+ @na.save( @client )
172
+ end
173
+
174
+ Given /^some NamedGoodCollection with some NamedGood$/ do
175
+ @ngc = BigDoor::NamedGoodCollection.new({
176
+ 'pub_title' => 'Test Named Good Collection title',
177
+ 'pub_description' => 'test named Good collection description',
178
+ 'end_user_title' => 'test named Good collection end user title',
179
+ 'end_user_description' => 'test named Good collection end user description',
180
+ })
181
+ @ngc.save( @client )
182
+
183
+ @ng = BigDoor::NamedGood.new({
184
+ 'pub_title' => 'Named Good pub title',
185
+ 'pub_description' => 'Named Good pub description',
186
+ 'end_user_title' => 'Named Good end user title',
187
+ 'end_user_description' => 'Named Good end user description',
188
+ 'relative_weight' => 1,
189
+ 'named_good_collection_id' => @ngc.resource_id,
190
+ })
191
+ @ng.save( @client )
192
+ end
193
+
194
+ Given /^some NamedTransactionGroup with some NamedTransaction$/ do
195
+ @ntg = BigDoor::NamedTransactionGroup.new({
196
+ 'pub_title' => 'Test Transaction Group',
197
+ 'pub_description' => 'test description',
198
+ 'end_user_title' => 'end user title',
199
+ 'end_user_description' => 'end user description',
200
+ 'end_user_cap' => '-1',
201
+ 'end_user_cap_interval' => '-1',
202
+ })
203
+ @ntg.save( @client )
204
+
205
+ @nt = BigDoor::NamedTransaction.new({
206
+ 'pub_title' => 'Test Transaction',
207
+ 'pub_description' => 'test description',
208
+ 'end_user_title' => 'end user title',
209
+ 'end_user_description' => 'end user description',
210
+ 'currency_id' => @currency.resource_id,
211
+ 'amount' => '50',
212
+ 'default_amount' => '50',
213
+ })
214
+ @nt.save( @client )
215
+
216
+ @ntg.associate_with( @nt, @client, 1 )
217
+ end
218
+
219
+ Given /^some NamedTransactionGroup with some NamedTransaction with Good$/ do
220
+ @ntg = BigDoor::NamedTransactionGroup.new({
221
+ 'pub_title' => 'Test Transaction Group',
222
+ 'pub_description' => 'test description',
223
+ 'end_user_title' => 'end user title',
224
+ 'end_user_description' => 'end user description',
225
+ 'end_user_cap' => '-1',
226
+ 'end_user_cap_interval' => '-1',
227
+ 'has_good' => 'true',
228
+ })
229
+ @ntg.save( @client )
230
+
231
+ @ntwg = BigDoor::NamedTransaction.new({
232
+ 'pub_title' => 'Test Transaction',
233
+ 'pub_description' => 'test description',
234
+ 'end_user_title' => 'end user title',
235
+ 'end_user_description' => 'end user description',
236
+ 'named_good_id' => @ng.resource_id,
237
+ 'currency_id' => @currency.resource_id,
238
+ # 'amount' => '150',
239
+ # 'default_amount' => '150',
240
+ })
241
+ @ntwg.save( @client )
242
+
243
+ @ntg.associate_with( @ntwg, @client, 1 )
244
+ end
245
+
246
+ Given /^freshly created "([^"]*)" object with "([^"]*)" name$/ do |arg1, arg2|
247
+ @username = (0...10).map{ ('a'..'z').to_a[rand(26)] }.join
248
+ @object = BigDoor::EndUser.new({
249
+ 'end_user_login' => @username
250
+ })
251
+ @object.save( @client )
252
+ end
253
+
254
+ When /^I execute NamedTransactionGroup$/ do
255
+ @ntg.execute( @username, { 'good_receiver' => @username , 'verbosity' => '9', 'allow_negative_balance' => 'true'}, @client )
256
+ end
257
+
258
+ When /^I create and save a new "EndUser" object with "random" name$/ do
259
+ @username = (0...10).map{ ('a'..'z').to_a[rand(26)] }.join
260
+ @object = BigDoor::EndUser.new({
261
+ 'end_user_login' => @username
262
+ })
263
+ @object.save( @client )
264
+ end
265
+
266
+ When /^I assign Award to EndUser$/ do
267
+ award = BigDoor::Award.new({
268
+ 'end_user_login' => @username,
269
+ 'named_award_id' => @na.resource_id
270
+ })
271
+ award.save( @client )
272
+ end
273
+
274
+
275
+ When /^save object$/ do
276
+ @object.save( @client )
277
+ end
278
+
279
+ When /^load object$/ do
280
+ @object.load( @client )
281
+ end
282
+
283
+ Then /^I should get list of "(\w+)" objects$/ do |class_name|
284
+ @list_all.should be_a_instance_of( Array )
285
+ end
286
+
287
+ Then /^I should get list of all "(\d+)" "(\w+)" objects$/ do |number, class_name|
288
+ @list_all.should be_a_instance_of( Array )
289
+ @list_all.should have(number.to_i).items
290
+ if number.to_i > 0
291
+ @list_all[0].should be_a_instance_of( eval(sprintf "BigDoor::%s", class_name) )
292
+ unless class_name == 'CurrencyBalance'
293
+ @list_all[0].load( @client )
294
+ end
295
+ end
296
+ end
297
+
298
+ Then /^I should get a "(\w+)" object$/ do |class_name|
299
+ @object.should be_a_instance_of( eval(sprintf "BigDoor::%s", class_name) )
300
+ end
301
+
302
+ Then /^object should has resource_id defined$/ do
303
+ @object.should respond_to(:resource_id)
304
+ @object.resource_id.should be
305
+ @object.resource_id.to_s.should match(/\d+/)
306
+ end
307
+
308
+ Then /^object should has guid defined$/ do
309
+ @object.should respond_to(:guid)
310
+ @object.guid.should be
311
+ @object.guid.to_s.should match(/[a-f\d]+/)
312
+ end
313
+
314
+ Then /^I should see Leaderboard$/ do
315
+ lb = BigDoor::Leaderboard.new()
316
+ lb.execute({
317
+ 'format' => 'json',
318
+ 'verbosity' => '9',
319
+ 'type' => 'currency',
320
+ 'filter_value' => @currency.resource_id },
321
+ @client )
322
+
323
+ end
324
+ Then /^I should be able to remove object$/ do
325
+ @object.should respond_to(:delete)
326
+ @object.delete( @client )
327
+ end
328
+
329
+ Then /^collection should be removed too$/ do
330
+ @collection.should respond_to(:delete)
331
+ @collection.delete( @client )
332
+ end
333
+
334
+ Then /^Currency should be removed$/ do
335
+ @currency.delete( @client )
336
+ end
337
+
338
+ Then /^"([^"]*)" object should be removed$/ do |arg1|
339
+ @object.delete( @client )
340
+ end
341
+
342
+ Then /^"NamedAwardCollection" should be removed$/ do
343
+ @nac.delete( @client )
344
+ end
345
+
346
+ Then /^"NamedGoodCollection" should be removed$/ do
347
+ @ngc.delete( @client )
348
+ end
349
+
350
+ Then /^"NamedLevelCollection" should be removed$/ do
351
+ @nlc.delete( @client )
352
+ end
353
+
354
+ Then /^"NamedTransactionGroup" should be removed$/ do
355
+ @ntg.delete( @client )
356
+ end
357
+
358
+ Then /^I should be able to assign "Profile" to "EndUser"$/ do
359
+ @profile = BigDoor::Profile.new({
360
+ 'provider' => 'publisher',
361
+ 'email' => 'end_user@example.com',
362
+ 'first_name' => 'John',
363
+ 'last_name' => 'Doe',
364
+ 'display_name' => 'John Doe',
365
+ 'profile_photo' => 'http://example.com/image.jpg',
366
+ 'example_key' => 'Example Value',
367
+ 'end_user_login' => @username,
368
+ })
369
+ @profile.save( @client )
370
+ end
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
2
+
3
+ require 'big_door'
4
+
5
+ FAKE_APP_KEY = '28d3da80bf36fad415ab57b3130c6cb6'
6
+ FAKE_APP_SECRET = 'B66F956ED83AE218612CB0FBAC2EF01C'
7
+
8
+ TEST_APP_KEY = ENV['BIGDOOR_API_KEY'] || FAKE_APP_KEY
9
+ TEST_APP_SECRET = ENV['BIGDOOR_API_SECRET'] || FAKE_APP_SECRET
@@ -0,0 +1,21 @@
1
+ module BigDoor
2
+ #
3
+ # This module provides Attribute Resource object
4
+ # corresponding to /attribute BigDoor API end point
5
+ #
6
+ class Attribute < ResourceWithAssociation
7
+ ##
8
+ # Initialize new Attribute object with optional Hash
9
+ #
10
+ # @param [Hash] hash
11
+ # Optional fields to assign to object
12
+ #
13
+ def initialize( hash = {} )
14
+ $log.debug( "Attribute init with hash = #{hash.inspect}")
15
+ default_values = { }
16
+ default_values.merge!( hash )
17
+ $log.debug( "Attribute default_values = #{default_values.inspect}")
18
+ super( default_values )
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ module BigDoor
2
+ #
3
+ # This module provides Award Resource object
4
+ # corresponding to /end_user/{id}/award BigDoor API end point
5
+ #
6
+ class Award < ResourceEndUser
7
+ ##
8
+ # Initialize new Award object with optional Hash
9
+ #
10
+ # @param [Hash] hash
11
+ # Optional fields to assign to object
12
+ #
13
+ def initialize( hash = {} )
14
+ $log.debug( "Award init with hash = #{hash.inspect}")
15
+ default_values = {
16
+ 'end_user_login' => nil,
17
+ 'named_award_id' => nil,
18
+ }
19
+ default_values.merge!( hash )
20
+ $log.debug( "Award default_values = #{default_values.inspect}")
21
+ super( default_values )
22
+ end
23
+ end
24
+ end