joy_ussd_engine 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +559 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/images/lifecycle.jpg +0 -0
- data/images/menu_doc1.png +0 -0
- data/images/menu_doc2.png +0 -0
- data/images/menu_items_routes.png +0 -0
- data/images/paginate_item_selected.png +0 -0
- data/images/paginate_menu1.png +0 -0
- data/images/paginate_menu2.png +0 -0
- data/images/transactions_menu.png +0 -0
- data/joy_ussd_engine.gemspec +38 -0
- data/lib/generators/joy_data_transformer/USAGE +8 -0
- data/lib/generators/joy_data_transformer/joy_data_transformer_generator.rb +13 -0
- data/lib/generators/joy_data_transformer/templates/joy_transformer_template.template +33 -0
- data/lib/generators/joy_menu/USAGE +8 -0
- data/lib/generators/joy_menu/joy_menu_generator.rb +13 -0
- data/lib/generators/joy_menu/templates/joy_menu_template.template +28 -0
- data/lib/generators/joy_paginate_menu/USAGE +8 -0
- data/lib/generators/joy_paginate_menu/joy_paginate_menu_generator.rb +13 -0
- data/lib/generators/joy_paginate_menu/templates/joy_paginate_menu_template.template +48 -0
- data/lib/generators/joy_route_menu/USAGE +8 -0
- data/lib/generators/joy_route_menu/joy_route_menu_generator.rb +13 -0
- data/lib/generators/joy_route_menu/templates/joy_route_menu_template.template +36 -0
- data/lib/joy_ussd_engine/data_transformer.rb +35 -0
- data/lib/joy_ussd_engine/menu.rb +218 -0
- data/lib/joy_ussd_engine/paginate_menu.rb +193 -0
- data/lib/joy_ussd_engine/session_manager.rb +41 -0
- data/lib/joy_ussd_engine/version.rb +5 -0
- data/lib/joy_ussd_engine.rb +47 -0
- 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
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
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
|