I want to take the first step of a multi-step form for @trade_wizard (which has its own controller, WizardsController ) as partial inside ItemsController#show , but I donβt know how to build it without doubling the code from one controller to another.
I take the first step on the item display page:
<%= render "/wizards/step1" %>
@trade_wizard is processed in a special model that creates @trade, and then sequentially inherits checks from each step:
module Wizard module Trade STEPS = %w(step1 step2 step3).freeze class Base include ActiveModel::Model attr_accessor :trade delegate *::Trade.attribute_names.map { |attr| [attr, "#{attr}="] }.flatten, to: :trade def initialize(trade_attributes) @trade = ::Trade.new(trade_attributes) end end class Step1 < Base validates :trade_requester_id, :trade_recipient_id, :wanted_item_id, presence: true validates :shares, numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than_or_equal_to: :max_shares } def max_shares @trade.wanted_item.shares end end class Step2 < Step1 validates :collateral_item_id, presence: true end class Step3 < Step2 validates :agreement, presence: true end end end
And then my WizardsController runs checks at every step and saves the object:
class WizardsController < ApplicationController before_action :load_trade_wizard, except: %i(validate_step) def validate_step current_step = params[:current_step] @trade_wizard = wizard_trade_for_step(current_step) @trade_wizard.trade.attributes = trade_wizard_params session[:trade_attributes] = @trade_wizard.trade.attributes if @trade_wizard.valid? next_step = wizard_trade_next_step(current_step) create and return unless next_step redirect_to action: next_step else render current_step end end def create if @trade_wizard.trade.save session[:trade_attributes] = nil redirect_to root_path, notice: 'Trade succesfully created!' else redirect_to({ action: Wizard::Trade::STEPS.first }, alert: 'There were a problem when creating the trade.') end end private def load_trade_wizard @trade_wizard = wizard_trade_for_step(action_name) end def wizard_trade_next_step(step) Wizard::Trade::STEPS[Wizard::Trade::STEPS.index(step) + 1] end def wizard_trade_for_step(step) raise InvalidStep unless step.in?(Wizard::Trade::STEPS) "Wizard::Trade::#{step.camelize}".constantize.new(session[:trade_attributes]) end def trade_wizard_params params.require(:trade_wizard).permit(:trade_requester_id, :trade_recipient_id, :wanted_item_id, :collateral_item_id, :shares, :agreement) end class InvalidStep < StandardError; end end
In my routes I
resource :wizard do get :step1 get :step2 get :step3 post :validate_step end
The error with this setting is - First argument in form cannot contain nil or be empty . I know why this is happening - I need to define @trade_wizard inside the ItemsController # show, which I am not doing yet, because it just makes me duplicate the code from WizardsController. I donβt need someone to do my work for me, I just need a pointer to how I can get out of this problem.