In many applications, you’ll find that your edit and new views are basically the same. A common annoyance can be that if you change the model and add a new attribute, you are forced to do the change in both views, edit and new.
This violates the principle of “Don’t Repeat Yourself”, and Rails can help you solve it by letting you easily reuse the edit view as the new view of a model.
First make a new variable that holds the “mode” of the view you are in. The two modes might be called “edit” and “new.” You want your form to post to specific controller methods depending on the mode.
Add an if branch to the view.
<% if @mode == "edit" -%>
<%= start_form_tag :action=> "edit", :id => @member.id %>
<%= hidden_field "member", "id" %>
<% else -%>
<%= start_form_tag :action=> "new" %>
<% end -%>
Then create methods in the controller that can handle these modes:
def edit
@member = Member.find(@params['id'])
@mode = "edit"
# will use the "edit.rhtml" template by default
end
def new
@member = Member.new()
@mode = "new"
render_action "edit"
end
The tricky part is the render_action “edit” one in the new method. This call uses the “edit.rhtml” template to render the view instead of the default “new.rhtml”. and therefore makes it possible to reuse the template.
Thanks to noradio on #rubyonrails for pointing this out, and clarifications by msp here.
Here’s how I do it (with thanks to you for the original idea). This doesn’t require the @mode variable.
In my edit.rhtml I use:
<% if @params['action'] == "new" %>
<h1>New part</h1>
<%= start_form_tag :action=> "create" %>
<% else %>
<h1>Edit part</h1>
<%= start_form_tag :action=> "update", :id => @part.id %>
<%= hidden_field "part", "id" %>
<% end %>
In the controller I have:
def new
@part = Part.new
render_action 'edit'
end
def create
@part = Part.new(@params['part'])
if @part.save
flash['notice'] = 'Part was successfully created.'
redirect_to :action => 'list'
else
render_action 'edit'
end
end
def edit
@part = Part.find(@params['id'])
end
def update
@part = Part.find(@params['part']['id'])
if @part.update_attributes(@params['part'])
flash['notice'] = 'Part was successfully updated.'
redirect_to :action => 'show', :id => @part.id
else
render_action 'edit'
end
end
Thanks, JJ
def auto_form
action = case controller.action_name
when "new" then "create"
when "edit" then "update"
else controller.action_name
end
start_form_tag :controller => controller.controller_name, :action => action
end
Now you can start your forms with:
<%= auto_form %>
Which seems a lot cleaner to me. It’s also possible to make use of \ActiveRecord::Base#new_record? ( http://rails.rubyonrails.com/classes/ActiveRecord/Base.html#M000411 ) to determine if this is a new object.
<% if @params['action'] "new" %>
with
<% if @params['action'] “new” or
@params[‘action’] == “create” %>
Is there any reason not to merge “new” and “create” into one controller? (Likewise with “edit” and “update”). If you do that, you can use <= form_tag > with no arguments at all, and it will generate the url for the current controller, so you don’t have to detect which controller you’re in.
—tyler kiley
Nope that works just fine too.
—adam sanderson
What do you think about this:
Controller:
class UsersController < ApplicationController
def admin
if params[:id]
@user = User.find(params[:id])
@title = 'Edit user'
else
@user = User.new
@title = 'New user'
end
if @request.post?
@user.attributes = params[:user]
if @user.save
redirect_to :action => 'list'
end
end
end
end
admin.rhtml
<h1><%= @title %></h1>
<% form_for :user, User.new do |u| %>
<p><strong>Username:</strong><br />
<%= u.text_field :username %></p>
<p><strong>Password:</strong><br />
<%= u.password_field :password %></p>
<%= submit_tag %>
<% end %>
—Jules Jacobs