joy_ussd_engine 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +8 -0
  4. data/.rubocop.yml +13 -0
  5. data/CHANGELOG.md +5 -0
  6. data/CODE_OF_CONDUCT.md +84 -0
  7. data/Gemfile +10 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +559 -0
  10. data/Rakefile +8 -0
  11. data/bin/console +15 -0
  12. data/bin/setup +8 -0
  13. data/images/lifecycle.jpg +0 -0
  14. data/images/menu_doc1.png +0 -0
  15. data/images/menu_doc2.png +0 -0
  16. data/images/menu_items_routes.png +0 -0
  17. data/images/paginate_item_selected.png +0 -0
  18. data/images/paginate_menu1.png +0 -0
  19. data/images/paginate_menu2.png +0 -0
  20. data/images/transactions_menu.png +0 -0
  21. data/joy_ussd_engine.gemspec +38 -0
  22. data/lib/generators/joy_data_transformer/USAGE +8 -0
  23. data/lib/generators/joy_data_transformer/joy_data_transformer_generator.rb +13 -0
  24. data/lib/generators/joy_data_transformer/templates/joy_transformer_template.template +33 -0
  25. data/lib/generators/joy_menu/USAGE +8 -0
  26. data/lib/generators/joy_menu/joy_menu_generator.rb +13 -0
  27. data/lib/generators/joy_menu/templates/joy_menu_template.template +28 -0
  28. data/lib/generators/joy_paginate_menu/USAGE +8 -0
  29. data/lib/generators/joy_paginate_menu/joy_paginate_menu_generator.rb +13 -0
  30. data/lib/generators/joy_paginate_menu/templates/joy_paginate_menu_template.template +48 -0
  31. data/lib/generators/joy_route_menu/USAGE +8 -0
  32. data/lib/generators/joy_route_menu/joy_route_menu_generator.rb +13 -0
  33. data/lib/generators/joy_route_menu/templates/joy_route_menu_template.template +36 -0
  34. data/lib/joy_ussd_engine/data_transformer.rb +35 -0
  35. data/lib/joy_ussd_engine/menu.rb +218 -0
  36. data/lib/joy_ussd_engine/paginate_menu.rb +193 -0
  37. data/lib/joy_ussd_engine/session_manager.rb +41 -0
  38. data/lib/joy_ussd_engine/version.rb +5 -0
  39. data/lib/joy_ussd_engine.rb +47 -0
  40. metadata +101 -0
data/README.md ADDED
@@ -0,0 +1,559 @@
1
+ # JoyUssdEngine
2
+
3
+ A ruby library for building text based applications rapidly. It supports building whatsapp, ussd, telegram and various text or chat applications that communicate with your rails backend. With this library you can target multiple platforms(whatsapp, ussd, telegram, etc.) at once with just one codebase.
4
+
5
+ ## Table of Contents
6
+
7
+ - [JoyUssdEngine](#joyussdengine)
8
+ - [Table of Contents](#table-of-contents)
9
+ - [Installation](#installation)
10
+ - [Usage](#usage)
11
+ - [Bootstrap the App](#bootstrap-the-app)
12
+ - [Generators](#generators)
13
+ - [DataTransformer](#datatransformer)
14
+ - [Methods](#methods)
15
+ - [Example](#example)
16
+ - [Menu](#menu)
17
+ - [Menu Properties](#menu-properties)
18
+ - [Lifecycle Methods](#lifecycle-methods)
19
+ - [Render Methods](#render-methods)
20
+ - [Other Methods](#other-methods)
21
+ - [Create a menu](#create-a-menu)
22
+ - [Execution Order of Lifecycle Methods](#execution-order-of-lifecycle-methods)
23
+ - [Execution Order Diagram](#execution-order-diagram)
24
+ - [Get Http Post Data](#get-http-post-data)
25
+ - [Saving and Accessing Data](#saving-and-accessing-data)
26
+ - [Error Handling](#error-handling)
27
+ - [Routing Menus](#routing-menus)
28
+ - [PaginateMenu](#paginatemenu)
29
+ - [PaginateMenu Properties](#paginatemenu-properties)
30
+ - [PaginateMenu Methods](#paginatemenu-methods)
31
+ - [PaginateMenu Example](#paginatemenu-example)
32
+ - [Development](#development)
33
+ - [Contributing](#contributing)
34
+ - [License](#license)
35
+ - [Code of Conduct](#code-of-conduct)
36
+
37
+ ## Installation
38
+
39
+ Add this line to your application's Gemfile:
40
+
41
+ ```ruby
42
+ gem 'joy_ussd_engine'
43
+ ```
44
+
45
+ And then execute:
46
+
47
+ bundle install
48
+
49
+ Or install it yourself as:
50
+
51
+ gem install joy_ussd_engine
52
+
53
+ ## Usage
54
+
55
+ The ussd engine handles user session and stores user data with redis. So in your `Gemfile` you will have to add the `redis` and the `redis-namespace` gem.
56
+
57
+ ```ruby
58
+ gem 'redis'
59
+ gem 'redis-namespace'
60
+
61
+ # Not required but you can add connection pool for redis if you want
62
+ gem 'connection_pool', '~> 2.2', '>= 2.2.2'
63
+ ```
64
+
65
+ After installing redis you will need to setup the redis config in your rails application inside `config/initializers/redis.rb`
66
+
67
+ ```ruby
68
+ # With Connection Pool
69
+ require 'connection_pool'
70
+ NAMESPACE = :DEFAULT_NAMESPACE
71
+ REDIS = ConnectionPool.new(size: 10) { Redis::Namespace.new(NAMESPACE, :redis => Redis.new) }
72
+ ```
73
+
74
+ ```ruby
75
+ # Without Connection Pool
76
+ NAMESPACE = :DEFAULT_NAMESPACE
77
+ REDIS = Redis::Namespace.new(NAMESPACE, :redis => Redis.new)
78
+ ```
79
+
80
+ ### Bootstrap the App
81
+
82
+ In your rails app inside a controller create a post route and initialize the `JoyUssdEngine` by calling `JoyUssdEngine::Core.new` and providing some parameters. [Click here](#params) to view all the required parameters list.
83
+
84
+ ```ruby
85
+ class MyController < ApplicationController
86
+ skip_before_action :verify_authenticity_token
87
+
88
+ def create
89
+ joy_ussd_engine = JoyUssdEngine::Core.new(ussd_params, Transformers::HubtelTransformer, start_point: Ussd::Menus::StartMenu, end_point: Ussd::Menus::EndMenu)
90
+ response = joy_ussd_engine.process
91
+ render json: response, status: :created
92
+ end
93
+
94
+ def ussd_params
95
+ params.permit(:SessionId, :Mobile, :ServiceCode, :Type, :Message, :Operator, :Sequence, :ClientState)
96
+ end
97
+ end
98
+ ```
99
+
100
+ The `JoyUssdEngine::Core.new` class takes the following parameters.<a id="params"></a>
101
+
102
+ | Parameter | Type | Description |
103
+ | -------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------ |
104
+ | params | hash | Params coming from a post end point in a rails controller |
105
+ | [data_transformer](#transformer) | class | A class to transform the incoming and outgoing request between a particular provider and `JoyUssdEngine` |
106
+ | [start_point](#menu) | class | Points to a menu that starts the application. This menu is the first menu that loads when the app starts |
107
+ | [end_point](#menu) | class | This menu will terminate the ussd session if a particular provider (`data_transformer`) returns true in the `app_terminator` method. |
108
+
109
+ ### Generators
110
+
111
+ The rails terminal is very powerful and we can utilize it to generate menus easily.
112
+
113
+ - Generate a Menu - `rails g joy_menu <Menu_Name>`
114
+ - Generate a PaginateMenu - `rails g joy_paginate_menu <Menu_Name>`
115
+ - Generate a Routing Menu - `rails g joy_route_menu <Menu_Name>`
116
+ - Generate a DataTransformer - `rails g joy_data_transformer <Transformer_Name>`
117
+
118
+ ### DataTransformer
119
+
120
+ A data transformer transforms the incoming request and outgoing response between a particular provider and the `JoyUssdEngine` so they can effectively communicate between each other. The `JoyUssdEngine` can accept any request object but there are two required fields that needs to be present for it to work properly. The required fields are `session_id` and `message`. This is why the `DataTransformer` is needed to convert the request so it can provide this two required fields (`session_id`, `message`).
121
+
122
+ #### Methods
123
+
124
+ | Method | Parameters | Return Value | Description |
125
+ | -------------- | ------------------------------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
126
+ | request_params | params: [hash]() | [hash]() | Converts the incoming request into a format the ussd engine can understand. The hash is the params coming from the post request in a rails controller that calls `JoyUssdEngine::Core.new`. |
127
+ | response | message: [string](), app_state: [string]() | [hash]() | Converts the outgoing response coming from the ussd engine into a format the provider can understand. `(eg of providers: Whatsapp, Twilio, Hubtel, Telegram, etc.)` |
128
+ | release | message: [string]() | [hash]() | Converts the outgoing response coming from the ussd engine into a format the provider can understand and then terminates the application. `(eg of providers: Whatsapp, Twilio, Hubtel, Telegram, etc.)` |
129
+ | app_terminator | params: [hash]() | [boolean]() | Returns a true/false on whether to terminate the app when a particular condition is met based on the provider in use. `(eg of providers: Whatsapp, Twilio, Hubtel, Telegram, etc.)` |
130
+ | expiration | none | [Date/Time]() | Sets the time for which to end the user's session if there is no response from the user. **Default value is 60 seconds** |
131
+
132
+ #### Example
133
+
134
+ When using `hubtel` we need to convert the `hubtel` request into what the `JoyUssdEngine` expects with the `request_params` method. Also we need to convert the response back from `JoyUssdEngine` to `hubtel` with the `response` and `release` methods. With this approach we can easily extend the `JoyUssdEngine` to target multiple providers like (Twilio, Telegram, etc) with ease. The `app_terminator` returns a boolean and terminates the app when a particular condition is met(For example: On whatsapp the user sends a message with text `end` to terminate the app)
135
+
136
+ ```ruby
137
+ class HubtelTransformer < JoyUssdEngine::DataTransformer
138
+ # Transforms request payload between hubtel and our application
139
+ # The session_id and message fields are required so we get them from hubtel (session_id: params[:Mobile] and message: params[:Message]).
140
+ # And we pass in other hubtel specific params like (ClientState: params[:ClientState], Type: params[:Type])
141
+ def request_params(params)
142
+ {
143
+ session_id: params[:Mobile],
144
+ message: params[:Message],
145
+ ClientState: params[:ClientState],
146
+ Type: params[:Type]
147
+ }
148
+ end
149
+
150
+ # We check if hubtel sends a params[:Type] == 'Release' and terminate the application
151
+ # OR
152
+ # the hubtel params[:Type] is not a string with value "Initiation" and state data is blank (@context.get_state.blank?)
153
+ def app_terminator(params)
154
+ params[:Type] == 'Release' || (params[:Type] != "Initiation" && @context.get_state.blank?)
155
+ end
156
+
157
+ # Transforms response payload back to the format hubtel accepts by setting the message field (Type: "Response",Message: message, ClientState: client_state)
158
+ def response(message, client_state)
159
+ {
160
+ Type: "Response",
161
+ Message: message,
162
+ ClientState: client_state
163
+ }
164
+ end
165
+
166
+
167
+ # Transforms response payload back to the format hubtel accepts by setting the message field (Type: "Response",Message: message, ClientState: client_state) and then end the user session
168
+ def release(message)
169
+ {
170
+ Type: "Release",
171
+ Message: message,
172
+ ClientState: "End"
173
+ }
174
+ end
175
+
176
+
177
+ # Time for which the session has to end if the user does not send a request.
178
+ def expiration
179
+ 60.seconds
180
+ end
181
+ end
182
+ ```
183
+
184
+ ### Menu
185
+
186
+ Menus are simply the views for our application. They contain the code for rendering the text and menus that display on the user's device. Also they contain the business logic for your app.
187
+
188
+ #### Menu Properties
189
+
190
+ | Properties | Type | Description |
191
+ | ------------ | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
192
+ | context | object | Provides methods for setting and getting state values |
193
+ | field_name\* | string | The name for a particular input field. This name can be used to later retrieve the value the user entered in that field. (**Required**) |
194
+ | menu_text\* | string | The text to display to the user. (**Required**) |
195
+ | error_text | string | If there is an error you will have to set the error message here. (**Optional**) |
196
+ | skip_save | boolean | If set to true the user input will not be saved. **Default: false** (**Optional**) |
197
+ | menu_items | array <{title: '', menu: JoyUssdEngine::Menu}> | Stores an array of menu items. |
198
+ | field_error | boolean | If set to true it will route back to the menu the error was caught in for the user to input the correct values. |
199
+
200
+ #### Lifecycle Methods
201
+
202
+ | Methods | Description |
203
+ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
204
+ | before_render | Do all data processing and business logic here. |
205
+ | render | Render the ussd menu here. This is for only rendering out the response. (Only these methods `joy_release`, `joy_response`, `load_menu` can be used here) |
206
+ | after_render | After rendering out the ussd menu you can put any additional logic here. |
207
+ | on_validate | Validate user input here. |
208
+ | on_error | This method will be called when the `field_error` value is set to true. You can change the error message and render it to the user here. |
209
+
210
+ #### Render Methods
211
+
212
+ | Methods | Parameters | Description |
213
+ | ------------ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
214
+ | joy_response | Menu | This method takes a single argument (which is a class that points to the next menu) and is used to render out a menu to the user. |
215
+ | joy_release | none | This method renders a text to the user and ends the users session |
216
+ | load_menu | Menu | This method takes a single argument (which is a class that points to the next menu) and is used with the Routing and Paginating Menus to render out menu items. |
217
+
218
+ #### Other Methods
219
+
220
+ | Methods | Description |
221
+ | ----------------- | ---------------------------------------------------------------------- |
222
+ | show_menu | Returns a list of menus stored in the `menu_items` variable |
223
+ | get_selected_item | Gets the selected menu from the `menu_items` array |
224
+ | raise_error | Takes an error message as an arguments and ends the user session |
225
+ | has_selected? | Checks if the user has selected an item in from the `menu_items` array |
226
+
227
+ #### Create a menu
228
+
229
+ ```ruby
230
+ class Menus::MainMenu < JoyUssdEngine::Menu
231
+ def on_validate
232
+ # use this method to validate the user's input
233
+
234
+ # use @context.get_state with the @field_name value set in the before_render method to get the user inputs
235
+ if @context.get_state[:request] != "john doe"
236
+ # in case of errors set the @field_error value to true and set an error message in @error_text
237
+ @field_error = true
238
+ @error_text = "Wrong Value enter the correct value"
239
+ end
240
+ end
241
+
242
+ def before_render
243
+ # Implement before call backs
244
+
245
+ # store the input name for this ussd menu in the @field_name variable
246
+ @field_name = "request"
247
+
248
+ # store the text to show or render out in the ussd menu with the @menu_text variable
249
+ @menu_text = "Type you name"
250
+ end
251
+
252
+ def on_error
253
+ # this method will be executed if @field_error is set to true
254
+
255
+ # catch errors and display the errors in the ussd menu by setting the @menu_text to include the error_message from @error_text
256
+ @menu_text = "#{@error_text}\n#{@menu_text}"
257
+ end
258
+
259
+ def after_render
260
+ # Implement after call backs
261
+ end
262
+
263
+ def render
264
+ # Render ussd menu here
265
+
266
+ # the joy_response renders out the ussd menu and takes the class of the next menu to route to as an argument.
267
+ joy_response(Menus::NextMenu)
268
+ end
269
+ end
270
+ ```
271
+
272
+ This will be rendered out to the user when this menu is executed for the first time.
273
+
274
+ ![Menu1](./images/menu_doc1.png)
275
+
276
+ When the user enters a value which is not the string `"john doe"` an error will be displayed like we see in the screenshot below.
277
+
278
+ ![Menu2](./images/menu_doc2.png)
279
+
280
+ #### Execution Order of Lifecycle Methods
281
+
282
+ - before_render
283
+ ***
284
+ This is the first method that gets executed. It is used for querying the db and handling the business logic of our application. This method also is used to set the text (`menu_text`) to be rendered and the input name (`field_name`) for the current menu.
285
+ - on_validate
286
+
287
+ ***
288
+
289
+ This method will be executed when the user submits a response. We use this method to validate the user's input and set an error_message to display when there is an error. Normally we will set `field_error` value to true and store the error message in `error_text`. Then we can later access the error_message in the `on_error` lifecycle method and append the error to `menu_text` so it will be rendered out to the user.
290
+
291
+ - on_error
292
+
293
+ ***
294
+
295
+ This is the next method that gets executed and it is used to set error messages. It will only be executed if the `field_error` value is set to true.
296
+
297
+ - render
298
+
299
+ ***
300
+
301
+ This method is used for rendering out the menu by using the text stored in the `menu_text` variable. There are only three methods that should be used in the render method. Which are [joy_release](#render_methods), [joy_response](#render_methods), and [load_menu](render_methods).
302
+
303
+ - after_render
304
+ ***
305
+ Use this method to do any other business logic after the menu has been rendered out and awaiting user response.
306
+
307
+ #### Execution Order Diagram
308
+
309
+ The Diagram below shows how these methods are executed
310
+
311
+ ![Lifecycle Diagram](images/lifecycle.jpg)
312
+
313
+ #### Get Http Post Data
314
+
315
+ We can access the post request data coming from the rails controller in any menu with the `context` object. The `context` object can be used to access data by reading values from the `params` hash of a post request. This hash consist of the `session_id`, `message` and any other additional data returned by the `request_params` method in the [DataTransformer](#transformer) class.
316
+
317
+ ```ruby
318
+ # Just call @context.params[key] to access a particular value coming from a post request made available to our app through the DataTransformer.request_params method.
319
+
320
+ @context.params[:message] # Gets the message the user enters from the post end point.
321
+ ```
322
+
323
+ #### Saving and Accessing Data
324
+
325
+ We can save and access data in any menu with the `context` object. The `context` object has two methods `set_state` and `get_state` which are used for saving and retrieving data. The saved data will be destroyed once the user session ends or is expired and it is advisable to persist this data into a permanent storage like a database if you will need it after the user session has ended.
326
+
327
+ ```ruby
328
+ # Just call @context.set_state(key: value) to set a key with a particular value
329
+ @context.set_state(selected_book: selected_book)
330
+
331
+ # To access the values @context.get_state[:key]
332
+ @context.get_state[:selected_book]
333
+ ```
334
+
335
+ Also by default any menu that has the `field_name` variable set. Will automatically save the users input with a key matching the string stored in the `field_name` variable.
336
+
337
+ **Note:** However if the `skip_save` variable is set to true the user input will not be store for that particular menu. By default this value is false.
338
+
339
+ ```ruby
340
+ # This stores the name of the input field for this menu
341
+ @field_name = "user_email"
342
+
343
+ # @skip_save = true - user input will not be saved
344
+
345
+ # We can now get the user's input any where in our application with @context.get_state.
346
+ @context.get_state[:user_email]
347
+ ```
348
+
349
+ #### Error Handling
350
+
351
+ We can throw an error with a message and terminate the user session any where in our application by returning the `raise_error(error_message)` method and passing an error_message as an argument into the function.
352
+
353
+ ```ruby
354
+ # We raise an error in our application
355
+ return raise_error("Sorry something went wrong!")
356
+ ```
357
+
358
+ There is also another way to handle errors without ending or terminating the user session. We can use the `on_validate` lifecycle method to validate user input and when there is an error we set the `field_error` variable to true and the `error_text` variable to include the error message.
359
+
360
+ Then in the `on_error` lifecycle method we can append the `error_text` variable to the `menu_text` variable so it displays on the screen for the user.
361
+
362
+ **Note:** The `on_error` method will only be invoke if the `field_error` variable is set to true.
363
+
364
+ [View the example code on error handling here](#create_menu)
365
+
366
+ ### Routing Menus
367
+
368
+ You can show a list of menu items with their corresponding routes. When the user selects any item it will automatically route to the selected menu.
369
+ When the user selects a menu that is not in the list an error is displayed to the user and the user session wil be terminated.
370
+
371
+ ```ruby
372
+ class Menus::InitialMenu < JoyUssdEngine::Menu
373
+
374
+ def before_render
375
+ # Implement before call backs
376
+ @field_name='initiation'
377
+ @skip_save = true
378
+
379
+ # Store a list of menu items with their routes
380
+ @menu_items = [
381
+ {title: 'Make Payments', route: Menus::MakePayments},
382
+ {title: 'View Transaction', route: Menus::ViewTransaction}
383
+ ]
384
+
385
+ # Show menu items on screen with the show_menu method.
386
+ # The show_menu takes an optional parameter which is used to display the title of the page.
387
+ @menu_text = show_menu('Welcome to JoyUssdEngine')
388
+ end
389
+
390
+ def render
391
+ # Render ussd menu here
392
+
393
+ # Use this to load the menu the user selects
394
+ # get_selected_item automatically listens to user inputs and passes the selected menu into load_menu method
395
+ load_menu(get_selected_item)
396
+ end
397
+ end
398
+ ```
399
+
400
+ This will be rendered out when this menu is executed
401
+
402
+ ![MenuRoutes](./images/menu_items_routes.png)
403
+
404
+ If the `Menus::ViewTransaction` has a structure like this.
405
+
406
+ ```ruby
407
+ class Menus::ViewTransaction < JoyUssdEngine::Menu
408
+
409
+ def before_render
410
+ # Implement before call backs
411
+
412
+ @menu_text = "Transactions. \n1. ERN_CODE_SSD\n2. ERN_DESA_DAS\nThanks for using our services."
413
+ end
414
+
415
+ def render
416
+ # Render ussd menu here
417
+ joy_release
418
+ end
419
+ end
420
+ ```
421
+
422
+ When the user enters 2 in the `Menus::InitialMenu` menu then the following will be rendered and the user session will be terminated.
423
+
424
+ ![transaction](./images/transactions_menu.png)
425
+
426
+ The `Menus::ViewTransaction` menu uses the `joy_release` method to render out the text stored in the `@menu_text` variable and ends the user session.
427
+
428
+ ### PaginateMenu
429
+
430
+ A `PaginateMenu` handles pagination automatically for you. You can store an array of items that you want to paginate and they will be paginated automatically.
431
+ A `PaginateMenu` has all the properties and methods in a `Menu` in addition to the following properties.
432
+
433
+ #### PaginateMenu Properties
434
+
435
+ A `PaginateMenu` has the following properties in addition properties in [Menu](#menu).
436
+
437
+ | Properties | Type | Description |
438
+ | ---------------- | ----------- | ------------------------------------------------------------------------- |
439
+ | paginating_items | array <any> | Stores an array of items to paginate on a particular menu. |
440
+ | items_per_page | integer | The number of items to show per page. **Default: 5** |
441
+ | back_key | string | A string holding the input value for navigating back. **Default: '0'** |
442
+ | next_key | string | A string holding the input value for navigating forward. **Default: '#'** |
443
+
444
+ #### PaginateMenu Methods
445
+
446
+ | Methods | Description |
447
+ | ----------------- | ------------------------------------------------------------------------------------------------ |
448
+ | paginate | Returns a list of paginated items based on the page the user is currently on. |
449
+ | show_menu | Takes a list of paginated items and a page title as a parameter and renders it out on the screen |
450
+ | get_selected_item | Returns the selected item |
451
+ | has_selected? | Returns true if the user has selected an item |
452
+
453
+ #### PaginateMenu Example
454
+
455
+ ```ruby
456
+ class Menus::Books < JoyUssdEngine::PaginateMenu
457
+
458
+ def before_render
459
+ # Implement before call backs
460
+
461
+ # set an array of items that are going to be paginated
462
+ @paginating_items = [
463
+ {title: "Data Structures", item: {id: 1}},
464
+ {title: "Excel Programming", item: {id: 2}},
465
+ {title: "Economics", item: {id: 3}},
466
+ {title: "Big Bang", item: {id: 4}},
467
+ {title: "Democracy Building", item: {id: 5}},
468
+ {title: "Python for Data Scientist", item: {id: 6}},
469
+ {title: "Money Mind", item: {id: 7}},
470
+ {title: "Design Patterns In C#", item: {id: 8}}
471
+ ]
472
+
473
+ # The paginate methods returns a list of paginated list for the current page when it is called
474
+ paginated_list = paginate
475
+
476
+ # In a PaginateMenu the show_menu take a list a two optional named parameter values (title,key).
477
+
478
+ # The title shows the page title for the menu.
479
+
480
+ # The key stores the key of the hash which contains the text to be rendered on each list item.
481
+
482
+ # If the key is not set the paginating_items is treated as a string and rendered to the user.
483
+ # eg: @paginating_items = ["Data Structures","Excel Programming","Economics","Big Bang","Democracy Building","Python for Data Scientist","Money Mind","Design Patterns In C#"]
484
+
485
+ @menu_text = show_menu(paginated_list, title: 'My Books', key: 'title')
486
+
487
+ # In other to select a paginating item we have to wrap the selection logic in an if has_selected? block to prevent some weird errors.
488
+ if has_selected?
489
+ # the get_selected_item is used to get the selected item from the paginating menu
490
+ selected_book = get_selected_item
491
+
492
+ # We save the selected book so we can access later
493
+ @context.set_state(selected_book: selected_book)
494
+ end
495
+ end
496
+
497
+ def render
498
+ # Render ussd menu here
499
+
500
+ # The load_menu function points to a menu to load when a book is selected.
501
+ load_menu(Menus::ShowBook)
502
+ end
503
+ end
504
+ ```
505
+
506
+ To use a `PaginateMenu` we have to store the items to be paginated in the `paginating_items` variable. Then we call the `paginate` method and store the result in a variable. We can now pass the variable into the `show_menu` method and specify a `title` for the page if we have any. The `show_menu` method can also accept a `key` which is used to get the key containing the string to be rendered in a paginating_item. If the `key` is left blank the `paginating_items` are treated as strings and rendered automatically.
507
+
508
+ In order to get the item the user selected we have to wrap the selection login in an `if has_selected?` block to prevent some weird errors, then we can access the selected item with the `get_selected_item` method.
509
+
510
+ The following screenshots shows the paginating menu when it's first rendered.
511
+
512
+ ![paginate_menu1](./images/paginate_menu1.png)
513
+
514
+ When the user enters '#' we move to the next page in the list.
515
+
516
+ ![paginate_menu2](./images/paginate_menu2.png)
517
+
518
+ In the next menu (`Menus::ShowBook`) we have code that looks like this.
519
+
520
+ ```ruby
521
+ class Menus::ShowBook < Ussd::Menu
522
+ def before_render
523
+ # Implement before call backs
524
+ book = @context.get_state[:selected_book]
525
+ @menu_text = "The selected book is \nid: #{book[:item][:id]}\nname: #{book[:title]}"
526
+ end
527
+
528
+ def after_render
529
+ # Implement after call backs
530
+ end
531
+
532
+ def render
533
+ # Render ussd menu here
534
+ joy_release
535
+ end
536
+ end
537
+ ```
538
+
539
+ When th user selects an item in the `PaginateMenu` we get the users selection with `@context.get_state[:selected_book]` and display the selected item back to the user and end the session.
540
+
541
+ ![paginate_item_select](images/paginate_item_selected.png)
542
+
543
+ ## Development
544
+
545
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
546
+
547
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
548
+
549
+ ## Contributing
550
+
551
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/Caleb-Mantey/joy_ussd_engine>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/Caleb-Mantey/joy_ussd_engine/blob/master/CODE_OF_CONDUCT.md).
552
+
553
+ ## License
554
+
555
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
556
+
557
+ ## Code of Conduct
558
+
559
+ Everyone interacting in the JoyUssdEngine project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Caleb-Mantey/joy_ussd_engine/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubocop/rake_task"
5
+
6
+ RuboCop::RakeTask.new
7
+
8
+ task default: :rubocop
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "joy_ussd_engine"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/joy_ussd_engine/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "joy_ussd_engine"
7
+ spec.version = JoyUssdEngine::VERSION
8
+ spec.authors = ["Caleb Mantey"]
9
+ spec.email = ["manteycaleb@gmail.com"]
10
+
11
+ spec.summary = "A gem for building ussd and text based applications rapidly."
12
+ spec.description = "A ruby library for building text based applications rapidly. It supports building whatsapp, ussd, telegram and various text or chat applications that communicate with your rails backend. With this library you can target multiple platforms(whatsapp, ussd, telegram, etc.) at once with just one codebase."
13
+ spec.homepage = "https://github.com/Caleb-Mantey/joy_ussd_engine"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.4.0"
16
+
17
+ # spec.metadata["allowed_push_host"] = "'https://rubygems.org'"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/Caleb-Mantey/joy_ussd_engine"
21
+ spec.metadata["changelog_uri"] = "https://github.com/Caleb-Mantey/joy_ussd_engine/CHANGELOG.md"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ # Uncomment to register a new dependency of your gem
33
+ # spec.add_dependency "example-gem", "~> 1.0"
34
+ spec.add_dependency "will_paginate", "~> 3.3.0"
35
+
36
+ # For more information and examples about making a new gem, checkout our
37
+ # guide at: https://bundler.io/guides/creating_gem.html
38
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Will generate a DataTransformer class for transforming the incoming post request and outgoing response.
3
+
4
+ Example:
5
+ bin/rails generate joy_data_transformer Hubtel
6
+
7
+ This will create:
8
+ app/services/ussd/transformers/hubtel_transformer.rb