0

I understand that this issue has been raised several times, but I can't find to find a way around..

I am using this solution to create a multi-step form without the Wicked gem: Best way to make Multi-Steps form in Rails 5

It seems that I am able to create a product when I use binding.pry and enter the needed commands inside my rails console.

However, the app in itself does not function and I can't manage to work around it..

Two issues throw the ActionController::ParameterMissing error:

1) First, anytime I intend to press the back_button it raises ParameterMissing error (see at the end for exact error message).

2) When I get to the last_step of the form, it displays all the needed information, but will neither create nor save new products (also ParameterMissing).

Here is my code:

Routes


  get 'products/new', to: 'products#new', as: 'new_product'
  post 'products', to: 'products#create', as: 'create_new_product'

  resources :categories, only: [:index, :show, :new, :edit, :destroy] do
    resources :sub_categories, only: [:index, :show, :new, :edit, :destroy]
    resources :products, only: [:index, :show, :destroy]
  end

Products Controller

class ProductsController < ApplicationController
  skip_after_action :verify_authorized, except: :index, unless: :skip_pundit?
  skip_after_action :verify_policy_scoped, only: :index, unless: :skip_pundit?
  before_action :set_category, only: [:index, :show]

  def new
    session[:product_params] ||= {}
    authorize @product = Product.new(session[:product_params])
    @product.current_step = session[:product_step]
    @product.user = current_user
  end

  def create

    session[:product_params].deep_merge!(params_product) if params_product
    authorize @product = Product.new(session[:product_params])
    @product.current_step = session[:product_step]

    @product.user = current_user

    if @product.valid?

      if params[:back_button]
        @product.previous_step

      elsif @product.last_step?
        if @product.all_valid?
          @product.save!
          flash[:notice] = 'Your product was created successfully'
          redirect_to newest_products_path && return
        end

      else
        @product.next_step
      end

    end
    session[:product_step] = @product.current_step

    if @product.new_record?
      return render :new

    else
      session[:product_step] = session[:product_params] = nil
    end
  end

  private

  def set_category
    authorize @category = Category.find(params[:category_id])
  end

  def params_product
    params.require(:product).permit(:name, :price, :description, :category, :category_id,
                                    :sub_category, :sub_category_id, :user, :user_id, :id)
  end
end

Product Model

class Product < ApplicationRecord
  attr_writer :current_step

  belongs_to :user, optional: true
  belongs_to :sub_category
  belongs_to :category, inverse_of: :products

  validates :category, presence: true

  validates_presence_of :name, :category_id, if: lambda { |e| e.current_step == "card" }
  validates_presence_of :sub_category_id, :description, :price, if: lambda { |e| e.current_step == "details" }

  def current_step
    @current_step || steps.first
  end

  def steps
    %w[card details confirmation]
  end

  def next_step
    self.current_step = steps[steps.index(current_step) + 1]
  end

  def previous_step
    self.current_step = steps[steps.index(current_step) - 1]
  end

  def first_step?
    current_step == steps.first
  end

  def last_step?
    current_step == steps.last
  end

  def all_valid?
    steps.all? do |step|
      self.current_step = step
      valid?
    end
  end
end

New Products View

      <%= form_for @product, url: create_new_product_path do |f| %>
        <%= render "#{@product.current_step}_step", :f => f  %>
        <div class="bottom-signup">
          <%= f.submit "Continue" unless @product.last_step? %>
          <%= f.submit "Submit Product" if @product.last_step? %>
          <%= f.submit "Back", :name => "back_button" unless @product.first_step? %>
        </div>
      <% end %>

Here is the exact error that is thrown by ActionController:

ActionController::ParameterMissing in ProductsController#create

param is missing or the value is empty: product

#around ligne (96)

95  def params_product
96    params.require(:product).permit(:name, :price, :description, :category, :category_id,
97                                    :sub_category, :sub_category_id, :user, :user_id, :id)
98  end

And finally, here is what appears if I raise params.inspect:

params.inspect

=> "<ActionController::Parameters {\"utf8\"=>\"✓\", \"authenticity_token\"=>\"AHfNwkeFOWBeEup+fCCvfZv1RowuP/YULHjo8kTnzer5YNCY7lMYAKzrrWJBVMcfOO+P1GmZGgi9PDpa/09rzw==\", \"commit\"=>\"Submit Product\", \"controller\"=>\"products\", \"action\"=>\"create\"} permitted: false>"

If someone understands where I'm wrong, I'd be more than glad to discuss it!

Thanks in advance.

Best, Ist

Istvanlaz
  • 3
  • 3
  • The problem is in the general approach. HTTP is stateless and when you shove a bunch of data into the session and pass it around like this you're really just laying a trap for yourself as all that dangling state and complexity is going to kill the test-ability of your code. Instead you need to save the record early and then do the following steps as series of PATCH requests to update the record, or split the domain into several objects. One common example of this is checkouts where people try to cram everything into `POST /orders` like its the end of the world. – max Apr 14 '20 at 18:41
  • The issue with your approach is that it means that I would have to create a product in the database even before it has passed the final step of seller approval. From the first step of the form onwards, and then update it along each step of the form (basically, using the wicked gem approach or similar - I have it up and running on an other branch). But I don't want to bother my db with incomplete and thus inconsistant products. That's why I chose this approach of several .deep_merge! which seems perfect for that purpose in theory.. however, it throws in the missingParam error.. do you know why? – Istvanlaz Apr 15 '20 at 11:00
  • Or how I could fix this error to make this parcel of the code function? – Istvanlaz Apr 15 '20 at 11:00
  • That's really a nonconcern. Add a flag/enum on the model to mark the model as incomplete and use a recurring task to clean up the DB. As I have already explained putting a record into the DB that's not "fully complete" in the DB is not the end of the world. And definately not worth completely abondoning the stateless nature of REST for. I'm not even going to attempt to salvage this as you need a change in course. – max Apr 15 '20 at 11:05
  • This code has just way to much cyclic complexity to ever be reliable. – max Apr 15 '20 at 11:07
  • Sorry, but I'm still relatively new to rails.. if you have 5 min, what do you call a flag/enum (briefly), and what is the goal of it? and how do you implement a recurring task to clean up the DB? Is that an idea of it?: https://stackoverflow.com/questions/46746431/handling-recurring-tasks-in-rails – Istvanlaz Apr 15 '20 at 11:18
  • [An enum](https://api.rubyonrails.org/v5.2.3/classes/ActiveRecord/Enum.html) is a column used to store a set of states like for example: `draft, pending_review, published`. By a flag I mean a boolean column like for example `completed` which you trigger on and off to denote state. – max Apr 15 '20 at 11:40
  • and how do you implement a recurring task to clean up the DB? Google it. – max Apr 15 '20 at 11:41

0 Answers0