0

I'm trying to create an order confirmation page for my Rails 6 app. The idea is that user will see a preview of the item they are creating before submitting and the object being saved in the database. Below desired flow:

  • User visits /cash_transactions/withdrawals/new
  • User enters data and clicks submit
  • User is redirected to /cash_transactions/withdrawals/confirm which displays the entry
  • User clicks confirm to save object to db or cancel
  • Object is saved

I followed two main threads that describe this type of action, but they are quite old - 11 and 12 years old. Nevertheless based on that I've created below code:

# controllers/cash_transactions/withdrawals_controller.tb

module CashTransactions
  class WithdrawalsController < CashTransactions::BaseController

    (...)

    def confirm
      @cash_transaction = CashTransaction.new(cash_transaction_params)

      render 'cash_transactions/_confirm'
    end
  end
end

# routes.rb

  namespace :cash_transactions do
    resources :withdrawals, only: %i[new create] do
      collection do
        post :confirm
      end
    end
  end

With corresponding views:

# app/views/cash_transactions/new.html.erb

<%= render 'cash_transactions/form', cash_transaction: @cash_transaction %>


# views/cash_transactions/_form
# the form is rendered for cash_transaction create action

<%= simple_form_for cash_transaction, url: { action: :confirm } do |f| %>
  <%= f.input :amount %>
  <%= f.button :submit, 'Submit' %>
<% end %>


# confirmation page under views/cash_transactions/_confirm.html.erb

<div>
  Total of withdrawal: <%= @cash_transaction.amount.to_i %>
</div>

<%= link_to 'Confim', cash_transactions_withdrawals_path(@cash_transaction), method: :post %>
<%= link_to 'Cancel', cash_transactions_path %>

And everything works until the user clicks confirm button in views/cash_transactions/_confirm.html.erb - instead of creating a record an error appears:

param is missing or the value is empty: cash_transaction Did you mean? authenticity_token action controller _method

where did I go wrong? or there is a completely different way to do so?

mr_muscle
  • 2,536
  • 18
  • 61
  • The code here doesn't actually show how you are rendering the form. Check the `name` attributes of the inputs in the rendered HTML. `render 'cash_transactions/_confirm'` is also pretty smelly. If you're rendering it from the controller then its not a partial. – max Aug 25 '21 at 14:19
  • @max I don't get it, after all it shows how the form is rendered here - `# views/cash_transactions/_form`. And that's it, there is only one input and one button inside of this form. – mr_muscle Aug 25 '21 at 14:49
  • No it doesn't, there is no `render partial: 'form', ...` which shows how you are passing the variable. – max Aug 25 '21 at 14:51
  • @max It's pretty standard - `<%= render 'cash_transactions/form', cash_transaction: @cash_transaction %>` inside of `app/views/cash_transactions/new.html.erb`. Question updated. – mr_muscle Aug 25 '21 at 15:00

1 Answers1

1

tl/dr: You need to add parameters to your create request.

Why this is happening

The /confirm view is being rendered with an (unsaved) @cash_transaction object, however that object is not being used and so the information is being lost when the page is rendered.

The line:

<%= link_to 'Confim', cash_transactions_withdrawals_path(@cash_transaction), method: :post %>

Will submit a POST request with no parameters to the /cash_transactions/withdrawals#create (because you've given it no parameters to post). It doesn't know to include the params from the previous request.

There are a few options to fix this... you can add params as URL parameters in link_to like this, however I wouldn't recommend posting with params in the URL.

You can use button_to instead, and pass in the cash_transaction arguments from the previous request in the params: option (or pull them out of the unsaved @cash_transaction object).

Approach #1 - reuse create params

# Get them from the params sent in the previous request. In the controller...
  def create
    @cash_transaction = CashTransaction.create!(cash_transaction_params)
    # etc...
  end
  #...
  protected

  def cash_transaction_params
    params[:cash_transaction].permit(:amount, :whatever)
  end
  helper_method :cash_transaction_params
# In the view
<%= button_to 'Confirm', {action: 'create', params: cash_transaction_params}

Approach #2 - Access attributes from the model you built

<%= button_to 'Confirm', {action: 'create', params: @cash_transaction.attributes.slice('amount', 'other_attribute') }

Or you could do something like render the form again but hidden and have the "confirm" button submit the hidden form (with { action: :create } instead of { action: :confirm}). This last solution is probably the easiest to understand.

melcher
  • 1,543
  • 9
  • 15
  • I'm getting an error: `unable to convert unpermitted parameters to hash` with `button_to` – mr_muscle Aug 25 '21 at 20:07
  • You can use `params[:cash_transaction].permit!` or whitelist them. Getting the parameters from the model is a good idea through. – max Aug 25 '21 at 21:46
  • I'll update w/ an example of getting them from the model - just keep in mind you need to keep it in sync with the `/new` form or the fields they submit won't actually be used. – melcher Aug 25 '21 at 21:48
  • @max @meicher guys, don't you think this `confirm` method should be GET instead of POST ? EOD it it behaves exactly the same as `new` method. – mr_muscle Aug 26 '21 at 01:37
  • Could be. You can’t post form data with a “get”, and I dislike sending form data in URL params… but it’s an option. I’d probably use a post myself but wouldn’t think too hard about it. In actuality I’d probably handle this with a confirmation modal and not push anything to the backend until the user confirms it (or create the thing in an unconfirmed status) – melcher Aug 26 '21 at 06:53
  • 1
    GET would be wrong as its not really a idempotent request. – max Aug 26 '21 at 09:18