Jimmy Shaw Jimmy Shaw - 3 years ago 82
Ruby Question

Why form submit keep defaulting to a particular POST action?

Imagine a typical Rails 4.2 application with a resource called location. Within the LocationsController, we have the usual actions.

The new view and the edit view both utilize a shared partial called _location_form.html.erb. I want to recycle that same partial view for two additional custom actions, clone and clone_create. The clone (GET) action has the same code body as the edit action. The clone_create (POST) action has the same code body as the create action.

The ultimate goal is to add a 'Clone' button on show.html.erb, which leads to clone.html.erb that has a particular location's data already pre-populated on the shared partial. The user modifies the form field values accordingly, clicks submit and the clone_create action is triggered saving that location to the database. Essentially, "cloning" is akin to editing a location then creating a new location upon form submit.

# new.html.erb
<h1>New Location</h1>
<%= render :partial => 'admin/shared/location_form', :locals => { :url => admin_data_provider_location_index_path } %>

# edit.html.erb
<h1>Edit Location</h1>
<%= render :partial => 'admin/shared/location_form', :locals => { :url => admin_data_provider_location_path(@location) } %>

# clone.html.erb
<h1>Clone Location</h1>
<%= render :partial => 'admin/shared/location_form', :locals => { :url => clone_create_admin_data_provider_location_index_path } %>

# _location_form.html.erb
<%= simple_form_for [:admin, @location], :url => url, :html => { :multipart => true } do |f| %>
<%= f.submit nil, :class => 'btn' %>
<% end %>

# locations_controller.rb
class Admin::DataProvider::LocationsController < AdminController
def new
@location = Location.new

def create
# Creates a location and saves it to the database.
render :new

def edit
@location = Location.find(params[:id])

def update
@location = Location.find(params[:id])
# Saves edited location field values back to same location in the database.
render :edit

def clone
# Same code body as edit.

def clone_create
# Same code body as create.

# routes.rb
namespace :admin do
namespace :data_provider do
resources :location, :controller => :locations, :exception => [:destroy] do
get 'clone', :on => member
post 'clone_create', :on => :collection

# rake routes | grep location
clone_admin_data_provider_location GET /admin/data_provider/location/:id/clone(.:format) admin/data_provider/locations#clone

clone_create_admin_data_provider_location_index POST /admin/data_provider/location/clone_create(.:format) admin/data_provider/locations#clone_create

edit_admin_data_provider_location GET /admin/data_provider/location/:id/edit(.:format) admin/data_provider/locations#edit

admin_data_provider_location PATCH PUT /admin/data_provider/location/:id(.:format) admin/data_provider/locations#update

new_admin_data_provider_location GET /admin/data_provider/location/new(.:format) admin/data_provider/locations#new

admin_data_provider_location_index POST /admin/data_provider/location(.:format) admin/data_provider/locations#create

The clone action behaves fine. For example, hitting the url /location/123/clone brings up the shared partial form filled with data exactly like hitting /location/123/edit. Great.

The issue comes when the clone form is submitted. The form POST request absolutely refuses to execute the clone_create action. The form posts to update every single time, no matter how I modify clone.html.erb or

<%= simple_form_for [:admin, @location], :url => url, :html => { :multipart => true } do |f| %>

This is some high level Rails magic happening here where Rails FORCES posting to a specific action regardless of what the developer code wants.

1) Why does the clone form keep posting to update when I obviously want it to post to clone_create?

2) How do I update the current code or what additional code do I have to write to ensure the clone form posts to clone_create?

Any reference links or resources to better help with my understanding would be helpful. Thanks.

Answer Source

I don't see that you need a specific clone_create method.

def clone
  @location = Location.find(params[:id]).dup
  render :new

Then you have (effectively) a new record which happens to have the same values as the old record, and can be processed by the standard create method.

Note that as long as you're showing an existing record, submit will always take you to the update action.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download