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.
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
@@ -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,8 @@
1
+ Description:
2
+ This will generate a new ussd menu.
3
+
4
+ Example:
5
+ bin/rails generate joy_menu Transactions
6
+
7
+ This will create:
8
+ app/services/ussd/menus/transactions_menu.rb
@@ -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,8 @@
1
+ Description:
2
+ This will create a new paginating ussd menu
3
+
4
+ Example:
5
+ bin/rails generate joy_paginate_menu Paginate
6
+
7
+ This will create:
8
+ app/services/ussd/menus/paginate_menu.rb
@@ -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,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ bin/rails generate joy_route_menu Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -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