In this chapter, we will complete the REST actions for the Users resource (Table 7.1) by adding edit, update, index, and destroy actions. We’ll start by giving users the ability to update their profiles, which will also provide a natural opportunity to enforce an authorization model (made possible by the authentication code in Chapter 8). Then we’ll make a listing of all users (also requiring authentication), which will motivate the introduction of sample data and pagination. Finally, we’ll add the ability to destroy users, wiping them clear from the database. Since we can’t allow just any user to have such dangerous powers, we’ll take care to create a privileged class of administrative users authorized to delete other users.
The pattern for editing user information closely parallels that for creating new users (Chapter 7). Instead of a new action rendering a view for new users, we have an edit action rendering a view to edit users; instead of create responding to a POST request, we have an update action responding to a PATCH request (Box 3.2). The biggest difference is that, while anyone can sign up, only the current user should be able to update their information. The authentication machinery from Chapter 8 will allow us to use a before filter to ensure that this is the case.
To get started, let’s start work on an updating-users topic branch:
$ git checkout master
$ git checkout -b updating-users
We start with the edit form, whose mockup appears in Figure 9.1.1 To turn the mockup in Figure 9.1 into a working page, we need to fill in both the Users controller edit action and the user edit view. We start with the edit action, which requires pulling the relevant user out of the database. Note from Table 7.1 that the proper URL for a user’s edit page is /users/1/edit (assuming the user’s id is 1). Recall that the id of the user is available in the params[:id] variable, which means that we can find the user with the code in Listing 9.1.

edit action. app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
log_in @user
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
def edit
@user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
The corresponding user edit view (which you will have to create by hand) is shown in Listing 9.2. Note how closely this resembles the new user view from Listing 7.13; the large overlap suggests factoring the repeated code into a partial, which is left as an exercise (Section 9.6).
app/views/users/edit.html.erb
<% provide(:title, "Edit user") %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages' %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation, "Confirmation" %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit "Save changes", class: "btn btn-primary" %>
<% end %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="http://gravatar.com/emails" target="_blank">change</a>
</div>
</div>
</div>
Here we have reused the shared error_messages partial introduced in Section 7.3.3. By the way, the use of target="_blank" in the Gravatar link is a neat trick to get the browser to open the page in a new window or tab, which is convenient behavior when linking to a third-party site.
With the @user instance variable from Listing 9.1, the edit page should render properly, as shown in Figure 9.2. The “Name” and “Email” fields in Figure 9.2 also shows how Rails automatically pre-fills the Name and Email fields using the attributes of the existing @user variable.

Looking at the HTML source for Figure 9.2, we see a form tag as expected, as in Listing 9.3 (slight details may differ).
<form accept-charset="UTF-8" action="/users/1" class="edit_user"
id="edit_user_1" method="post">
<input name="_method" type="hidden" value="patch" />
.
.
.
</form>
Note here the hidden input field
<input name="_method" type="hidden" value="patch" />
Since web browsers can’t natively send PATCH requests (as required by the REST conventions from Table 7.1), Rails fakes it with a POST request and a hidden input field.2
There’s another subtlety to address here: the code form_for(@user) in Listing 9.2 is exactly the same as the code in Listing 7.13—so how does Rails know to use a POST request for new users and a PATCH for editing users? The answer is that it is possible to tell whether a user is new or already exists in the database via Active Record’s new_record? boolean method:
$ rails console
>> User.new.new_record?
=> true
>> User.first.new_record?
=> false
When constructing a form using form_for(@user), Rails uses POST if @user.new_record? is true and PATCH if it is false.
As a final touch, we’ll fill in the URL of the settings link in the site navigation. This is easy using the named route edit_user_path from Table 7.1, together with the handy current_user helper method defined in Listing 8.36:
<%= link_to "Settings", edit_user_path(current_user) %>
The full application code appears in Listing 9.4).
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", root_path, id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Help", help_path %></li>
<% if logged_in? %>
<li><%= link_to "Users", '#' %></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Account <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><%= link_to "Profile", current_user %></li>
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
<li class="divider"></li>
<li>
<%= link_to "Log out", logout_path, method: "delete" %>
</li>
</ul>
</li>
<% else %>
<li><%= link_to "Log in", login_path %></li>
<% end %>
</ul>
</nav>
</div>
</header>
In this section we’ll handle unsuccessful edits, following similar ideas to unsuccessful signups (Section 7.3). We start by creating an update action, which uses update_attributes (Section 6.1.5) to update the user based on the submitted params hash, as shown in Listing 9.5. With invalid information, the update attempt returns false, so the else branch renders the edit page. We’ve seen this pattern before; the structure closely parallels the first version of the create action (Listing 7.16).
update action. app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
log_in @user
flash[:success] = "Welcome to the Sample App!"
redirect_to @user
else
render 'new'
end
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
if @user.update_attributes(user_params)
# Handle a successful update.
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
end
Note the use of user_params in the call to update_attributes, which uses strong parameters to prevent mass assignment vulnerability (as described in Section 7.3.2).
Because of the existing User model validations and the error-messages partial in Listing 9.2, submission of invalid information results in helpful error messages (Figure 9.3).

We left Section 9.1.2 with a working edit form. Following the testing guidelines from Box 3.3, we’ll now write an integration test to catch any regressions. Our first step is to generate an integration test as usual:
$ rails generate integration_test users_edit
invoke test_unit
create test/integration/users_edit_test.rb
Then we’ll write a simple test of an unsuccessful edit, as shown in Listing 9.6. The test in Listing 9.6 checks for the correct behavior by verifying that the edit template is rendered after getting the edit page and re-rendered upon submission of invalid information. Note the use of the patch method to issue a PATCH request, which follows the same pattern as get, post, and delete.
test/integration/users_edit_test.rb
require 'test_helper'
class UsersEditTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "unsuccessful edit" do
get edit_user_path(@user)
assert_template 'users/edit'
patch user_path(@user), user: { name: "",
email: "foo@invalid",
password: "foo",
password_confirmation: "bar" }
assert_template 'users/edit'
end
end
At this point, the test suite should still be green:
$ bundle exec rake test
Now it’s time to get the edit form to work. Editing the profile images is already functional since we’ve outsourced image upload to the Gravatar website; we can edit Gravatars by clicking on the “change” link from Figure 9.2, as shown in Figure 9.4. Let’s get the rest of the user edit functionality working as well.
As you get more comfortable with testing, you might find that it’s useful to write integration tests before writing the application code instead of after. In this context, such tests are sometimes known as acceptance tests, since they determine when a particular feature should be accepted as complete. To see how this works, we’ll complete the user edit feature using test-driven development.
We’ll test for the correct behavior of updating users by writing a test similar to the one shown in Listing 9.6, only this time we’ll submit valid information. Then we’ll check for a nonempty flash message and a successful redirect to the profile page, while also verifying that the user’s information correctly changed in the database. The result appears in Listing 9.8. Note that the password and confirmation in Listing 9.8 are blank, which is convenient for users who don’t want to update their passwords every time they update their names or email addresses. Note also the use of @user.reload (first seen in Section 6.1.5) to reload the user’s values from the database and confirm that they were successfully updated. (This is the kind of detail you could easily forget initially, which is why acceptance testing (and TDD generally) require a certain level of experience to be effective.)
test/integration/users_edit_test.rb
require 'test_helper'
class UsersEditTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
.
.
.
test "successful edit" do
get edit_user_path(@user)
assert_template 'users/edit'
name = "Foo Bar"
email = "foo@bar.com"
patch user_path(@user), user: { name: name,
email: email,
password: "",
password_confirmation: "" }
assert_not flash.empty?
assert_redirected_to @user
@user.reload
assert_equal name, @user.name
assert_equal email, @user.email
end
end
The update action needed to get the tests in Listing 9.8 to pass is similar to the final form of the create action (Listing 8.22), as seen in Listing 9.9.
update action. red app/controllers/users_controller.rb
class UsersController < ApplicationController
.
.
.
def update
@user = User.find(params[:id])
if @user.update_attributes(user_params)
flash[:success] = "Profile updated"
redirect_to @user
else
render 'edit'
end
end
.
.
.
end
As indicated in the caption to Listing 9.9, the test suite is still red, which is the result of the password length validation (Listing 6.39) failing due to the empty password and confirmation in Listing 9.8. To get the tests to green, we need to make an exception to the password validation if the password is empty. We can do this by passing the allow_nil: true option to validates, as seen in Listing 9.10.
app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :remember_token
before_save { self.email = email.downcase }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true, length: { maximum: 255 },
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 6 }, allow_nil: true
.
.
.
end
In case you’re worried that Listing 9.10 might allow new users to sign up with empty passwords, recall from Section 6.3.3 that has_secure_password includes a separate presence validation that specifically catches nil passwords. (Because nil passwords now bypass the main presence validation but are still caught by has_secure_password, this also fixes the duplicate error message mentioned in Section 7.3.3.)
With the code in this section, the user edit page should be working (Figure 9.5), as you can double-check by re-running the test suite, which should now be green:
$ bundle exec rake test

In this section, we’ll add the penultimate user action, the index action, which is designed to display all the users instead of just one. Along the way, we’ll learn how to seed the database with sample users and how to paginate the user output so that the index page can scale up to display a potentially large number of users. A mockup of the result—users, pagination links, and a “Users” navigation link—appears in Figure 9.8.6 In Section 9.4, we’ll add an administrative interface to the users index so that users can also be destroyed.

To get started with the users index, we’ll first implement a security model. Although we’ll keep individual user show pages visible to all site visitors, the user index will be restricted to logged-in users so that there’s a limit to how much unregistered users can see by default.7
To protect the index page from unauthorized access, we’ll first add a short test to verify that the index action is redirected properly (Listing 9.31).
index action redirect. red test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
def setup
@user = users(:michael)
@other_user = users(:archer)
end
test "should redirect index when not logged in" do
get :index
assert_redirected_to login_url
end
.
.
.
end
Then we just need to add an index action and include it in the list of actions protected by the logged_in_user before filter (Listing 9.32).
index action. green app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update]
before_action :correct_user, only: [:edit, :update]
def index
end
def show
@user = User.find(params[:id])
end
.
.
.
end
To display the users themselves, we need to make a variable containing all the site’s users and then render each one by iterating through them in the index view. As you may recall from the corresponding action in the toy app (Listing 2.5), we can use User.all to pull all the users out of the database, assigning them to an @users instance variable for use in the view, as seen in Listing 9.33. (If displaying all the users at once seems like a bad idea, you’re right, and we’ll remove this blemish in Section 9.3.3.)
index action. app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update]
.
.
.
def index
@users = User.all
end
.
.
.
end
To make the actual index page, we’ll make a view (whose file you’ll have to create) that iterates through the users and wraps each one in an li tag. We do this with the each method, displaying each user’s Gravatar and name, while wrapping the whole thing in a ul tag (Listing 9.34).
app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>
<ul class="users">
<% @users.each do |user| %>
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
<% end %>
</ul>
The code in Listing 9.34 uses the result of Listing 7.31 from Section 7.7, which allows us to pass an option to the Gravatar helper specifying a size other than the default. If you didn’t do that exercise, update your Users helper file with the contents of Listing 7.31 before proceeding.
Let’s also add a little CSS (or, rather, SCSS) for style (Listing 9.35).
app/assets/stylesheets/custom.css.scss
.
.
.
/* Users index */
.users {
list-style: none;
margin: 0;
li {
overflow: auto;
padding: 10px 0;
border-bottom: 1px solid $gray-lighter;
}
}
Finally, we’ll add the URL to the users link in the site’s navigation header using users_path, thereby using the last of the unused named routes in Table 7.1. The result appears in Listing 9.36.
app/views/layouts/_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
<div class="container">
<%= link_to "sample app", root_path, id: "logo" %>
<nav>
<ul class="nav navbar-nav navbar-right">
<li><%= link_to "Home", root_path %></li>
<li><%= link_to "Help", help_path %></li>
<% if logged_in? %>
<li><%= link_to "Users", users_path %></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
Account <b class="caret"></b>
</a>
<ul class="dropdown-menu">
<li><%= link_to "Profile", current_user %></li>
<li><%= link_to "Settings", edit_user_path(current_user) %></li>
<li class="divider"></li>
<li>
<%= link_to "Log out", logout_path, method: "delete" %>
</li>
</ul>
</li>
<% else %>
<li><%= link_to "Log in", login_path %></li>
<% end %>
</ul>
</nav>
</div>
</header>
With that, the users index is fully functional, with all tests green:
$ bundle exec rake test
On the other hand, as seen in Figure 9.9, it is a bit…lonely. Let’s remedy this sad situation.

In this section, we’ll give our lonely sample user some company. Of course, to create enough users to make a decent users index, we could use our web browser to visit the signup page and make the new users one by one, but a far better solution is to use Ruby (and Rake) to make the users for us.
First, we’ll add the Faker gem to the Gemfile, which will allow us to make sample users with semi-realistic names and email addresses (Listing 9.38). (Ordinarily, you’d probably want to restrict the faker gem to a development environment, but in the case of the sample app we’ll be using it on our production site as well (Section 9.5).)
Gemfile.
source 'https://rubygems.org'
gem 'rails', '4.2.2'
gem 'bcrypt', '3.1.7'
gem 'faker', '1.4.2'
.
.
.
Then install as usual:
$ bundle install
Next, we’ll add a Rake task to seed the database with sample users, for which Rails uses the standard location db/seeds.rb. The result appears in Listing 9.39. (The code in Listing 9.39 is a bit advanced, so don’t worry too much about the details.)
db/seeds.rb
User.create!(name: "Example User",
email: "example@railstutorial.org",
password: "foobar",
password_confirmation: "foobar")
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}@railstutorial.org"
password = "password"
User.create!(name: name,
email: email,
password: password,
password_confirmation: password)
end
The code in Listing 9.39 creates an example user with name and email address replicating our previous one, and then makes 99 more. The create! method is just like the create method, except it raises an exception (Section 6.1.4) for an invalid user rather than returning false. This behavior makes debugging easier by avoiding silent errors.
With the code as in Listing 9.39, we can reset the database and then invoke the Rake task using db:seed:8
$ bundle exec rake db:migrate:reset
$ bundle exec rake db:seed
Seeding the database can be slow, and on some systems could take up to a few minutes. Also, some readers have reported that they are unable to run the reset command if the Rails server is running, so you may have to stop the server first before proceeding.
After running the db:seed Rake task, our application has 100 sample users. As seen in Figure 9.10, I’ve taken the liberty of associating the first few sample addresses with Gravatars so that they’re not all the default Gravatar image. (You may have to restart the webserver at this point.)

Our original user doesn’t suffer from loneliness any more, but now we have the opposite problem: our user has too many companions, and they all appear on the same page. Right now there are a hundred, which is already a reasonably large number, and on a real site it could be thousands. The solution is to paginate the users, so that (for example) only 30 show up on a page at any one time.
There are several pagination methods in Rails; we’ll use one of the simplest and most robust, called will_paginate. To use it, we need to include both the will_paginate gem and bootstrap-will_paginate, which configures will_paginate to use Bootstrap’s pagination styles. The updated Gemfile appears in Listing 9.40.
will_paginate in the Gemfile.
source 'https://rubygems.org'
gem 'rails', '4.2.2'
gem 'bcrypt', '3.1.7'
gem 'faker', '1.4.2'
gem 'will_paginate', '3.0.7'
gem 'bootstrap-will_paginate', '0.0.10'
.
.
.
Then run bundle install:
$ bundle install
You should also restart the web server to ensure that the new gems are loaded properly.
To get pagination working, we need to add some code to the index view telling Rails to paginate the users, and we need to replace User.all in the index action with an object that knows about pagination. We’ll start by adding the special will_paginate method in the view (Listing 9.41); we’ll see in a moment why the code appears both above and below the user list.
app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<% @users.each do |user| %>
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
<% end %>
</ul>
<%= will_paginate %>
The will_paginate method is a little magical; inside a users view, it automatically looks for an @users object, and then displays pagination links to access other pages. The view in Listing 9.41 doesn’t work yet, though, because currently @users contains the results of User.all (Listing 9.33), whereas will_paginate requires that we paginate the results explicitly using the paginate method:
$ rails console
>> User.paginate(page: 1)
User Load (1.5ms) SELECT "users".* FROM "users" LIMIT 30 OFFSET 0
(1.7ms) SELECT COUNT(*) FROM "users"
=> #<ActiveRecord::Relation [#<User id: 1,...
Note that paginate takes a hash argument with key :page and value equal to the page requested. User.paginate pulls the users out of the database one chunk at a time (30 by default), based on the :page parameter. So, for example, page 1 is users 1–30, page 2 is users 31–60, etc. If page is nil, paginate simply returns the first page.
Using the paginate method, we can paginate the users in the sample application by using paginate in place of all in the index action (Listing 9.42). Here the page parameter comes from params[:page], which is generated automatically by will_paginate.
index action. app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update]
.
.
.
def index
@users = User.paginate(page: params[:page])
end
.
.
.
end
The users index page should now be working, appearing as in Figure 9.11. (On some systems, you may have to restart the Rails server at this point.) Because we included will_paginate both above and below the user list, the pagination links appear in both places.

If you now click on either the 2 link or Next link, you’ll get the second page of results, as shown in Figure 9.12.

Now that our users index page is working, we’ll write a lightweight test for it, including a minimal test for the pagination from Section 9.3.3. The idea is to log in, visit the index path, verify the first page of users is present, and then confirm that pagination is present on the page. For these last two steps to work, we need to have enough users in the test database to invoke pagination, i.e., more than 30.
We created a second user in the fixtures in Listing 9.20, but 30 or so more users is a lot to create by hand. Luckily, as we’ve seen with the user fixture’s password_digest attribute, fixture files support embedded Ruby, which means we can create 30 additional users as shown in Listing 9.43. (Listing 9.43 also creates a couple of other named users for future reference.)
test/fixtures/users.yml
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
archer:
name: Sterling Archer
email: duchess@example.gov
password_digest: <%= User.digest('password') %>
lana:
name: Lana Kane
email: hands@example.gov
password_digest: <%= User.digest('password') %>
malory:
name: Malory Archer
email: boss@example.gov
password_digest: <%= User.digest('password') %>
<% 30.times do |n| %>
user_<%= n %>:
name: <%= "User #{n}" %>
email: <%= "user-#{n}@example.com" %>
password_digest: <%= User.digest('password') %>
<% end %>
With the fixtures defined in Listing 9.43, we’re ready to write a test of the users index. First we generate the relevant test:
$ rails generate integration_test users_index
invoke test_unit
create test/integration/users_index_test.rb
The test itself involves checking for a div with the required pagination class and verifying that the first page of users is present. The result appears in Listing 9.44.
test/integration/users_index_test.rb
require 'test_helper'
class UsersIndexTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "index including pagination" do
log_in_as(@user)
get users_path
assert_template 'users/index'
assert_select 'div.pagination'
User.paginate(page: 1).each do |user|
assert_select 'a[href=?]', user_path(user), text: user.name
end
end
end
The result should be a green test suite:
$ bundle exec rake test
The paginated users index is now complete, but there’s one improvement I can’t resist including: Rails has some incredibly slick tools for making compact views, and in this section we’ll refactor the index page to use them. Because our code is well-tested, we can refactor with confidence, assured that we are unlikely to break our site’s functionality.
The first step in our refactoring is to replace the user li from Listing 9.41 with a render call (Listing 9.46).
app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<% @users.each do |user| %>
<%= render user %>
<% end %>
</ul>
<%= will_paginate %>
Here we call render not on a string with the name of a partial, but rather on a user variable of class User;9 in this context, Rails automatically looks for a partial called _user.html.erb, which we must create (Listing 9.47).
app/views/users/_user.html.erb
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
This is a definite improvement, but we can do even better: we can call render directly on the @users variable (Listing 9.48).
app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>
<%= will_paginate %>
<ul class="users">
<%= render @users %>
</ul>
<%= will_paginate %>
Here Rails infers that @users is a list of User objects; moreover, when called with a collection of users, Rails automatically iterates through them and renders each one with the _user.html.erb partial. The result is the impressively compact code in Listing 9.48.
As with any refactoring, you should verify that the test suite is still green after changing the application code:
$ bundle exec rake test
Now that the users index is complete, there’s only one canonical REST action left: destroy. In this section, we’ll add links to delete users, as mocked up in Figure 9.13, and define the destroy action necessary to accomplish the deletion. But first, we’ll create the class of administrative users, or admins, authorized to do so.

We will identify privileged administrative users with a boolean admin attribute in the User model, which will lead automatically to an admin? boolean method to test for admin status. The resulting data model appears in Figure 9.14.
admin boolean attribute.
As usual, we add the admin attribute with a migration, indicating the boolean type on the command line:
$ rails generate migration add_admin_to_users admin:boolean
The migration adds the admin column to the users table, as shown in Listing 9.50. Note that we’ve added the argument default: false to add_column in Listing 9.50, which means that users will not be administrators by default. (Without the default: false argument, admin will be nil by default, which is still false, so this step is not strictly necessary. It is more explicit, though, and communicates our intentions more clearly both to Rails and to readers of our code.)
admin attribute to users. db/migrate/[timestamp]_add_admin_to_users.rb
class AddAdminToUsers < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, default: false
end
end
Next, we migrate as usual:
$ bundle exec rake db:migrate
As expected, Rails figures out the boolean nature of the admin attribute and automatically adds the question-mark method admin?:
$ rails console --sandbox
>> user = User.first
>> user.admin?
=> false
>> user.toggle!(:admin)
=> true
>> user.admin?
=> true
Here we’ve used the toggle! method to flip the admin attribute from false to true.
As a final step, let’s update our seed data to make the first user an admin by default (Listing 9.51).
db/seeds.rb
User.create!(name: "Example User",
email: "example@railstutorial.org",
password: "foobar",
password_confirmation: "foobar",
admin: true)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}@railstutorial.org"
password = "password"
User.create!(name: name,
email: email,
password: password,
password_confirmation: password)
end
Then reset the database:
$ bundle exec rake db:migrate:reset
$ bundle exec rake db:seed
You might have noticed that Listing 9.51 makes the user an admin by including admin: true in the initialization hash. This underscores the danger of exposing our objects to the wild Web: if we simply passed an initialization hash in from an arbitrary web request, a malicious user could send a PATCH request as follows:10
patch /users/17?admin=1
This request would make user 17 an admin, which would be a potentially serious security breach.
Because of this danger, it is essential that we only update attributes that are safe to edit through the web. As noted in Section 7.3.2, this is accomplished using strong parameters by calling require and permit on the params hash:
def user_params
params.require(:user).permit(:name, :email, :password,
:password_confirmation)
end
Note in particular that admin is not in the list of permitted attributes. This is what prevents arbitrary users from granting themselves administrative access to our application. Because of its importance, it’s a good idea to write a test for any attribute that isn’t editable, and writing such a test for the admin attribute is left as an exercise (Section 9.6).
destroy actionThe final step needed to complete the Users resource is to add delete links and a destroy action. We’ll start by adding a delete link for each user on the users index page, restricting access to administrative users. The resulting "delete" links will be displayed only if the current user is an admin (Listing 9.52).
app/views/users/_user.html.erb
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
<% if current_user.admin? && !current_user?(user) %>
| <%= link_to "delete", user, method: :delete,
data: { confirm: "You sure?" } %>
<% end %>
</li>
Note the method: :delete argument, which arranges for the link to issue the necessary DELETE request. We’ve also wrapped each link inside an if statement so that only admins can see them. The result for our admin user appears in Figure 9.15.
Web browsers can’t send DELETE requests natively, so Rails fakes them with JavaScript. This means that the delete links won’t work if the user has JavaScript disabled. If you must support non-JavaScript-enabled browsers you can fake a DELETE request using a form and a POST request, which works even without JavaScript.11

To get the delete links to work, we need to add a destroy action (Table 7.1), which finds the corresponding user and destroys it with the Active Record destroy method, finally redirecting to the users index, as seen in Listing 9.53. Because users have to be logged in to delete users, Listing 9.53 also adds :destroy to the logged_in_user before filter.
destroy action. app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
.
.
.
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
end
private
.
.
.
end
Note that the destroy action uses method chaining to combine the find and destroy into one line:
User.find(params[:id]).destroy
As constructed, only admins can destroy users through the web since only they can see the delete links, but there’s still a terrible security hole: any sufficiently sophisticated attacker could simply issue a DELETE request directly from the command line to delete any user on the site. To secure the site properly, we also need access control on the destroy action, so that only admins can delete users.
As in Section 9.2.1 and Section 9.2.2, we’ll enforce access control using a before filter, this time to restrict access to the destroy action to admins. The resulting admin_user before filter appears in Listing 9.54.
destroy action to admins. app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :logged_in_user, only: [:index, :edit, :update, :destroy]
before_action :correct_user, only: [:edit, :update]
before_action :admin_user, only: :destroy
.
.
.
private
.
.
.
# Confirms an admin user.
def admin_user
redirect_to(root_url) unless current_user.admin?
end
end
With something as dangerous as destroying users, it’s important to have good tests for the expected behavior. We start by arranging for one of our fixture users to be an admin, as shown in Listing 9.55.
test/fixtures/users.yml
michael:
name: Michael Example
email: michael@example.com
password_digest: <%= User.digest('password') %>
admin: true
archer:
name: Sterling Archer
email: duchess@example.gov
password_digest: <%= User.digest('password') %>
lana:
name: Lana Kane
email: hands@example.gov
password_digest: <%= User.digest('password') %>
malory:
name: Malory Archer
email: boss@example.gov
password_digest: <%= User.digest('password') %>
<% 30.times do |n| %>
user_<%= n %>:
name: <%= "User #{n}" %>
email: <%= "user-#{n}@example.com" %>
password_digest: <%= User.digest('password') %>
<% end %>
Following the practice from Section 9.2.1, we’ll put action-level tests of access control in the Users controller test file. As with the logout test in Listing 8.28, we’ll use delete to issue a DELETE request directly to the destroy action. We need to check two cases: first, users who aren’t logged in should be redirected to the login page; second, users who are logged in but who aren’t admins should be redirected to the Home page. The result appears in Listing 9.56.
test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
def setup
@user = users(:michael)
@other_user = users(:archer)
end
.
.
.
test "should redirect destroy when not logged in" do
assert_no_difference 'User.count' do
delete :destroy, id: @user
end
assert_redirected_to login_url
end
test "should redirect destroy when logged in as a non-admin" do
log_in_as(@other_user)
assert_no_difference 'User.count' do
delete :destroy, id: @user
end
assert_redirected_to root_url
end
end
Note that Listing 9.56 also makes sure that the user count doesn’t change using the assert_no_difference method (seen before in Listing 7.21).
The tests in Listing 9.56 verify the behavior in the case of an unauthorized (non-admin) user, but we also want to check that an admin can use a delete link to successfully destroy a user. Since the delete links appear on the users index, we’ll add these tests to the users index test from Listing 9.44. The only really tricky part is verifying that a user gets deleted when an admin clicks on a delete link, which we’ll accomplish as follows:
assert_difference 'User.count', -1 do
delete user_path(@other_user)
end
This uses the assert_difference method first seen in Listing 7.26 when creating a user, this time verifying that a user is destroyed by checking that User.count changes by \( -1 \) when issuing a delete request to the corresponding user path.
Putting everything together gives the pagination and delete test in Listing 9.57, which includes tests for both admins and non-admins.
test/integration/users_index_test.rb
require 'test_helper'
class UsersIndexTest < ActionDispatch::IntegrationTest
def setup
@admin = users(:michael)
@non_admin = users(:archer)
end
test "index as admin including pagination and delete links" do
log_in_as(@admin)
get users_path
assert_template 'users/index'
assert_select 'div.pagination'
first_page_of_users = User.paginate(page: 1)
first_page_of_users.each do |user|
assert_select 'a[href=?]', user_path(user), text: user.name
unless user == @admin
assert_select 'a[href=?]', user_path(user), text: 'delete'
end
end
assert_difference 'User.count', -1 do
delete user_path(@non_admin)
end
end
test "index as non-admin" do
log_in_as(@non_admin)
get users_path
assert_select 'a', text: 'delete', count: 0
end
end
Note that Listing 9.57 checks for the right delete links, including skipping the test if the user happens to be the admin (which lacks a delete link due to Listing 9.52).
At this point, our deletion code is well-tested, and the test suite should be green:
$ bundle exec rake test
We’ve come a long way since introducing the Users controller way back in Section 5.4. Those users couldn’t even sign up; now users can sign up, log in, log out, view their profiles, edit their settings, and see an index of all users—and some can even destroy other users.
As it presently stands, the sample application forms a solid foundation for any website requiring users with authentication and authorization. In Chapter 10, we’ll add two additional refinements: an account activation link for newly registered users (verifying a valid email address in the process) and password resets to help users who forget their passwords.
Before moving on, be sure to merge all the changes into the master branch:
$ git add -A
$ git commit -m "Finish user edit, update, index, and destroy actions"
$ git checkout master
$ git merge updating-users
$ git push
You can also deploy the application and even populate the production database with sample users (using the pg:reset task to reset the production database):
$ bundle exec rake test
$ git push heroku
$ heroku pg:reset DATABASE
$ heroku run rake db:migrate
$ heroku run rake db:seed
$ heroku restart
Of course, on a real site you probably wouldn’t want to seed it with sample data, but I include it here for purposes of illustration (Figure 9.16). Incidentally, the order of the sample users in Figure 9.16 may vary, and on my system doesn’t match the local version from Figure 9.11; this is because we haven’t specified a default ordering for users when retrieved from the database, so the current order is database-dependent. This doesn’t matter much for users, but it will for microposts, and we’ll address this issue further in Section 11.1.4.

PATCH request to the update action.
db/seeds.rb to seed the database with sample data using rake db:seed.
render @users automatically calls the _user.html.erb partial on each user in the collection.
admin on users automatically gives a user.admin? boolean method.
DELETE requests to the Users controller destroy action.
For a suggestion on how to avoid conflicts between exercises and the main tutorial, see the note on exercise topic branches in Section 3.6.
session[:forwarding_url].
log_in_as helper.
PATCH request directly to the update method as shown in Listing 9.59, verify that the admin attribute isn’t editable through the web. To be sure your test is covering the right thing, your first step should be to add admin to the list of permitted parameters in user_params so that the initial test is red.
new.html.erb and edit.html.erb views to use the partial in Listing 9.60, as shown in Listing 9.61 and Listing 9.62. Note the use of the provide method, which we used before in Section 3.4.3 to eliminate duplication in the layout.12
admin attribute is forbidden. test/controllers/users_controller_test.rb
require 'test_helper'
class UsersControllerTest < ActionController::TestCase
def setup
@user = users(:michael)
@other_user = users(:archer)
end
.
.
.
test "should redirect update when logged in as wrong user" do
log_in_as(@other_user)
patch :update, id: @user, user: { name: @user.name, email: @user.email }
assert_redirected_to root_url
end
test "should not allow the admin attribute to be edited via the web" do
log_in_as(@other_user)
assert_not @other_user.admin?
patch :update, id: @other_user, user: { password: FILL_IN,
password_confirmation: FILL_IN,
admin: FILL_IN }
assert_not @other_user.FILL_IN.admin?
end
.
.
.
end
new and edit form. app/views/users/_form.html.erb
<%= form_for(@user) do |f| %>
<%= render 'shared/error_messages', object: @user %>
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
<%= f.label :email %>
<%= f.email_field :email, class: 'form-control' %>
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>
app/views/users/new.html.erb
<% provide(:title, 'Sign up') %>
<% provide(:button_text, 'Create my account') %>
<h1>Sign up</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
</div>
</div>
app/views/users/edit.html.erb
<% provide(:title, 'Edit user') %>
<% provide(:button_text, 'Save changes') %>
<h1>Update your profile</h1>
<div class="row">
<div class="col-md-6 col-md-offset-3">
<%= render 'form' %>
<div class="gravatar_edit">
<%= gravatar_for @user %>
<a href="http://gravatar.com/emails" target="_blank">Change</a>
</div>
</div>
</div>
before_filter, but the Rails core team decided to rename it to emphasize that the filter takes place before particular controller actions. ↑rake db:reset, but as of this writing this command doesn’t work with the latest version of Rails. ↑user is immaterial—we could have written @users.each do |foobar| and then used render foobar. The key is the class of the object—in this case, User. ↑curl can issue PATCH requests of this form. ↑new and edit partials. ↑