Lesson 12
User Management
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:
- Learning Rails example app code as of the end of Lesson 11
- Learning Rails example app code as of the end of Lesson 12
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 “PUT” HTTP 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.

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:
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
<%= submit_tag 'Update' %>
1. where do user_path, edit_user_path and 'Update' headed? (or how did you know it can be used) .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 => :showYou could use the
:onlyoption 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!