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
@@ -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