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.
Comments on This Lesson
From: Ali Date: 07/01/08 04:16 PM
Subject: Question about if 'User'
Hello, What does the ‘User’ in ‘if !User.find_by_login(‘admin’)’ and some other lines of code refer to.
From: Steve Date: 05/29/08 10:10 AM
Subject: thanks
Christopher – that’s awesome! Thanks! Now I’ve got to get busy implementing it. :)
From: Christopher Haupt Date: 05/29/08 10:10 AM
Subject: RE: protect some actions
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 ]
From: Steve Date: 05/28/08 09:09 AM
Subject: protect some actions but not others?
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.
From: Enrique Date: 05/25/08 01:13 PM
Subject: @user.destroy
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
From: Walter Date: 05/15/08 11:11 AM
Subject: Solved missing Edit :id parameter in show.html.erb
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
From: Walter Date: 05/14/08 08:20 PM
Subject: missing :id in Edit link_to from show view
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
From: Michael Slater Date: 05/09/08 04:16 PM
Subject: Restful routes
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
From: Shilpa Som Date: 05/09/08 12:12 PM
Subject: Restful paths
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 Date: 04/29/08 12:00 AM
Subject:
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!
From: Christopher Haupt Date: 07/01/08 04:16 PM
Subject: RE: User
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