Table of Contents

View Screencast (Quicktime)

Right-click on the button above and choose Save As to save file to your computer


Support the Course

There's no charge for the course, but we greatly appreciate any donations.

Suggested donations:
  • One or two lessons: $5
  • Several lessons $10
  • Entire course: $25 – $50

We hope you've found the course to be valuable, and we appreciate any support you care to provide.


Sign Up Now!

If you aren’t already receiving our course lessons via email, sign up now to be sure you don’t miss anything.

Every few days, we’ll send you an email with a link to the next episode, plus a list of additional resources for advancing your knowledge.

There’s no cost and no obligation. And we’ll never share your email address with any third party.

We’ll send you the first lesson right away.

Looking for a Powerful Hosted CMS?

The authors of the Learning Rails course also offer a very powerful hosted content management system for web designers, which enables you to build sophisticated, database-driven sites without programming. This is a great alternative to building a custom Ruby on Rails site for those applications for which you just can't justify the cost of a custom solution.

To learn more, sign up for the free Learning Webvanta course on building database-driven web sites without programming.

Lesson 12
User Management

comments Bookmark and Share

Goals

In this lesson, we work towards implementing an administrative dashboard by expanding the user controller generated by restful_authentication to give us full abilities to manage user data.

Setup

We begin with the code with which we ended Lesson 11. These zip files contain the beginning and ending states of the code:

Clean up session create with a notice if user incorrectly enters credentials

Before diving in to the core work in this lesson, let’s continue to do some small changes to improve the experience our application provides. One pain point occurs when you try to log in and type the wrong credentials.

Let’s put a notice in the create failure path, before re-rendering the edit dialog

flash[:notice] = "Try entering your credentials again"

Create a default admin user (migration)

Let’s use a migration called a “data migration”. It doesn’t change the structure of the database. Instead it loads data, in this case, a default user account.

script/generate migration AddDefaultUser
self.up
  if !User.find_by_login('admin')
    User.create(:login => 'admin', :email => 'admin@sample.com', 
                :password => 'changeme', :password_confirmation => 'changeme')
  end
end

We aren’t creating a down migration in this case. We don’t know if we were the ones who created this account for sure, so we won’t delete it when this migration is run in reverse.

Run the migration in the terminal:

rake db:migrate

Add a user list page

The “restful_authentication” plugin’s generate creates a RESTful controller, but it only creates the new and create actions. We need to fill the rest out and can use the Pages controller as a model.

Add an index action to users controller.

def index
  @users = User.find(:all)
end

Create users/index view in the users directory (index.html.erb). Also prepare by adding links to show, edit, and delete. Put a link at the bottom to create new users.

<h1>Listing Users</h1>

<table>
  <tr>
    <th>Login</th>
    <th>Email</th>
  </tr>

  <% for user in @users %>
    <tr>
      <td><%=h user.login %></td>
      <td><%=h user.email %></td>
      <td><%= link_to 'Show', user %></td>
      <td><%= link_to 'Edit', edit_user_path(user) %></td>
      <td><%= link_to 'Delete', user, 
          :confirm => "Are you sure you want to delete '#{user.login}'?",
          :method => :delete %></td>
    </tr>
  <% end %>
</table>

<br />

<%= link_to 'New User', new_user_path %>

Take a look…seems to work, but we need to fill out the rest of the actions and the appropriate views.

Add show user page

Add a show action to the users controller.

def show
  @user = User.find(params[:id])
end

Add the show view (show.html.erb).

<p>
  <b>Login:</b>
  <%=h @user.login %>
</p>

<p>
  <b>Email:</b>
  <%=h @user.email %>
</p>

<p>
  <b>Password:</b>
  [secret]
</p>


<%= link_to 'Edit', edit_user_path(@user) %> |
<%= link_to 'Back', users_path %>

Add a user delete function

Add destroy action to users controller.

def destroy
  @user = User.find(params[:id])
  @user.destroy

  redirect_to(users_url)
end

Note the link is already in the index view and uses a javascript alert dialog to confirm the deletion with the user.

Add a edit/Update user functions

Add edit and update actions to the users controller. New/create and edit/update always work in pairs in the RESTful implementation in Rails 2.

def edit
  @user = User.find(params[:id])
end
  
def update
  @user = User.find(params[:id])

  if @user.update_attributes(params[:user])
    flash[:notice] = 'User was successfully updated.'
    redirect_to(user_path(@user))
  else
    render :action => 'edit'
  end
end

Create a corresponding view for edit. It is nearly identical to the new view, but we changed the form_for line to take advantage of Rails behavior. By specifying the object on the form_for line, Rails automatically generates the proper URL to our update action and submits the form using the “PUTHTTP method.

<%= error_messages_for :user %>

<% form_for @user do |f| -%>
  <p><label for="login">Login</label><br/>
  <%= f.text_field :login %></p>

  <p><label for="email">Email</label><br/>
  <%= f.text_field :email %></p>

  <p><label for="password">Password</label><br/>
  <%= f.password_field :password %></p>

  <p><label for="password_confirmation">Confirm Password</label><br/>
  <%= f.password_field :password_confirmation %></p>

  <p><%= submit_tag 'Update' %></p>
<% end %>

Now test all of the pages together. They work!

Wrapping up

Of course, it is tiresome to keep typing the URLs manually to get to these pages. You can navigate directly to the users controller at localhost:3000/users, but it would be a lot easier to have a dashboard that collects all of these links in one easy to access place. Even better, the dashboard should be available from our navigation tags when appropriately logged in.

Coming up

In our next lesson, we’ll add support for “administrative” pages in our simple CMS, so we can create the dashboard using our own technology. We’ll also tweak the tab navigation so it automatically discovers and displays links without us needing to edit code.


Add Your Comments

(not published)

Reader Comments

28 comments

Edit

From: Joao, 10/26/11 05:27 PM

Hello, I followed this tutorial until the end without any problem (adapting stuff to rails 3.1.1) except for the user's edit view, it just appeared blank after the menu and even the source was blank as well after the menu. After looking at the code and not seeing anything signaling for attention I added a '=' to line 3: <%= form_for @user do |f| -%> and that made it work... but now on the source code of the page is a bunch of info that wasn't supposed to be there:

. Is there a way around this? Is it because I'm using rails 3.1 and the form_for works differently? Thanks in advance.

delete method

From: garrett hawes, 04/16/11 01:27 PM

hey thanks for the great lessons i am going through with rails 3 changing what seems to need to be changed, everything is going along nicely but the delete method does not seem to work just takes me to the edit page, any ideas? thanks garrett

AddDefaultUser

From: bevan, 10/10/09 06:03 AM

Hi, I am running Rails 2.3.4 & get the following error with the migration: == AddDefaultUser: migrating =================================================== rake aborted! An error has occurred, this and all later migrations canceled: wrong number of arguments (1 for 3) /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/observer.rb:171:in `after_create' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/observer.rb:171:in `send' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/observer.rb:171:in `update' /usr/lib/ruby/1.8/observer.rb:185:in `notify_observers' /usr/lib/ruby/1.8/observer.rb:184:in `each' /usr/lib/ruby/1.8/observer.rb:184:in `notify_observers' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/callbacks.rb:357:in `notify' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/callbacks.rb:350:in `callback' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/callbacks.rb:267:in `create' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2867:in `create_or_update_without_callbacks' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/callbacks.rb:250:in `create_or_update' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:2538:in `save_without_validation' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/validations.rb:1078:in `save_without_dirty' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/dirty.rb:79:in `save_without_transactions' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:229:in `send' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:229:in `with_transaction_returning_status' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/connection_adapters/abstract/database_statements.rb:136:in `transaction' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:182:in `transaction' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:228:in `with_transaction_returning_status' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:196:in `save' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:208:in `rollback_active_record_state!' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:196:in `save' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/base.rb:723:in `create' ./db/migrate//20090613155243_add_default_user.rb:4:in `up_without_benchmarks' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:282:in `send' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:282:in `migrate' /usr/lib/ruby/1.8/benchmark.rb:293:in `measure' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:282:in `migrate' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:365:in `__send__' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:365:in `migrate' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:486:in `migrate' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:560:in `call' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:560:in `ddl_transaction' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/connection_adapters/abstract/database_statements.rb:136:in `transaction' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/transactions.rb:182:in `transaction' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:560:in `ddl_transaction' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:485:in `migrate' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:472:in `each' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:472:in `migrate' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:400:in `up' /usr/lib/ruby/gems/1.8/gems/activerecord-2.3.4/lib/active_record/migration.rb:383:in `migrate' /usr/lib/ruby/gems/1.8/gems/rails-2.3.4/lib/tasks/databases.rake:116 /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:636:in `call' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:636:in `execute' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:631:in `each' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:631:in `execute' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:597:in `invoke_with_call_chain' /usr/lib/ruby/1.8/monitor.rb:242:in `synchronize' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:590:in `invoke_with_call_chain' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:583:in `invoke' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2051:in `invoke_task' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2029:in `top_level' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2029:in `each' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2029:in `top_level' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2068:in `standard_exception_handling' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2023:in `top_level' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2001:in `run' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:2068:in `standard_exception_handling' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake.rb:1998:in `run' /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/bin/rake:31 /usr/bin/rake:19:in `load' /usr/bin/rake:19 The log output is as follows: Migrating to AddDefaultUser (20090613155243) User Load (0.2ms) SELECT * FROM "users" WHERE ("users"."login" = 'admin') LIMIT 1 User Load (0.2ms) SELECT "users".id FROM "users" WHERE (LOWER("users"."login") = 'admin') LIMIT 1 User Load (0.1ms) SELECT "users".id FROM "users" WHERE (LOWER("users"."email") = 'admin@sample.com') LIMIT 1 User Create (0.4ms) INSERT INTO "users" ("salt", "created_at", "crypted_password", "remember_token_expires_at", "updated_at", "remember_token", "login", "email") VALUES('ff168bf49b2701f7d7f87f813ff7ef6153398ea9', '2009-10-10 12:50:31', '7c4c7c816ceefc120bb648286e473e5f9666b57a', NULL, '2009-10-10 12:50:31', NULL, 'admin', 'admin@sample.com') Has anyone else been experiencing similar problems and know how to fix it? Thanks

paths queries

From: cecille, 09/26/09 02:54 AM

hi. i hope that anybody can answer these. i'm sorry for sounding too stupid. hehe. i just really want to know or confirm. thanks for reading and hopefully answering <%= link_to 'Show', user %> <%= link_to 'Edit', edit_user_path(user) %>

<%= submit_tag 'Update' %>

1. where do user_path, edit_user_path and 'Update' headed? (or how did you know it can be used) .<%= link_to 'Delete', user, :confirm => "Are you sure you want to delete '#{user.login}'?", :method => :delete %> 2. from the code above, how did rails (or ruby) knew what to delete? 3. i tried logging in with credentials not in the database, it resulted to "Login information not found" (or something like that), its suppose to act like that, right? but when i click on 'home' button the result is: "Login information not found" "Welcome to Learning on Rails App" <--the home page body 4. lastly, i would just like to confirm that when i delete the admin user data it will be forever deleted unless i make one, right? (or since we have AddUserDefault migration, it(the admin user data) suppose to be created everytime the website is accessed) thanks again!!!

Figured out you dropped Migration _03 from previous episode and re-used number

From: Wayne Simacek, 09/16/09 09:22 PM

I decided to just start with the code from 12 and follow along and figured out that the Migration _03 to add the admin user was missing from this version's file zip. I copied that one back in and re-named the new one to _04 and it seems to have worked OK. Thanks for the lessons, Wayne

how to implements new layout...

From: hendri, 07/21/09 07:54 AM

i've finished your tutorial, but i get difficulty to implements difference page to this appilcation...i want to put blog and news page in the difference layout and css, how to do that ?

Delete user ends up on sessions page

From: Jim, 04/26/09 12:12 PM

I've been following along, and everything works except for when I delete the user at the end of the lesson. Instead of going back to the page: http://localhost:3000/users/ I end up at the login screen at: http://localhost:3000/session/new The log says that it is trying to redirect to the right place I believe, but somehow the app thinks I'm no longer logged in and takes me to log in under 'sessions'? Processing UsersController#destroy (for 127.0.0.1 at 2009-04-26 14:10:06) [DELETE] Parameters: {"authenticity_token"=>"UJn7/yOTI9QeqKSNgZX9Ut+95uoihHd7NIY6CYMPg14=", "id"=>"7"} User Load (0.3ms) SELECT * FROM "users" WHERE ("users"."id" = 7) LIMIT 1 User Load (0.3ms) SELECT * FROM "users" WHERE ("users"."id" = 7) User Destroy (0.4ms) DELETE FROM "users" WHERE "id" = 7 Redirected to http://localhost:3000/users Completed in 24ms (DB: 1) | 302 Found [http://localhost/users/7]

erb options

From: Michael Slater, 02/04/09 03:01 PM

@<%=h@ is just invoking the html sanitize (h) helper that is part of rails, which escapes any html and js code so the rendered text is safe and can't mess up the layout or do anything nefarious. @-%>@ inhibits the newline that is normally generated. Rarely does this make any difference, but it can make the HTML output prettier for people who are looking at the page source.

<%=h and -%>

From: Fletch, 02/04/09 02:39 PM

Another good tute. One question, previously you carefully explained the difference between <% and <%= but in this lesson you introduce <%=h and -%> without any explanation. How do they work?

problem with localhost:3000/users

From: Fidelio Artur, 11/11/08 01:20 PM

Thanks.I have moved successfully to this lesson. Now a Im stuck on onother point. I've followed the lesson till the end. But now when I navigate on localhost:3000/users see the error:SyntaxError in UsersController#index. Before this lesson I had created admin page. It was my missundurstandig and is still in there. How can I get out from this? Have a nice day. Artur

problem with localhost:3000/users

From: Fidelio Artur, 11/11/08 01:20 PM

Thanks.I have moved successfully to this lesson. Now a Im stuck on onother point. I've followed the lesson till the end. But now when I navigate on localhost:3000/users see the error:SyntaxError in UsersController#index. Before this lesson I had created admin page. It was my missundurstandig and is still in there. How can I get out from this? Have a nice day. Artur

RE: Deleting admin user

From: Christopher Haupt, 09/27/08 10:27 AM

If you delete the admin user, you can create another one in several ways: # Create another migration (the one that originally created the admin user can't be "used again" unless you roll-back your DB). # Use some other account that you set as "administrator" # Go in to script/console, which gives you an interactive Ruby prompt (irb) in the full context of your application. You can actually create a new account record (Account.create(...)) just like we did in that migration. Use the migration as a pattern. Good luck

Deleting Admin user

From: Ronen, 09/27/08 08:03 AM

I understand our admin migration created our Admin user with the default password. What happens if we delete the admin user? Can it be recreated again without logging in as an existing user?

Admin migration

From: George L., 09/21/08 05:13 AM

I figured it out. I used the same email address for the admin account as another user already in the table. The User model doesn't allow multiple users with the same email address. The migration didn't report an error but when I tried to add the user via users/new it reported the error.

Admin migration

From: George L., 09/20/08 07:23 PM

I don't get it. The migration to add the admin user doesn't work. I get no complaints from rake when I rung the migration but the record doesn't end up in the database. What could I be doing wrong.

User class

From: Michael Slater, 07/16/08 02:47 AM

The User class is the model. Models are singular, controllers are plural.

User 2

From: Simen G, 07/13/08 09:18 PM

So where are this User class? I thought we were referring to the controller “users”, but then I don’t understand why we are reffering to “user” and not “users”. I can see that there’s a create method inside the users controller, but again, why do we use User and not Users in the migration file?

RE: User

From: Christopher Haupt, 07/01/08 09:47 AM

We are referring to the class “User” and invoking class methods on User. Class methods are those that don’t require a specific instance object of a class and typically affect all members of the Class. In this specific example, the finder “find_by_login” searches for all Users with a login name of ‘admin’. You’ll see this pattern all over Rails.

-c

Question about if 'User'

From: Ali, 07/01/08 09:12 AM

Hello, What does the ‘User’ in ‘if !User.find_by_login(‘admin’)’ and some other lines of code refer to.

thanks

From: Steve, 05/29/08 03:59 AM

Christopher – that’s awesome! Thanks! Now I’ve got to get busy implementing it. :)

RE: protect some actions

From: Christopher Haupt, 05/29/08 03:51 AM

Steve, absolutely you can protect only certain methods. There are a couple of ways, but perhaps the easiest is to list which methods you want to “except” from the filter:

before_filter :login_required, :except => :show

You could use the :only option instead to just do the filter on specific actions. If you need to specify more than on action, put them into an array:

before_filter :login_required, :only => [ :edit, :update ]

protect some actions but not others?

From: Steve, 05/28/08 02:56 AM

Hi; I really appreciate the meticulous thoroughness you have provided in these first screencasts. My question is: is it possible to use the restful authentication plugin to prevent access to only some actions without login? You have set up two categories of controllers in your sample project: ones that a logged in user has access to and one that is public. I’m thinking of a project in which the “show” method of a controller would be public (unprotected), but only logged in admins would have access to the create, update and destroy actions. Can you point me to some tips on how to do this? Thanks.

@user.destroy

From: Enrique , 05/25/08 06:23 AM

When doing @user.destroy on the destroy method the session is destroy as well not just that record, I can see in your screencast this does not happen to you.

However I checked and compared each file modified by you (that is the users, sessions controllers and the views for the user controller) and I can’t find anything wrong nor anything different, What could be wrong with my app?.

Thanks in Advance

Solved missing Edit :id parameter in show.html.erb

From: Walter, 05/15/08 04:54 AM

Found the error! <= link_to ‘Edit’, edit_user_path(@user) %> in show.html.erb had @users not @user. Now the Edit link works fine. Interestingly enough playing around with <= debug(@user) %> added in the index and show views pointed me back to the user controller, then I realized I made a typo….. Anyway, killed a brain cell but good practice I guess. Cheers

missing :id in Edit link_to from show view

From: Walter, 05/14/08 01:41 PM

I have an error that I’m trying to track down. In /users I do get the List of all logins, and can successfully click Show/Edit/Delete without problems. However, when inside a show action ie: /user/2, the lower Back button works but the Edit button shows /user//edit and is missing the :id reference. The Edit in the /users comes from inside the index.html.erb loop <=link_to ‘Edit’, edit_user_path(user) %> where (user) is the iteration of @users, and works just fine. Inside show.html.erb this is accessed directly with <= link_to ‘Edit’, edit_user_path(@user) %> but does not include the :id. Checked the edit_user reference in rake routes but that’s okay. Any ideas? Thanks

Restful routes

From: Michael Slater, 05/09/08 09:26 AM

Shilpa, these are the “magic” restful routes provided by the single line in routes.rb, map.resources :user. You can find the docs the explain them here: http://api.rubyonrails.org/classes/ActionController/Resources.html

Restful paths

From: Shilpa Som, 05/09/08 05:10 AM

Thanks a lot for responding for my question in previous episode. This is one more great screencast.

Again query :) Could you pls clarify the restful paths used in the code like edit_user_path,users_path,user_url. I didn’t find find any expansion for these paths in routes.rb.

Or Could you redirect me to any links online for further reading in this regard??

Thanks again.

From: Adam Teale, 04/28/08 05:04 PM

Christopher & Michael your RoR screencast series is so awesome, thanks a lot for all the effort to put it together. I really appreciate the cruisy pace that you take the viewer/user through the whole process. It’s great that you have the lessons doc/files online too. Looking forward to your next ep!

 

Sponsored By

New Relic Rails Performance Monitoring