Index of All Lessons

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.


Want to help spread the word? We’d be grateful if you would include a link to the course in your blog, web site, or emails.

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.

User Management

comments

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 a Comment

Have a comment or question about this lesson? Add it here.






Comments on This Lesson

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

Subject: AddDefaultUser

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 = ‘admin’) LIMIT 1 User Load (0.1ms) SELECT “users”.id FROM “users” WHERE (LOWER = ‘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

Has anyone else been experiencing similar problems and know how to fix it?

Thanks

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

Subject: paths queries

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!!!

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

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

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

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

Subject: how to implements new layout...

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 ?

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

Subject: Delete user ends up on sessions page

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]

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

Subject: erb options

<%=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.

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

Subject: <%=h and -%>

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?

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

Subject: problem with localhost:3000/users

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

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

Subject: problem with localhost:3000/users

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

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

Subject: RE: Deleting admin user

If you delete the admin user, you can create another one in several ways:

  1. Create another migration (the one that originally created the admin user can’t be “used again” unless you roll-back your DB).
  2. Use some other account that you set as “administrator”
  3. 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

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

Subject: Deleting Admin user

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?

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

Subject: Admin migration

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.

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

Subject: Admin migration

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.

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

Subject: User class

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

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

Subject: User 2

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?

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

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

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

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 03:59 AM

Subject: thanks

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

From: Christopher Haupt       Date: 05/29/08 03:51 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 =&gt; :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 02:56 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 06:23 AM

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 04:54 AM

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

Found the error! <= link_to ‘Edit’, edit_user_path(user) %&gt; in show.html.erb had @users not @user. Now the Edit link works fine. Interestingly enough playing around with &lt;</span>= 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 01:41 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 &lt;</span>= link_to &#8216;Edit&#8217;, 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 09:26 AM

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 05:10 AM

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/28/08 05:04 PM

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!

 

Sponsored By

New Relic Rails Performance Monitoring