рендеринг первого шага мастера многошаговой формы как частичного в действии 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. Мне не нужно, чтобы кто-то делал за меня мою работу, мне просто нужен указатель того, как я могу найти выход из этой проблемы.


person calyxofheld    schedule 21.01.2018    source источник
comment
Если разным контроллерам необходимо использовать общий код, поместите его в помощник контроллера. Или сделать базовый контроллер и расширить его. Ваш частичный фактически генерирует свои собственные запросы? Я имею в виду, что это ситуация типа AJAX, когда вы обрабатываете запросы и обновляете только часть страницы? Если нет, то контроллер для партиала звучит странно.   -  person elc    schedule 26.01.2018
comment
Каждый шаг делает POST запрос к validate_step_wizard_path, а затем перенаправляет на следующий шаг, так что да, частичное будет генерировать свой собственный запрос.   -  person calyxofheld    schedule 30.01.2018


Ответы (2)


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

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

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

person FedericoG    schedule 26.01.2018

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

Вы создаете модель Wizard, которая имеет много шагов и принимает steps как nested attributes. Подробнее о nested forms можно прочитать в руководстве по rails.

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

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

person Fabrizio Bertoglio    schedule 27.01.2018