在另一个控制器的显示操作中将多步表单向导的第一步呈现为部分
rendering first step of multistep form wizard as partial in another controller's show action
我想将 @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
。我知道为什么会这样——我需要在 ItemsController#show 中定义 @trade_wizard
,我还没有这样做,因为这只会导致我从 WizardsController 复制代码。我不需要任何人为我做我的工作,我只需要一个指导,告诉我如何解决这个问题。
控制器被设计成独立的,它们不能相互依赖。这与视图不同,视图可以像您正在做的那样通过局部重用和组合。
如果您需要在控制器中重用行为(这与一个控制器依赖另一个控制器不同),您可以使用继承,或者按照 Rails 方式,concerns。
在这种情况下,我会担心在包含 wizards/step1
部分视图的任何控制器中设置 @trade_wizard
变量。
如 elc 所述,我会使用 ajax 隐藏和显示步骤,并结合嵌套形式。
您创建了一个 Wizard
模型,它有很多步骤并接受 steps
作为 nested attributes
。您可以在 rails guide
中阅读有关 nested forms
的更多信息
class Wizard < ActiveRecord:Base
has_many :steps
accepts_nested_attributes_for :steps
end
Step
模型属于向导
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 %>
此表单向 /wizards
执行 post
请求,添加一些 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
我会写更多但我需要去
我想将 @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
。我知道为什么会这样——我需要在 ItemsController#show 中定义 @trade_wizard
,我还没有这样做,因为这只会导致我从 WizardsController 复制代码。我不需要任何人为我做我的工作,我只需要一个指导,告诉我如何解决这个问题。
控制器被设计成独立的,它们不能相互依赖。这与视图不同,视图可以像您正在做的那样通过局部重用和组合。
如果您需要在控制器中重用行为(这与一个控制器依赖另一个控制器不同),您可以使用继承,或者按照 Rails 方式,concerns。
在这种情况下,我会担心在包含 wizards/step1
部分视图的任何控制器中设置 @trade_wizard
变量。
如 elc 所述,我会使用 ajax 隐藏和显示步骤,并结合嵌套形式。
您创建了一个 Wizard
模型,它有很多步骤并接受 steps
作为 nested attributes
。您可以在 rails guide
nested forms
的更多信息
class Wizard < ActiveRecord:Base
has_many :steps
accepts_nested_attributes_for :steps
end
Step
模型属于向导
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 %>
此表单向 /wizards
执行 post
请求,添加一些 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
我会写更多但我需要去