stealth icon indicating copy to clipboard operation
stealth copied to clipboard

Possibility to pass optional arguments in step_to

Open luizcarvalho opened this issue 6 years ago • 3 comments

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

luizcarvalho avatar Mar 29 '19 13:03 luizcarvalho

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

luizcarvalho avatar Mar 29 '19 13:03 luizcarvalho

Perhaps https://github.com/hellostealth/stealth/pull/183 can work for you.

bhtabor avatar Aug 02 '19 08:08 bhtabor

Works fine!! Thanks @bhtabor

luizcarvalho avatar Aug 08 '19 11:08 luizcarvalho