Possibility to pass optional arguments in step_to
Hello my friends,
Me again :P
During all that I used Stealth, has a functionality that I always missed. The possibility of passing a parameter to another flow action.
For example, when I work with dynamic items as a product listing. This would be my controller:
class ProductsController < BotController
def say_products
@products = Product.all
send_replies
end
def say_product_details
@product = Product.find(number: 1)
send_replies
end
end
And this, my reply.
<% if @products.count > 1 %>
- reply_type: list
top_element_style: compact
elements:
<% @products.each do |product| %>
- title: "<%= product.name %>"
subtitle: "🏷 <%= product.description %>"
buttons:
- type: "payload"
text: 'Details'
payload: PRODUCT_DETAILS_<%= product.number %>"
<% end %>
<% else %>
<% product = @products.first %>
- reply_type: text
text: |
Name: <%= product.name %>
Description: <%= product.description %>
Size: <%= product.size %>
Color: <%= product.color %>
buttons:
- type: "payload"
text: 'Buy'
payload: BUY_PRODUCT_<%= product.number %>"
- type: "payload"
text: 'Back'
payload: LIST_PRODUCTS
<% end %>
This is a little ugly to me and not reusable. I need copy and paste parte this code in product_details.yml and, every that this change, change in all other parte of project
A see a, maybe, better way to do this. If we could pass a product id with a params, and control this in controller, otherwise replies? Like this (is only a example to illustrate the case):
class ProductsController < BotController
def say_products
@products = Product.all
if @products.count > 1
send_replies
elsif @products.count = 1
step_to action: :say_product_details, params: { product_id: @products.last.number }
else
step_to action: :say_no_products
end
end
def say_product_details
@product = Product.find(number: 1)
send_replies
end
def say_no_products
send_replies
end
end
Some like this. Controller would orchestrate information and replies are only responsible for formatting them, and thus would better separation of responsibilities.
say_products.yml.erb only this:
- reply_type: list
top_element_style: compact
elements:
<% @products.each do |product| %>
- title: "<%= product.name %>"
subtitle: "🏷 <%= product.description %>"
buttons:
- type: "payload"
text: 'Details'
payload: PRODUCT_DETAILS_<%= product.number %>"
<% end %>
And say_product_details.yml.erb only this.
- reply_type: text
text: |
Name: <%= product.name %>
Description: <%= product.description %>
Size: <%= product.size %>
Color: <%= product.color %>
buttons:
- type: "payload"
text: 'Buy'
payload: BUY_PRODUCT_<%= product.number %>"
- type: "payload"
text: 'Back'
payload: LIST_PRODUCTS
Another benefit of this feature is more powerful and contextual messages to CatchAll system.
CatchAll scheme is a awesome to retry and limit wrongs inputs of the users, but your messages are very generics, to all cases. If we could pass the error as parameter, the bot will be able to respond more efficiently and contextually.
We could create validations that raise exceptions and for each exception return respective message with useful information.
def get_product_number
validate(@current_message.message)
# raise length error: The number should be more than 10 characters
# raise format error: Oh... this need be a number
# raise includes error: category need be shoes, t-shirt or hat.
# etc etc etc
send_replies
end
And in run_catch_all we can pass the error type.
def run_catch_all(reason: nil, error: nil)
error_level = fetch_error_level
Stealth::Logger.l(topic: "catch_all", message: "CatchAll #{calculate_catch_all_state(error_level)} triggered for #{error_slug}: #{reason}")
if defined?(CatchAllsController) && FlowMap.flow_spec[:catch_all].present?
catch_all_state = calculate_catch_all_state(error_level)
if FlowMap.flow_spec[:catch_all].states.keys.include?(catch_all_state.to_sym)
step_to flow: 'catch_all', state: catch_all_state, params: error
else
# We are out of bounds, do nothing to prevent an infinite loop
Stealth::Logger.l(topic: "catch_all", message: "Stopping; we've exceeded the number of defined catch_all states.")
return false
end
end
end
And in our catch_all_controller.rb we can take error message easily
def level1
@error_message = fetch_error_message(error)
send_replies
if fail_session.present?
step_to session: fail_session
else
step_to session: previous_session - 2.states
end
end
Perhaps https://github.com/hellostealth/stealth/pull/183 can work for you.
Works fine!! Thanks @bhtabor