Rendering the first step of a multi-stage form wizard as partial in another controller show action

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.

+5
source share
2 answers

Controllers are designed for independence, they can not depend on each other. This is different from submissions, which can be reused and compiled in partial how you do it.

If you need to reuse behavior in controllers (which does not match one controller depending on another), you can use inheritance or, following the Rails path, problems .

In this case, I would set the task to configure the @trade_wizard variable on any controller that includes a partial view of wizards/step1 .

+2
source

as said in elc I would use ajax to hide and show steps in combination with a nested form.

You create a Wizard model that has many steps and accepts steps as nested attributes . You can learn more about nested forms in guide rails.

 class Wizard < ActiveRecord:Base has_many :steps accepts_nested_attributes_for :steps end 

Model Step belongs to the Wizard

 class Step < ActiveRecord:Base belongs_to :wizard end 

this is your form

 <%= form_for @wizard, class: 'hidden' do |f| %> Addresses: <ul> <%= f.fields_for :steps do |step| %> // include your fields <% end %> </ul> <% end %> 

this form executes a post request to /wizards to add some ajax logic that will hide some of these steps forms that you create in the app/views/wizards called create.js.erb and write your js that can include any variable used in your controller since it is an erb file.

It depends on you how you want to write it, but you can include this logic in the wizards#create action

in some cases, you may need to run js to display the following form, other cases that you want to save for this object, and visualize a new view. The concept is that http does not have statelessness, so for each request you will recreate an instance of @wizard, but the field filled out of the form when it is hidden will still be re-presented as strong parameters

 # app/controllers/wizards_controller.rb # ...... def create @wizard = Wizard.new(params[:wizard]) respond_to do |format| // you can set conditions and perform different AJAX responses based on the request you received. format.js format.html { render action: "new" } end end 

I would write more, but I need to go

+2
source

Source: https://habr.com/ru/post/1274847/


All Articles