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