C*UD (Create, Update, Delete)

The previous sections of this chapter described how to do the Retrieve part of CRUD: pulling records from the database and formatting them as HTML. In this section, we will look at the other parts of CRUD: creating records, updating them, and deleting them.

We'll start with adding people, as this is the most likely way new data would make its way into the database: someone rings up Acme, and the member of staff adds their details to the database, such as name, email address, and phone number. They would probably want to attach a company record to the person at the same time, too.

Creating a Person

As with retrieve operations, we need two elements to implement person creation:

  1. Controller actions to manage displaying the form for creating a record, as well as performing operations on the model to add the record to the database.
  2. A view to display the interface, which enables the user to input the data for the new record.

Tackling the controller first, we can add a new action to app/controllers/people_controller.rb, which displays the form; and a create action, which manages adding the data to the database:

class PeopleController < ApplicationController
# ... other actions ...
# Only accept post requests to the new action; # redirect to index otherwise
verify :method => :post, :only => :create, :redirect_to => {:action => :index}
# Display a form to add a person, or attempt to save
# if data posted in the request.
def new
@page_title = 'Add a new person'
@person = Person.new
end
# Save submitted data to the database
def create
@person = Person.new(params[:person])
if @person.save
redirect_to :action => 'index'
else
@page_title = 'Add a new person'
render :action => 'create'
end
end
end

The verify method ensures that the create method only accepts POST (and not GET requests). Any non-POST requests get automatically redirected to the index action. This prevents malicious users sending GET requests to create new people in the database.

When the create action runs, an instance variable, @person, is created and populated with parameters from the request; specifically, those associated with :person. As in PHP, Rails parses POST parameters into a hash of name-value pairs, accessible via the params method. So a POST body like this:

day=today&name=ell

is parsed into the hash:

{ :day => 'today', :name => 'ell' }

Additionally, Rails understands the specially formatted fields in HTML forms with names of the form object[field]. POST parameters with a name formatted like this create a sub-hash within the main params hash. The key into the sub-hash is derived from the first part of the field name (object). In our case, a POST request like the following:

day=today&name=ell&person%5Bfirst_name%5D=bill&person%5Blast_name%5D=brum

is parsed into the hash:

{ :day => 'today', :name => 'ell', :person => { :first_name => 'bill', :last_name => 'brum' } }

Note

If you're wondering about the %5B and %5D parts of the POST parameters, these are the URL-encoded representations of the [ and] characters respectively. An<input> element like this:

<input type="text" name="person[first_name]" value="Bill" />

ends up sending a URL-encoded POST parameter like this:

person%5Bfirst_name%5D=bill

Within the controller, we can then retrieve just the POST parameters relating to the person by accessing the :person key inside params, which would give us { :first_name => 'bill', :last_name => 'brum' }.

Once @person has been instantiated, the action tries to save the input, which will return true or false. In cases where the save is successful, the redirect_to helper is used to send the client back to the index page (which lists all the people in the database).

Where the save fails, the default app/views/people/new.rhtml template is rendered. (Recall that if render is not explicitly called, the default is to call render :action => <action_name>, where<action_name> is the name of the action being executed.)

We'll create the form template next, used to insert the details for the person. To keep things simple, we'll start with a basic version of the form, which only displays the required fields for a person (first_name, last_name, email, gender):

<% form_for :person, @person, :url => {:action => 'create'} do |f| %>
<p><strong><label for="person_first_name">First name</label>:</strong><br />
<%= f.text_field :first_name %></p>
<p><strong><label for="person_last_name">Last name</label>:</strong><br />
<%= f.text_field :last_name %>
</p>
<p><strong><label for="person_email">Email address</label>:</strong><br />
<%= f.text_field :email %></p>
<p><strong><label for="person_gender">Gender</label>:</strong>
<%= f.select :gender, Person::GENDERS.keys %></p>
<p><%= submit_tag 'Save' %></p>
<% end %>

This template does the following:

  1. Uses the form_for method to create a form object for the Person model, using the @person instance to populate the fields of the form. As we are creating a new object, @person initially has blank fields. However, each time we POST to the create action, a new @person is created and populated with values from the request. So, if we are trying to save the new person and have validation errors, the object will contain the values we just set for each field. This means Rails can re-display the form and re-fill the fields with the values we first entered, and show this alongside the validation error messages.
  2. Creates the individual form fields using more Rails form helpers. Note that each helper is called as a method on the object returned by form_for (f), e.g.
    f.text_field :first_name
    

    The text_field helper creates a standard HTML <input> element, generating the name of the element from the name of the model and setting its value by calling the first_name method on the model instance (@person here). The resulting HTML is:

    <input id="person_first_name" name="person[first_name]" size="30" type="text" value="" />
    

    Note that the id attribute is set to the name of the model, followed by an underscore, then the name of the attribute. The for attributes on the <label> elements are formatted in the same way.

    You can specify extra HTML options by passing them to text_field, e.g.

    f.text_field :first_name, :size => 20, :class => 'myfield'
    

    The other form helper used in the form is select:

    f.select :gender, Person::GENDERS.keys
    

    select creates a <select> element, with options derived from a collection. In this case, we pass in the keys from the Person::GENDERS hash (i.e. ['M', 'F']), which yields:

    <select id="person_gender" name="person[gender]">
    <option value="M">M</option>
    <option value="F">F</option>
    </select>
    

    You can pass a hash of name => value pairs as the collection argument; but the select helper uses the name part of each pair to create the <option> value attribute, and uses the value part of each pair to create the <option> text. Passing the whole Person::GENDERS hash:

    f.select :gender, Person::GENDERS
    

    therefore, gives the wrong output (the full gender name is used for the value attributes on the <option> elements):

    <select id="person_gender" name="person[gender]">
    <option value="male">M</option>
    <option value="female">F</option>
    </select>
    

    Here is a case for making Gender a model in its own right: this would give us the flexibility to format the genders so that they are suitable for creating a drop-down. As it is, we will stick with the simple solution for now, using the short form of the gender for both the option values and text.

To test the new form, browse to http://localhost:3000/people/new. Try adding one or two people and check that they appear in the people index. Don't worry about displaying validation errors for the moment: we'll come to that shortly.

Refining with a Helper

Notice all the repeated HTML code in the create.rhtml template (highlighted below)?

<p><strong><label for="person_first_name">First name</label>:</strong><br />

<%= f.text_field :first_name %></p>
<p><strong><label for="person_last_name">Last name</label>:</strong><br />

<%= f.text_field :last_name %>
</p>

Time to add a custom helper! (Note that there is a minimal amount of HTML code to create here, so a helper is fine, rather than a partial.) We can make this an application-level helper, as we're likely to need it in other forms. We can also take the opportunity to use the helper to mark required fields with an asterisk:

module ApplicationHelper
# ... other helpers ...
# Format a label element for a form field.
#
# +options+ can include:
#
# [:required] If +true+, an asterisk is added to the label.
# [:field_name] If true, the for attribute on the label
# is set from +model+ + +field_name+;
# otherwise, for attribute is set from
# +model+ + lowercased and underscored +label_text+.
#
# Example call:
# label(:person, 'Email')
#
# Example output:
# <strong><label for="person_email">Email</label>:</strong>
def label(model, label_text, options={})
# Use the field_name option if supplied
field_name = options[:field_name]
field_name ||= label_text.gsub(' ', '_')
# The value for the for attribute.
label_for = (model.to_s + '_' + field_name).downcase
# The <label> tag
label_tag = content_tag('label', label_text, :for => label_for)
# Add an asterisk if :required option specified.
label_tag += '*' if options[:required]
# Finish off the label.
label_tag += ':'
# Wrap the <label> tag in a <strong> tag.
content_tag('strong', label_tag)
end
end

This is now ready for use in the form template, e.g.:

<p><%= label :person, 'First name', :required => true %><br />

<%= f.text_field :first_name %></p>
<p><%= label :person, 'Last name', :required => true %><br />

<%= f.text_field :last_name %></p>

Validation Errors

One of the beauties of the Model-View-Controller architecture is that the validation code we added to our models in Chapter 4 Working with Rails is still quietly doing its work in the background. If you attempt to add a person and leave one of the required fields blank, calling @person.save will return false, as the validation fails. Although, difficult to spot, Rails will also modify the view, wrapping any form element for which validation failed in a<div> element, e.g.:

<div class="fieldWithErrors"><input id="person_first_name" name="person[first_name]" size="30" type="text" value="" /></div>

By styling this<div> with some CSS (in public/stylesheets/base.css), we can highlight the fields with errors, putting a red border around them, e.g.

.fieldWithErrors {
border: 0.2em solid red;
display: table;
}

Try saving an invalid person record now: any fields with errors are surrounded by a red border. Note that this works best in Firefox; in Internet Explorer, the red border spans the width of the whole page, rather than hugging the input element as it does in Firefox.

Note

While automatic addition of a<div> is a cute trick, it does mean that the layout for forms built with the form helpers is somewhat limited:<div> elements appearing in the middle of your HTML can throw your format into unexpected shapes. This is why it's safest to put the label on one line and the input element after a linebreak (<br />) within the same paragraph. If you want more control over layout and want to avoid these automatic<div> elements, you can create input elements using the lower-level *_tag helpers, e.g. for a text field:

<%= text_field_tag 'person[email]', @person.email %>

(Note that there is no need to reference the form being built, f, as there is with the text_field helper.) If you take this approach, Rails will not wrap the field with a<div> if it throws a validation error; which means, you will have to manually highlight fields with errors too, perhaps with a custom helper.

The model validation rules defined in the previous chapter also set error messages when validation fails; we saw how to use these from the command line. We can get all the error messages for a model using another helper, error_messages_for:

<%= error_messages_for :person %>

This will display a block with a red frame, with errors shown as bullet points. Alternatively, we can get at the error messages (if any) for individual fields using the error_message_on method. For example, to place the error message for the first_name text field under the text input element:

<p><%= label :person, 'First name', :required => true %><br />
<%= f.text_field :first_name %>
<%= error_message_on :person, :first_name %></p>

Where an error message occurs, this helper adds a<div> to the view, with text set to the error message for the field, e.g. if validation of first_name fails this code will render:

<p><strong><label for="person_first_name">First name</label>*:</strong><br />
<div class="fieldWithErrors"><input id="person_first_name" name="person[first_name]" size="30" type="text" value="" /></div>
<div class="formError">Please enter a first name</div></p>

This<div> can easily be styled with CSS in public/stylesheets/base.css, e.g. to put error messages in slightly smaller, red text:

.formError {
color: red;
font-size: 0.9em;
}

We'll add the remaining error messages later, in the section Finishing Touches.

The Flash

Giving the user feedback about problems with their input is vital; but equally important is giving some feedback about actions that completed successfully. Currently, the user gets no feedback about whether their actions added a new person; they have to scan the list of people to see their new record.

What's needed is a short informational message indicating that the action was successful; this message needs to be carried from the action that adds the record to the view that is displayed next. However, we are currently using a redirect when the save is successful, back to the index view. The action we redirect to by default doesn't know anything about the previous action, due to the stateless nature of HTTP requests and responses.

The solution in other languages is to place the message into the client session, to maintain the client's state between the two requests. Indeed, Rails provides session classes, which provide the same functionality. However, for this particular use case (storing a short piece of data across two requests, which can be immediately discarded when it's been used), Rails provides a further convenience within sessions called the flash. Values stored in the flash have exactly the property that we need: they are set up in response to one request, and then available to the response of the next request (from the same client). After the second request, they are cleared out automatically. This means we can put a message into the flash in the create action, then display it in the index action, if the two occur in sequence. Rails manages clearing out the message once it's been displayed.

Adding a message to the flash in the create action is simple (highlighted below):

class PeopleController < ApplicationController
# ... more actions ...
def create
@person = Person.new(params[:person])
if request.post? and @person.save
flash[:notice] = 'Person added successfully'

redirect_to :action => 'index'
else
@page_title = 'Add a new person'
render :action => 'new'
end
end
end

The flash is a hash, associated with a client session. To set a value in this hash, you use the flash method to return the hash; then specify the key you want to set (:notice here) and its value ("Person added successfully").

The next step is to display this in the view. We don't need to do anything in the controller to get at the content of the flash, as it is accessible by default from every view template. We can put items from the flash wherever we want them, in any template (associated with a layout, action, or partial). In the Intranet application, we could need flash notices in any view, so the logical location for it is in the layout template for the whole application (app/views/layouts/application.rhtml):

<div id="content">
<% if flash[:notice] -%>
<p class="notice"><%= flash[:notice] %></p>
<% end -%>

<%= yield %>
</div>

The if...end ensures that a paragraph tag is only added if the flash has been set; the<p> tag itself is styled with class="notice", which we can define in the CSS file to show flash messages in green (public/stylesheets/base.css):

.notice {
color: green;
}

Try adding a new person: you should get your confirmation message displayed at the top of the page in green.

Note

The flash isn't restricted to containing just text: any Ruby object can be placed inside it. If you need to retain an object between actions (for whatever reason), it can also be used as a temporary store for that.

Finishing Touches

Putting all of the above together gives us the following template for app/views/people/new.rhtml:

<h1><%= @page_title %></h1>
<p>Required fields are marked with &quot;*&quot;.</p>
<% form_for :person, @person, :url => {:action => 'create'} do |f| %>
<p><%= label :person, 'Title' %> <%= f.text_field :title, :size => 8 %></p>
<p><%= label :person, 'First name', :required => true %><br />
<%= f.text_field :first_name %>
<%= error_message_on :person, :first_name %></p>
<p><%= label :person, 'Last name', :required => true %><br />
<%= f.text_field :last_name %>
<%= error_message_on :person, :last_name %></p>
<p><%= label :person, 'Job title' %>
<%= f.text_field :job_title %></p>
<p><%= label :person, 'Telephone' %> <%= f.text_field :telephone, :size => 16 %>
<%= label :person, 'Mobile', :field_name => 'mobile_phone' %>
<%= f.text_field :mobile_phone, :size => 16 %></p>
<p><%= label :person, 'Email address', :field_name => 'email', :required => true %><br />
<%= f.text_field :email %>
<%= error_message_on :person, :email %></p>
<p><%= label :person, 'Gender', :required => true %>
<%= f.select :gender, Person::GENDERS.keys %>
<%= error_message_on :person, :gender %></p>
<p><%= label :person, 'Date of birth' %>
<span id="person_date_of_birth">
<% this_year = Time.now.year -%>
<%= f.date_select :date_of_birth, :order => [:year, :month, :day], :include_blank => true, :end_year => (this_year - 100),
:start_year => this_year %>

</span></p>
<p><%= label :person, 'Keywords' %> <%= f.text_field :keywords %></p>
<p><%= label :person, 'Notes' %><br />
<%= f.text_area :notes %></p>

<p><%= submit_tag 'Save' %> | <%= link_to 'Cancel', :action => 'index' %></p>
<% end %>

Note that we've added all the required labels (marked with an asterisk where the field is required) and validation error notifications.

We've also used a couple of new helpers here (highlighted):

  • date_select creates a series of drop-downs (<select> elements) for selecting the separate elements of a date and time. The :order option specifies how to arrange the drop-downs; :start_year and :end_year specify the range of years to show. If :start_year is less than :end_year, the year<option> elements are sorted in descending order.

    The resulting <select> elements have specially formatted names, which Rails will re-compose into a single date-time string when the form is submitted. This string is used to set the date for the person's date of birth when the model is saved back to the database.

  • text_area creates an HTML<textarea> element.

A final finishing touch is to add a menu item linked to the create action (in app/views/layouts/application.rhtml):

...
<li><%= link_to 'People', :controller => 'people' %>
<ul>
<li><%= link_to 'Add a person', :controller => 'people',
:action => 'new' %></li>
</ul>

</li>
...

Updating a Person

Once records are in the contact database, we may still need to go back periodically and alter them. Fortunately, we can reuse a lot of the code and techniques from the previous new and create actions to accomplish this.

Firstly, we need edit and update actions to manage displaying the form and inserting the record:

class PeopleController < ApplicationController
# .. other actions ...
# Add update to the list of actions, which only
# accept post requests
verify :method => :post, :only => [:create, :update],
:redirect_to => {:action => :index}
def edit
@person = Person.find(params[:id])
@page_title = 'Editing ' + @person.full_name
end
def update
@person = Person.find(params[:id])
if @person.update_attributes(params[:person])
flash[:notice] = 'Person updated successfully'
redirect_to :action => 'index'
else
@page_title = 'Editing ' + @person.full_name
render :action => 'edit'
end
end
end

The edit action fetches a person's record from the database using a finder (the person's ID is passed in as part of the URL, in much the same way as it is for the show action in the PeopleController, and available from params) and creates @page_title from the person's name; then it renders the form for editing the person's details. The update action attempts to update the person's record using the request parameters (but only if the request is a POST, as set by the verify method). The update_attributes method updates all of the model instance's attributes, and then attempts to save the instance to the database, returning true (save successful) or false (save failed, normally due to validation errors).

Next, we need a template to display a form when someone edits an existing person's record. This will be virtually the same as the form for adding a person; plus, the text_field and other form helpers we used for the create form will automatically populate the form with the details in the retrieved Person instance (@person). However, the form will need to submit to a different URL (/people/update/X, where X is the person's ID, rather than to /people/create). The obvious answer to this is to put the form into a partial, called _form.rhtml; then turn the new.rhtml and edit.rhtml templates into wrappers around it, e.g. new.rhtml looks like this:

<% form_for :person, @person,
:url => {:action => 'create'} do |f| %>
<%= render :partial => 'form', :locals => {:f => f} %>
<% end %>

_form.rhtml contains all of the code taken out of this template, plus the title at the top of the page and the instruction about required fields—see the source code repository for the complete listing. One other point to note is that we now have to pass the f argument into the partial as a local, otherwise, the partial has no access to it.

The edit.rhtml template looks like this:

<% form_for :person, @person,
:url => {:action => 'update', :id => @person.id} do |f| %>
<%= render :partial => 'form', :locals => {:f => f} %>
<% end %>

Rather amazingly, that's it. The last step is to add links to app/views/people/index.rhtml view, which points to the update form for each person's record (the changed parts of the template are highlighted below):

<table>
<tr>
<th>Name</th>
<th>Actions</th>

</tr>
<% for person in @people -%>
<% full_name = person.last_name + ', ' + person.first_name -%>
<tr>
<td>
<%= link_to full_name, { :action => 'show', :id => person.id },
{ :title => "Show details", :class => "person_link" } %>
</td>
<td>
<%= link_to 'Edit', :action => 'edit', :id => person.id %>
</td>

</tr>
<% end -%>
</table>

Try clicking through few of the edit links from the index page to ensure that you can see the form populated correctly. Also, check that the validation still works correctly.

Opportunities for Refactoring

Rails provides a lot of helpers for common tasks, so you don't have to keep reinventing the wheel when writing views. However, your own application is likely to have chunks of code that occur in multiple places. Always keep an eye out for opportunities for refactoring this type of code, and make use of the framework to remove duplication: in Rails parlance, this is known colloquially as DRY-ing out your code (where DRY stands for "Don't Repeat Yourself"). In this section, we'll see a few places where we can DRY out our Intranet application.

Using Filters

Filters are a useful tool in Rails. They enable you to run some method before, after, or even around an action (like servlet filters in Java). This makes them ideal for implementing functionality like authentication and authorization (you can prevent an action being run, if a person is not logged in), and logging (e.g. you could log the parameters passed to one or more of the actions in a controller).

However, they are also useful for more mundane tasks, like setting the stage for an action by creating instance variables before it runs. This means we can take repeated code out of actions and put it into a controller method, then use a filter to run that method before each action wherever that code is needed.

In the PeopleController we have three actions, which each run the same piece of code (highlighted):

def show
@person = Person.find(params[:id])

...
end
...
def edit
@person = Person.find(params[:id])

...
end
...
def update
@person = Person.find(params[:id])

...
end

The first step in turning this into a before filter is to add a private method to the bottom of the PeopleController class definition, which does the preparation for the action:

private
def get_person
@person = Person.find(params[:id])
end

This method is scoped as private so that it is not publicly available; without this keyword, the default is for the method to be public, which would actually expose it as a controller action. In addition, the method is placed at the bottom of the class because the private keyword will apply to any method definitions following it (unless those methods are explicitly marked as public or protected). This keeps the action method definitions public, while protecting methods like this one, which are only intended for internal use by the controller.

Next, strip out the lines from the show, edit, and update actions (the ones highlighted above), which this method replaces.

Finally, add a before_filter definition to the PeopleController class definition:

class PeopleController < ApplicationController
before_filter :get_person, :only => [:show, :update, :edit]

# ... other methods ...
end

Here, the :only option specifies that actions in front of we want the filter to run. An :except option is also available, so we can rewrite the above as:

class PeopleController < ApplicationController
before_filter :get_person, :except => [:index, :new, :create]

# ... other methods ...
end

If neither an :only nor :except option is used, the filter runs before all controller actions.

If you call before_filter multiple times when defining a controller, each of the applicable filters will be applied before an action is run.

The sister after_filter method enables you to call some method after action invocations. For example, the following after_filter globally replaces the word "People" in the output from a template with the word "Fools":

class PeopleController < ApplicationController
after_filter :replace_with_fools
# ... other methods ...
private
def replace_with_fools
response.body.gsub!(/People/, 'Fools')
end
end

Not very useful, but mildly amusing. The important point is that the response body is accessible via the response.body method call; the filter can either change the response body in place (as it is done here by gsub!), or modify and then reset it using response.body=. You can also set response headers or otherwise modify the response with an after_filter, perhaps based on properties of the output from a template. More practical uses of an after_filter might be to write a hard copy of a generated response to a debug log, or to email an administrator if certain phrases occur in generated content.

Creating Application-Level Controller Methods

When we perform the create and update actions, adding a new record or modifying an existing one respectively, both actions set a message in the flash and redirect back to the index action, e.g. from create:

def create
@person = Person.new(params[:person])
if request.post? and @person.save
flash[:notice] = 'Person added successfully'
redirect_to :action => 'index'

else
@page_title = "Add a new person"
render :action => 'new'
end
end

There is an opportunity here to slim down our code. But rather than create a method on the controller itself, we'll create it on the ApplicationController (in app/controller/application.rb), so we can use this functionality in any controller:

class ApplicationController < ActionController::Base
session :session_key => '_Intranet_session_id'
# Set +message+ under the :notice key in the flash,
# then redirect to the index action.
private
def redirect_to_index(message=nil)
flash[:notice] = message
redirect_to :action => 'index'
end
end

Then rewrite the create action to use it:

def create
@person = Person.new(params[:person])
if request.post? and @person.save
redirect_to_index 'Person added successfully'

else
@page_title = "Add a new person"
render :action => 'new'
end
end

And modify the update action too:

def update
if @person.update_attributes(params[:person])
redirect_to_index 'Person updated successfully'
else
@page_title = "Editing " + @person.full_name
render :action => 'edit'
end
end

We can use the redirect_to_index method in any controller, inside any action where we want to set the flash and redirect back to the controller's index. Small adjustments like this can radically improve the readability and coherence of your code.

Deleting a Person

Adding a delete action is pretty trivial: the scaffold does it in a few lines of code. However, in the scaffold, the code for doing the deletion (the destroy action) is ugly, for two reasons:

  1. Confirmation of the action is performed through JavaScript (when you click on a Destroy link). If a user has JavaScript disabled, they don't get a chance to confirm the deletion, and it goes ahead, recklessly.
  2. Early versions of the scaffold put the destroy action behind a link (not behind a form). This works OK, but doesn't account for accelerator software (like the Google Web Accelerator). Accelerator software works with the browser to pre-fetch possible future pages by following links on the current page; then, when one of the possible pages is visited, it loads more quickly, as it is already cached locally. Unfortunately, some Rails applications, when used with accelerator software, ended up having their database emptied: the accelerator fetched all the Destroy links, disregarding the JavaScript confirmation prompts, and destroying all the records in the database!

    The solution in newer versions of the scaffold is to use a weird JavaScript hack, which, when a Destroy link is clicked, turns the link into a form with method="post" and submits it; then puts a filter on the destroy action so it only accepts POST requests. This is bad. While it solves the accelerator problem, it completely breaks the links for browsers without JavaScript support (e.g. screen readers, Lynx).

A simple and more accessible approach is to place a confirm step between the user clicking on the Destroy link and the destroy action being executed. The confirm step displays a form with method set to "post" and a big Confirm button: the record only gets deleted if the user clicks on the button. This prevents the accelerator problem, gets around ugly scaffold-style hacks, and makes Destroy links usable in browsers without JavaScript.

And it's very simple to implement in Rails. First, add delete and confirm actions to PeopleController:

class PeopleController < ApplicationController
before_filter :get_person, :only => [:show, :edit, :update, :confirm, :delete]
verify :method => :post, :only => [:create, :update, :delete],
:redirect_to => {:action => :index}
# ... other methods ...
def confirm
@page_title = "Do you really want to delete 
#{@person.full_name}?"
end
def delete
if 'yes' == params[:confirm]
@person.destroy
redirect_to_index 'Person deleted successfully'
end
end
# ... private methods ...
end

Note that the confirm and delete actions are added to the before filter's :only option, so that the @person variable is populated with the record to be deleted (see the earlier section Using Filters). The delete action will only perform the deletion in response to a POST request (set using the verify method), which must contain a parameter'confirm' with the value'yes'.

Next, create a view to display the confirmation form (in app/views/people/confirm.rhtml), which is shown when the confirm action is first called:

<h1><%= @page_title %></h1>
<% form_tag :action => 'delete', :id => @person.id do %>
<%= hidden_field_tag 'confirm', 'yes' %>
<p><%= submit_tag 'Yes' %> |
<%= link_to 'Cancel', request.referer %></p>
<% end %>

We've used the simple form_tag helper here, which generates a plain HTML form from URL parameters. Clicking the Confirm button submits a'confirm=yes' parameter via a POST request to the right action and ID. That's why the hidden_field_tag helper is used to add this form element:

<input type="hidden" name="confirm" value="yes" />

The other new feature we haven't seen before is use of request.referer to set the URL of the Cancel button. The referer method returns the URL of the page the user was on before visiting this one, so it's a useful way to send a user back to where they came from, canceling the current action.

Finally, add another link into the people index view for each person (app/views/people/index.rhtml) to delete a person's record:

<td>
<%= link_to 'Edit', :action => 'update', :id => person.id %> |
<%= link_to 'Delete', :action => 'confirm', :id => person.id %>
</td>

Note

Is deletion a good idea?

One other consideration is whether to allow deletions at all. Deleting a record may destroy valuable historical data about interactions with a customer you are perhaps no longer dealing with, or who has gone out of business.

It might be better to instead disable a person's record, but keep a copy of it in the database, and then filter out inactive records from the person list unless the user specifically requests to see them. A simple implementation may be to add a Boolean active column to the Person model, which could be used to filter the people index view. We aren't going to go down this route with Intranet, for the sake of simplicity.

Adding Edit and Delete Links to a Person's Profile

This is a trivial change, but it makes a big difference to usability. Add two links to the show.rhtml template for a person: one to edit the person, and one to delete their record:

<p><%= link_to 'Edit', :action => 'edit', :id => @person.id %> |
<%= link_to 'Delete', :action => 'confirm', :id => @person.id %></p>

Now, you can skip around the application to your heart's content.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.214.27