如何在 Rails 中为同一表单创建多个提交按钮?

我需要有多个提交按钮。

我有一个创建 Contact _ Call 实例的窗体。

一个按钮就能正常创建。

另一个按钮创建它,但需要与默认值不同的属性值,它还需要在控制器中使用的不同但相关的模型上设置属性。

我该怎么做?我不能改变路线,那么有没有办法发送一个不同的变量,它被[ : params ]拾取?

如果我这样做了,我在控制器中做什么,设置 case 语句?

55158 次浏览

You can create multiple submit buttons and provide a different value to each:

<% form_for(something) do |f| %>
..
<%= f.submit 'A' %>
<%= f.submit 'B' %>
..
<% end %>

This will output:

<input type="submit" value="A" id=".." name="commit" />
<input type="submit" value="B" id=".." name="commit" />

Inside your controller, the submitted button's value will be identified by the parameter commit. Check the value to do the required processing:

def <controller action>
if params[:commit] == 'A'
# A was pressed
elsif params[:commit] == 'B'
# B was pressed
end
end

However, remember that this tightly couples your view to the controller which may not be very desirable.

You can alternatively recognized which button was pressed changing its attribute name.

<% form_for(something) do |f| %>
..
<%= f.submit 'A', name: 'a_button' %>
<%= f.submit 'B', name: 'b_button' %>
..
<% end %>

It's a little bit uncomfortable because you have to check for params keys presence instead of simply check params[:commit] value: you will receive params[:a_button] or params[:b_button] depending on which one was pressed.

We solved using advanced constraints in rails.

The idea is to have the same path (and hence the same named route & action) but with constraints routing to different actions.

resources :plan do
post :save, constraints: CommitParamRouting.new("Propose"), action: :propose
post :save, constraints: CommitParamRouting.new("Finalize"), action: :finalize
end

CommitParamRouting is a simple class that has a method matches? which returns true if the commit param matches the given instance attr. value.

This available as a gem commit_param_matching.

Similar solution to one suggested by @vss123 without using any gems:

resources :plan do
post :save, constraints: lambda {|req| req.params.key?(:propose)}, action: :propose
post :save, constraints: lambda {|req| req.params.key?(:finalize)}, action: :finalize
end

Notice that I avoid using value and use input name instead since submit button value is often internationalized / translated. Also, I'd avoid using this too much since it will quickly clutter your routes file.

An old question, but since I've been dealing with the same situation, I thought I'd post my solution. I'm using controller constants to avoid introducing a discrepancy between the controller logic and the view button.

class SearchController < ApplicationController
SEARCH_TYPES = {
:searchABC => "Search ABCs",
:search123 => "Search 123s"
}


def search
[...]
if params[:commit] == SEARCH_TYPES[:searchABC]
[...]
elsif params[:commit] == SEARCH_TYPES[:search123]
[...]
else
flash[:error] = "Search type not found!"]
[...]
end
end
[...]
end

And then in the view:

<% form_for(something) do |f| %>
[...]
<%= f.submit SearchController::SEARCH_TYPES[:searchABC] %>
<%= f.submit SearchController::SEARCH_TYPES[:search123] %>
[...]
<% end %>

This way the text only lives in one place - as a constant in the controller. I haven't tried to figure out how to i18n this yet, however.

There is also another approach, using the formaction attribute on the submit button:

<% form_for(something) do |f| %>
...
<%= f.submit "Create" %>
<%= f.submit "Special Action", formaction: special_action_path %>
<% end %>

The code stays clean, as the standard create button doesn't need any change, you only insert a routing path for the special button:

formaction:
The URI of a program that processes the information submitted by the input element, if it is a submit button or image. If specified, it overrides the action attribute of the element's form owner. Source: MDN

I have a variable number of submit buttons on my form thanks to nested_form_fields, so just using the name wasn't enough for me. I ended up including a hidden input field in the form and using Javascript to populate it when one of the form submit buttons was pressed.