Визуализация первого шага мастера многоэтапной формы как частичного в действии show другого контроллера

Я хочу сделать первый шаг многошаговой формы для @trade_wizard (который имеет свой собственный контроллер, WizardsController) как частичное внутри ItemsController#show, но я не знаю, как построить это, не удваивая код с одного контроллера на другой.

Я отрисовываю первый шаг на странице показа элемента:

<%= render "/wizards/step1" %>

@trade_wizard обрабатывается в специальной модели, которая создает экземпляр @trade, а затем последовательно наследует проверки от каждого шага:

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

И тогда мой WizardsController запускает проверки на каждом шаге и сохраняет объект:

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

В моих маршрутах у меня есть

resource :wizard do
    get :step1
    get :step2
    get :step3
    post :validate_step
end

Ошибка, которую я получаю с этой настройкой First argument in form cannot contain nil or be empty, Я знаю, почему это происходит - мне нужно определить @trade_wizard внутри ItemsController # show, который я пока не делаю, потому что это просто приводит к дублированию кода из WizardsController. Мне не нужно, чтобы кто-то выполнял за меня мою работу, мне просто нужен указатель на то, как я могу найти выход из этой проблемы.

2 ответа

Решение

Контроллеры разработаны, чтобы быть независимыми, они не могут зависеть друг от друга. Это отличается от представлений, чем может быть повторно использовано и составлено через частичные компоненты, как вы делаете.

Если вам нужно повторно использовать поведение в контроллерах (которое отличается от одного контроллера в зависимости от другого), вы можете использовать наследование или, следуя путям Rails, проблемы.

В этом случае я хотел бы создать проблему для настройки @trade_wizard переменная в любом контроллере, который включает в себя wizards/step1частичный вид.

Как сказал elc, я бы использовал ajax, чтобы скрыть и показать шаги в сочетании с вложенной формой.

Вы создаете Wizard Модель, которая имеет много шагов и принимает steps как nested attributes, Вы можете прочитать больше о nested forms в направляющей рельсов

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

Step Модель принадлежит Wizard

class Step < ActiveRecord:Base
   belongs_to :wizard
end

это твоя форма

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

эта форма выполняет post запросить /wizards, чтобы добавить логику Ajax, которая позволит скрыть некоторые из тех, steps формы вы создаете файл в app/views/wizards называется create.js.erb и напиши там свой js логика, которая может включать в себя любую переменную, используемую в вашем контроллере, так как это erb файл.

От вас зависит, как вы хотите написать это, но вы можете включить эту логику в wizards#create действие

в некоторых случаях вы можете захотеть выполнить js, чтобы показать следующую форму, в других случаях вы хотите сохранить этот объект и отобразить новое представление. Концепция заключается в том, что http не имеет состояния, поэтому при каждом запросе вы будете воссоздавать экземпляр @wizard, но поле, заполненное в скрытой форме, все равно будет повторно отправлено в виде сильных параметров.

# 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

Я бы написал больше, но мне нужно идти

Другие вопросы по тегам