joy_ussd_engine 0.1.0
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.
- 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
@@ -0,0 +1,13 @@
|
|
1
|
+
class JoyDataTransformerGenerator < Rails::Generators::Base
|
2
|
+
source_root File.expand_path('templates', __dir__)
|
3
|
+
argument :transformer_name, type: :string
|
4
|
+
|
5
|
+
def generate_transformer
|
6
|
+
create_transformer
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def create_transformer
|
11
|
+
template 'joy_transformer_template.template', "app/services/ussd/transformers/#{transformer_name.underscore}_transformer.rb"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Ussd::Transformers::<%= transformer_name.camelcase %>Transformer < JoyUssdEngine::DataTransformer
|
2
|
+
|
3
|
+
# Responsible for transforming ussd requests and responses from different providers into
|
4
|
+
# what our application can understand
|
5
|
+
|
6
|
+
def request_params(params)
|
7
|
+
# transform request body of ussd provider currently in use to match the ussd engine request type
|
8
|
+
# {
|
9
|
+
# session_id: '',
|
10
|
+
# message: '',
|
11
|
+
# }
|
12
|
+
end
|
13
|
+
|
14
|
+
def app_terminator(params)
|
15
|
+
# Checks to see if ussd app can be terminated by a particular provider depending on the response
|
16
|
+
# default is to return false
|
17
|
+
return false
|
18
|
+
end
|
19
|
+
|
20
|
+
def response(message, next_state = '')
|
21
|
+
# Returns a tranformed ussd response for a particular provider and waits for user feedback
|
22
|
+
end
|
23
|
+
|
24
|
+
def release(message)
|
25
|
+
# Returns a tranformed ussd response for a particular provider and ends the ussd session
|
26
|
+
end
|
27
|
+
|
28
|
+
def expiration
|
29
|
+
# set expiration for different providers
|
30
|
+
# default is 60 seconds
|
31
|
+
60.seconds
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class JoyMenuGenerator < Rails::Generators::Base
|
2
|
+
source_root File.expand_path('templates', __dir__)
|
3
|
+
argument :menu_name, type: :string
|
4
|
+
|
5
|
+
def generate_menu
|
6
|
+
create_menu
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def create_menu
|
11
|
+
template 'joy_menu_template.template', "app/services/ussd/menus/#{menu_name.underscore}_menu.rb"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Ussd::Menus::<%= menu_name.camelcase %>Menu < JoyUssdEngine::Menu
|
2
|
+
def on_validate
|
3
|
+
# User input validation
|
4
|
+
end
|
5
|
+
|
6
|
+
def before_render
|
7
|
+
# Implement before call backs
|
8
|
+
@field_name="<%= menu_name.underscore %>"
|
9
|
+
@menu_text = "Welcome to the <%= menu_name.camelcase %> menu"
|
10
|
+
end
|
11
|
+
|
12
|
+
def on_error
|
13
|
+
# Render error
|
14
|
+
# @menu_text = "#{@error_text}\n#{@menu_text }"
|
15
|
+
end
|
16
|
+
|
17
|
+
def after_render
|
18
|
+
# Implement after call backs
|
19
|
+
end
|
20
|
+
|
21
|
+
def render
|
22
|
+
# Render ussd menu and pass the next menu as an argument
|
23
|
+
# joy_response(Ussd::Menus::<%= menu_name.camelcase %>Menu)
|
24
|
+
|
25
|
+
# Render ussd menu and terminate the user session
|
26
|
+
# joy_release
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class JoyPaginateMenuGenerator < Rails::Generators::Base
|
2
|
+
source_root File.expand_path('templates', __dir__)
|
3
|
+
argument :paginating_menu_name, type: :string
|
4
|
+
|
5
|
+
def generate_menu
|
6
|
+
create_menu
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def create_menu
|
11
|
+
template 'joy_paginate_menu_template.template', "app/services/ussd/menus/#{paginating_menu_name.underscore}_menu.rb"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Ussd::Menus::<%= paginating_menu_name.camelcase %>Menu < JoyUssdEngine::PaginateMenu
|
2
|
+
def on_validate
|
3
|
+
# User input validation
|
4
|
+
end
|
5
|
+
|
6
|
+
def before_render
|
7
|
+
# Implement before call backs
|
8
|
+
@field_name="<%= paginating_menu_name.underscore %>"
|
9
|
+
|
10
|
+
# Store paginating items in @paginating_items array variable
|
11
|
+
# @paginating_items = [
|
12
|
+
# {title: "Data Structures", item: {id: 1}},
|
13
|
+
# {title: "Excel Programming", item: {id: 2}},
|
14
|
+
# {title: "Economics", item: {id: 3}},
|
15
|
+
# {title: "Big Bang", item: {id: 4}},
|
16
|
+
# {title: "Democracy Building", item: {id: 5}},
|
17
|
+
# {title: "Python for Data Scientist", item: {id: 6}},
|
18
|
+
# {title: "Money Mind", item: {id: 7}},
|
19
|
+
# {title: "Design Patterns In C#", item: {id: 8}}
|
20
|
+
# ]
|
21
|
+
|
22
|
+
# Call the paginate method and store the return value in paginated_list
|
23
|
+
# paginated_list = paginate
|
24
|
+
|
25
|
+
# Use the show menu method to get the paginated list items and return them as text and store in @menu_text
|
26
|
+
# @menu_text = show_menu(paginated_list, title: "Welcome to the <%= paginating_menu_name.camelcase %> menu", key: 'title')
|
27
|
+
|
28
|
+
# Get the selected item from the user with the `get_selected_item` method and save in state with @context.set_state
|
29
|
+
# if has_selected?
|
30
|
+
# selected_book = get_selected_item
|
31
|
+
# @context.set_state(selected_book: selected_book)
|
32
|
+
# end
|
33
|
+
end
|
34
|
+
|
35
|
+
def on_error
|
36
|
+
# Render error
|
37
|
+
# @menu_text = "#{@error_text}\n#{@menu_text }"
|
38
|
+
end
|
39
|
+
|
40
|
+
def after_render
|
41
|
+
# Implement after call backs
|
42
|
+
end
|
43
|
+
|
44
|
+
def render
|
45
|
+
# Render paginating menu here or load the next menu after user has made a selection
|
46
|
+
# load_menu(Ussd::Menus::ShowBook)
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class JoyRouteMenuGenerator < Rails::Generators::Base
|
2
|
+
source_root File.expand_path('templates', __dir__)
|
3
|
+
argument :menu_name, type: :string
|
4
|
+
|
5
|
+
def generate_menu
|
6
|
+
create_menu
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def create_menu
|
11
|
+
template 'joy_route_menu_template.template', "app/services/ussd/menus/#{menu_name.underscore}_menu.rb"
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Ussd::Menus::<%= menu_name.camelcase %>Menu < JoyUssdEngine::Menu
|
2
|
+
def on_validate
|
3
|
+
# User input validation
|
4
|
+
end
|
5
|
+
|
6
|
+
def before_render
|
7
|
+
# Implement before call backs
|
8
|
+
# @field_name="<%= menu_name.underscore %>"
|
9
|
+
|
10
|
+
# title = "Welcome to the <%= menu_name.camelcase %> menu"
|
11
|
+
|
12
|
+
# Put menu routes in the @menu_items array
|
13
|
+
# @menu_items = [
|
14
|
+
# {title: 'Make Payments', route: Ussd::Menus::SendMenu},
|
15
|
+
# {title: 'View Transactions', route: Ussd::Menus::RequestMenu},
|
16
|
+
# {title: 'Books', route: Ussd::Menus::Books}
|
17
|
+
# ]
|
18
|
+
|
19
|
+
# Render the menu out with show_menu and pass the title as a parameter to the `show_menu` method
|
20
|
+
# @menu_text = show_menu(title)
|
21
|
+
end
|
22
|
+
|
23
|
+
def on_error
|
24
|
+
# Render error
|
25
|
+
# @menu_text = "#{@error_text}\n#{@menu_text }"
|
26
|
+
end
|
27
|
+
|
28
|
+
def after_render
|
29
|
+
# Implement after call backs
|
30
|
+
end
|
31
|
+
|
32
|
+
def render
|
33
|
+
# Render ussd menu and process to the selected menu when the user selects a menu.
|
34
|
+
# load_menu(get_selected_item)
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module JoyUssdEngine
|
2
|
+
class DataTransformer
|
3
|
+
# NOTE THIS CLASS SHOULD NEVER BE USED DIRECTLY BUT RATHER BE TREATED AS AN ABSTRACT CLASS
|
4
|
+
# THIS CLASS IS USED TO TRANSFORM REQUEST AND RESPONSE OBJECT FROM OUR USSD ENGINE
|
5
|
+
# TO ONE A PROVIDER (Hubtel, Twilio, etc.) CAN UNDERSTAND
|
6
|
+
|
7
|
+
# Responsible for transforming ussd requests and responses from different providers into
|
8
|
+
# what our application can understand
|
9
|
+
attr_reader :context
|
10
|
+
def initialize(context)
|
11
|
+
@context = context
|
12
|
+
end
|
13
|
+
|
14
|
+
def request_params(params)
|
15
|
+
# transform request body of ussd provider currently in use to match the ussd engine request type
|
16
|
+
end
|
17
|
+
|
18
|
+
def app_terminator(params)
|
19
|
+
#Checks to see if ussd app can be terminated by a particular provider depending on the response
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
|
23
|
+
def response(message, next_menu = nil)
|
24
|
+
# Returns a tranformed ussd response for a particular provider and wait for user feedback
|
25
|
+
end
|
26
|
+
|
27
|
+
def release(message)
|
28
|
+
# Returns a tranformed ussd response for a particular provider and ends the ussd session
|
29
|
+
end
|
30
|
+
|
31
|
+
def expiration
|
32
|
+
# set expiration for different providers
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'will_paginate/array'
|
2
|
+
module JoyUssdEngine
|
3
|
+
class Menu
|
4
|
+
# THIS CLASS IS THE BASE CLASS FOR ALL MENUS IN THE USSD ENGINE. EVERY MENU INHERITS FROM THIS CLASS AND IMPLEMENT THEIR CUSTOM BEHAVIOURS.
|
5
|
+
|
6
|
+
# NOTE THIS CLASS SHOULD NEVER BE USED DIRECTLY BUT RATHER BE TREATED AS AN ABSTRACT CLASS
|
7
|
+
# FROM WHICH OTHER CLASSES CAN INHERIT FROM AND IMPLEMENT CERTAIN CUSTOM BEHAVIOURS PERTAINING TO THEIR OPERATION
|
8
|
+
|
9
|
+
attr_reader :context
|
10
|
+
attr_accessor :field_name, :field_error, :errors, :skip_save, :previous_client_state, :current_client_state, :menu_text, :error_text, :menu_items, :menu_error, :previous_menu
|
11
|
+
|
12
|
+
def initialize(context)
|
13
|
+
@context = context
|
14
|
+
@current_client_state = @context.current_menu
|
15
|
+
end
|
16
|
+
|
17
|
+
def joy_response(client_state)
|
18
|
+
new_state = client_state.to_s
|
19
|
+
if @menu_text.blank?
|
20
|
+
set_previous_state
|
21
|
+
@context.current_menu = @current_client_state = new_state
|
22
|
+
return @context.load_menu(new_state)
|
23
|
+
end
|
24
|
+
|
25
|
+
{
|
26
|
+
ClientState: new_state,
|
27
|
+
data: @context.selected_provider.send("response", @menu_text, new_state)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def joy_release(error_message = "")
|
32
|
+
@context.reset_state
|
33
|
+
{
|
34
|
+
ClientState: "EndJoyUssdEngineiuewhjsdj",
|
35
|
+
data: @context.selected_provider.send("release", error_message.blank? ? @menu_text : error_message)
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_menu(menu_to_load)
|
40
|
+
return render_menu_error[:data] if @menu_error
|
41
|
+
next_menu = menu_to_load.to_s
|
42
|
+
if has_selected?
|
43
|
+
@context.set_state({"#{@current_client_state}_show_menu_initiation".to_sym => nil})
|
44
|
+
set_previous_state
|
45
|
+
@context.current_menu = @current_client_state = next_menu
|
46
|
+
@context.load_menu(next_menu)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def show_menu(title = '')
|
51
|
+
raise_error("Sorry something went wrong!") if @menu_items.blank?
|
52
|
+
tmp_menu = []
|
53
|
+
|
54
|
+
first_option = 0
|
55
|
+
@menu_items.each do |m|
|
56
|
+
tmp_menu << "#{first_option+=1}. #{m[:title]}"
|
57
|
+
end
|
58
|
+
text = tmp_menu.join("\n")
|
59
|
+
title.blank? ? text : "#{title}\n#{text}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def before_show_menu
|
63
|
+
is_first_render = @context.get_state[:"#{@current_client_state}_show_menu_initiation"].blank?
|
64
|
+
@context.set_state({"#{@current_client_state}_show_menu_initiation".to_sym => is_first_render ? "is_new" : "exists"})
|
65
|
+
end
|
66
|
+
|
67
|
+
def has_selected?
|
68
|
+
@context.get_state[:"#{@current_client_state}_show_menu_initiation"] == "exists"
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_selected_item(error_message = "Sorry wrong option selected")
|
72
|
+
|
73
|
+
return unless has_selected?
|
74
|
+
|
75
|
+
selected_item = nil
|
76
|
+
check_input = is_numeric(@context.params[:message]) && @context.params[:message].to_i != 0 && !(@context.params[:message].to_i > @menu_items.length)
|
77
|
+
|
78
|
+
if check_input
|
79
|
+
selected_item = @menu_items[@context.params[:message].to_i - 1][:route]
|
80
|
+
end
|
81
|
+
|
82
|
+
# 17332447
|
83
|
+
|
84
|
+
@menu_error = selected_item.blank?
|
85
|
+
@error_text = error_message if @menu_error
|
86
|
+
|
87
|
+
selected_item
|
88
|
+
end
|
89
|
+
|
90
|
+
def is_numeric(numeric_string)
|
91
|
+
"#{numeric_string}" !~ /\D/
|
92
|
+
end
|
93
|
+
|
94
|
+
def before_render
|
95
|
+
# Implement before call backs
|
96
|
+
puts "before render passed"
|
97
|
+
end
|
98
|
+
|
99
|
+
def after_render
|
100
|
+
# Implement after call backs
|
101
|
+
puts "after render passed"
|
102
|
+
end
|
103
|
+
|
104
|
+
def save_state(state)
|
105
|
+
return if @skip_save
|
106
|
+
data = @context.get_state
|
107
|
+
@previous_client_state = @current_client_state
|
108
|
+
# @context.set_state({"#{data[:field]}".to_sym => @context.params[:message]}) unless data[:field].blank?
|
109
|
+
@context.set_state({ClientState: state, PrevClientState: @previous_client_state , field: @field_name, field_error: @field_error, error_text: @error_text})
|
110
|
+
end
|
111
|
+
|
112
|
+
def save_field_value
|
113
|
+
data = get_previous_state
|
114
|
+
return if @skip_save
|
115
|
+
@context.set_state({"#{data[:field]}".to_sym => @context.params[:message]}) unless data[:field].blank?
|
116
|
+
end
|
117
|
+
|
118
|
+
def get_previous_state
|
119
|
+
data = @context.get_state
|
120
|
+
@previous_client_state = data[:PrevClientState]
|
121
|
+
data
|
122
|
+
end
|
123
|
+
|
124
|
+
def set_previous_state
|
125
|
+
@context.set_state({PrevClientState: @current_client_state})
|
126
|
+
end
|
127
|
+
|
128
|
+
def render
|
129
|
+
# Render ussd menu here
|
130
|
+
puts "render passed"
|
131
|
+
end
|
132
|
+
|
133
|
+
def on_validate
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
def on_error
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
def raise_error(message)
|
142
|
+
@error_text = message
|
143
|
+
@menu_error = true
|
144
|
+
end
|
145
|
+
|
146
|
+
def render_menu_error
|
147
|
+
@context.reset_state
|
148
|
+
joy_release(@error_text)
|
149
|
+
end
|
150
|
+
|
151
|
+
def render_field_error
|
152
|
+
before_show_menu
|
153
|
+
before_render
|
154
|
+
on_error
|
155
|
+
return render_menu_error[:data] if menu_error
|
156
|
+
response = render
|
157
|
+
response = response.blank? ? joy_response(current_client_state) : response
|
158
|
+
after_render
|
159
|
+
save_state(response[:ClientState]) if response[:ClientState] != "EndJoyUssdEngineiuewhjsdj"
|
160
|
+
@context.reset_state if response[:ClientState] == "EndJoyUssdEngineiuewhjsdj"
|
161
|
+
response[:data].blank? ? response : response[:data]
|
162
|
+
end
|
163
|
+
|
164
|
+
def render_previous
|
165
|
+
@context.current_menu = @previous_menu.current_client_state = @previous_client_state
|
166
|
+
@context.set_state({"#{@previous_client_state}_show_menu_initiation".to_sym => nil})
|
167
|
+
@previous_menu.render_field_error
|
168
|
+
end
|
169
|
+
|
170
|
+
def is_last_menu
|
171
|
+
@context.last_menu == @current_client_state
|
172
|
+
end
|
173
|
+
|
174
|
+
def allow_validation
|
175
|
+
!is_last_menu && !@previous_client_state.blank?
|
176
|
+
end
|
177
|
+
|
178
|
+
def do_validation
|
179
|
+
return unless allow_validation
|
180
|
+
@previous_menu = @previous_client_state.constantize.new(@context)
|
181
|
+
@previous_menu.on_validate
|
182
|
+
end
|
183
|
+
|
184
|
+
def run
|
185
|
+
save_field_value
|
186
|
+
do_validation
|
187
|
+
before_show_menu
|
188
|
+
before_render
|
189
|
+
return render_menu_error[:data] if @menu_error
|
190
|
+
if allow_validation
|
191
|
+
return render_previous if @previous_menu.field_error
|
192
|
+
end
|
193
|
+
response = render
|
194
|
+
response = response.blank? ? joy_response(@current_client_state) : response
|
195
|
+
after_render
|
196
|
+
save_state(response[:ClientState]) if response[:ClientState] != "EndJoyUssdEngineiuewhjsdj"
|
197
|
+
@context.reset_state if response[:ClientState] == "EndJoyUssdEngineiuewhjsdj"
|
198
|
+
response
|
199
|
+
end
|
200
|
+
|
201
|
+
def execute
|
202
|
+
save_field_value
|
203
|
+
do_validation
|
204
|
+
before_show_menu
|
205
|
+
before_render
|
206
|
+
return render_menu_error[:data] if @menu_error
|
207
|
+
if allow_validation
|
208
|
+
return render_previous if @previous_menu.field_error
|
209
|
+
end
|
210
|
+
response = render
|
211
|
+
response = response.blank? ? joy_response(@current_client_state) : response
|
212
|
+
after_render
|
213
|
+
save_state(response[:ClientState]) if response[:ClientState] != "EndJoyUssdEngineiuewhjsdj"
|
214
|
+
@context.reset_state if response[:ClientState] == "EndJoyUssdEngineiuewhjsdj"
|
215
|
+
response[:data].blank? ? response : response[:data]
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'will_paginate/array'
|
2
|
+
module JoyUssdEngine
|
3
|
+
class PaginateMenu < JoyUssdEngine::Menu
|
4
|
+
# NOTE THIS CLASS SHOULD NEVER BE USED DIRECTLY BUT RATHER BE TREATED AS AN ABSTRACT CLASS
|
5
|
+
# FROM WHICH OTHER CLASSES CAN INHERIT FROM AND IMPLEMENT CERTAIN CUSTOM BEHAVIOURS PERTAINING TO THEIR OPERATION
|
6
|
+
|
7
|
+
# Paginating Menus will need to set entire collection in paginating_items,
|
8
|
+
# The current_page is automatically determined from the user input and
|
9
|
+
# the default values items_per_page, back_key, and next_key can be overiding in any class that inherits from the PaginatingMenu
|
10
|
+
# Every paginating menu must set the current_client_state property to be their client_state this keeps track of pagination and menu location
|
11
|
+
|
12
|
+
# To paginate a menu you just call the `paginate` method and to display a paginated menu you call the `show_menu` method and pass the values returned from the `paginate` method.
|
13
|
+
# To get a item the user selects from the menu just use the `get_selected_item` method
|
14
|
+
# EXAMPLE:
|
15
|
+
# my_items = paginate
|
16
|
+
# show_menu(my_items)
|
17
|
+
# selected_item = get_selected_item
|
18
|
+
|
19
|
+
attr_reader :paginating_items, :paginating_items_selector, :paginating_error, :current_page, :items_per_page, :back_key, :next_key
|
20
|
+
|
21
|
+
def initialize(context)
|
22
|
+
super(context)
|
23
|
+
@items_per_page = 5
|
24
|
+
@back_key = '0'
|
25
|
+
@next_key = '#'
|
26
|
+
end
|
27
|
+
|
28
|
+
def paginate
|
29
|
+
|
30
|
+
before_paginate
|
31
|
+
return [] if @paginating_error
|
32
|
+
|
33
|
+
@current_page = get_current_page
|
34
|
+
|
35
|
+
paginated_items = @paginating_items.to_a.paginate(page: @current_page, per_page: @items_per_page)
|
36
|
+
|
37
|
+
is_first_render = @context.get_state[:"#{@field_name}_paginate_initiation"].blank?
|
38
|
+
|
39
|
+
@context.set_state({"#{@field_name}_paginate".to_sym => @current_page, "#{@field_name}_paginated_list_size".to_sym => paginated_items.length, "#{@field_name}_list_size".to_sym => paginated_items.length, "#{@field_name}_paginate_initiation".to_sym => is_first_render ? "is_new" : "exists"})
|
40
|
+
|
41
|
+
paginated_items
|
42
|
+
end
|
43
|
+
|
44
|
+
def show_menu(items=[], title: '', key: '')
|
45
|
+
|
46
|
+
return if has_selected?
|
47
|
+
|
48
|
+
@errors = true if @paginating_error || items.blank?
|
49
|
+
return @error_text if @paginating_error
|
50
|
+
return raise_error("Sorry something went wrong!") if items.blank?
|
51
|
+
|
52
|
+
more_menu = !is_last_page(@current_page, items.length)
|
53
|
+
back_menu = !is_first_page(@current_page)
|
54
|
+
|
55
|
+
item_number = (@current_page - 1) * @items_per_page
|
56
|
+
|
57
|
+
tmp_menu = []
|
58
|
+
|
59
|
+
first_option = item_number
|
60
|
+
items.each do |m|
|
61
|
+
tmp_menu << "#{first_option+=1}. #{key.blank? ? m : m[:"#{key}"]}"
|
62
|
+
end
|
63
|
+
|
64
|
+
tmp_menu << "#{back_key}. Back" if back_menu
|
65
|
+
tmp_menu << "#{next_key}. Next" if more_menu
|
66
|
+
text = tmp_menu.join("\n")
|
67
|
+
text = "#{title}\n#{text}" unless title.blank?
|
68
|
+
text
|
69
|
+
end
|
70
|
+
|
71
|
+
def load_menu(menu_to_load)
|
72
|
+
return render_menu_error[:data] if @menu_error
|
73
|
+
next_menu = menu_to_load.to_s
|
74
|
+
if has_selected?
|
75
|
+
@context.set_state({"#{@field_name}_paginate_initiation".to_sym => nil})
|
76
|
+
set_previous_state
|
77
|
+
@context.current_menu = @current_client_state = next_menu
|
78
|
+
@context.load_from_paginate_menu(next_menu)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def has_selected?
|
83
|
+
can_paginate? && ((@context.params[:message] != @next_key) && (@context.params[:message] != @back_key))
|
84
|
+
end
|
85
|
+
|
86
|
+
def can_paginate?
|
87
|
+
@context.get_state[:"#{@field_name}_paginate_initiation"] == 'exists'
|
88
|
+
end
|
89
|
+
|
90
|
+
def can_load_more?
|
91
|
+
!@context.get_state[:"#{@field_name}_paginate"].blank? &&
|
92
|
+
(@context.params[:message] == @next_key) && !is_last_page(get_previous_page, @context.get_state[:"#{@field_name}_paginated_list_size"])
|
93
|
+
end
|
94
|
+
|
95
|
+
def can_go_back?
|
96
|
+
!@context.get_state[:"#{@field_name}_paginate"].blank? &&
|
97
|
+
(@context.params[:message] == @back_key) && !is_first_page(get_previous_page)
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_selected_item(error_message = "Sorry wrong option selected")
|
101
|
+
|
102
|
+
selected_item = nil
|
103
|
+
check_input = is_numeric(@context.params[:message]) && @context.params[:message].to_i != 0
|
104
|
+
if check_input
|
105
|
+
selected_item = @paginating_items[@context.params[:message].to_i - 1]
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
@paginating_error = selected_item.blank?
|
110
|
+
@error_text = error_message if @paginating_error
|
111
|
+
selected_item
|
112
|
+
end
|
113
|
+
|
114
|
+
def load_next_page
|
115
|
+
(@context.get_state[:"#{@field_name}_paginate"].to_i + 1)
|
116
|
+
end
|
117
|
+
|
118
|
+
def load_prev_page
|
119
|
+
(@context.get_state[:"#{@field_name}_paginate"].to_i - 1)
|
120
|
+
end
|
121
|
+
|
122
|
+
def get_previous_page
|
123
|
+
can_paginate? ? @context.get_state[:"#{@field_name}_paginate"] : 1
|
124
|
+
end
|
125
|
+
|
126
|
+
def get_current_page
|
127
|
+
return 1 unless !@context.get_state[:"#{@field_name}_paginate"].blank?
|
128
|
+
return load_prev_page if can_go_back?
|
129
|
+
return load_next_page if can_load_more?
|
130
|
+
@current_page
|
131
|
+
end
|
132
|
+
|
133
|
+
def check_input_errors?
|
134
|
+
@errors = (!can_load_more? && @context.params[:message] == @next_key) || (!can_go_back? && @context.params[:message] == @back_key)
|
135
|
+
|
136
|
+
@error_text = "Sorry invalid input" if @errors
|
137
|
+
@errors
|
138
|
+
end
|
139
|
+
|
140
|
+
def is_last_page(page_number, page_items_size)
|
141
|
+
(((page_number - 1) * 5) + page_items_size) >= @paginating_items.count
|
142
|
+
end
|
143
|
+
|
144
|
+
def is_first_page(page_number)
|
145
|
+
page_number.present? ? page_number == 1 : true
|
146
|
+
end
|
147
|
+
|
148
|
+
def before_paginate
|
149
|
+
@paginating_error = check_input_errors?
|
150
|
+
end
|
151
|
+
|
152
|
+
def before_render
|
153
|
+
# To use pagination in a particular menu we need to follow the other of execution in the before_render method
|
154
|
+
#
|
155
|
+
# 1. {my_items = paginate} - first you have to execute the `paginate` method and cache the value
|
156
|
+
# 2. {show_menu(my_items)} - then you pass the cached value returned from the `paginate` method
|
157
|
+
# and save in some data in some variable you will use in the `render` method to render the page.
|
158
|
+
# 3. {get_selected_item if has_selected?} - we use this to `get_selected_item` to return the item
|
159
|
+
# the user selected and we do so by checking if the user has selected an item with the `has_selected?` method
|
160
|
+
end
|
161
|
+
|
162
|
+
def render
|
163
|
+
# Paginating menu's render method calls the `load_menu` method to load a particular menu
|
164
|
+
# This can come from a selected paginating item
|
165
|
+
# load_menu(menu_name)
|
166
|
+
|
167
|
+
# Paginating menus also terminates the session with a `joy_release` method to end the ussd_application
|
168
|
+
# joy_release(@error_message - optional)
|
169
|
+
end
|
170
|
+
|
171
|
+
def render_previous
|
172
|
+
@context.current_menu = @previous_menu.current_client_state = @previous_client_state
|
173
|
+
@context.set_state({"#{@field_name}_paginate_initiation".to_sym => nil})
|
174
|
+
@previous_menu.render_field_error
|
175
|
+
end
|
176
|
+
|
177
|
+
def execute
|
178
|
+
save_field_value
|
179
|
+
do_validation
|
180
|
+
before_render
|
181
|
+
return render_menu_error[:data] if @paginating_error || @menu_error
|
182
|
+
if allow_validation
|
183
|
+
return render_previous if @previous_menu.field_error
|
184
|
+
end
|
185
|
+
response = render
|
186
|
+
response = response.blank? ? joy_response(@current_client_state) : response
|
187
|
+
after_render
|
188
|
+
save_state(response[:ClientState]) if response[:ClientState] != "EndJoyUssdEngineiuewhjsdj"
|
189
|
+
@context.reset_state if response[:ClientState] == "EndJoyUssdEngineiuewhjsdj"
|
190
|
+
response[:data].blank? ? response : response[:data]
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|