The Pragmatic Studio

User Accounts: UI

Exercises

Objective

Now that the User model is in good shape, we need a web interface that supports the following account management functions:

  • Allow new users to create an account using a sign-up form
  • Create a user profile page that shows a user's account information
  • Let the user edit their account information
  • Allow users to delete their account
  • Show a list of users, for completeness sake

Creating the web interface for user accounts is very similar to creating the interface for movies and reviews, as you did in the previous course. So most of this will be review, and good practice! In terms of MVC components, here's the road map for this exercise:

  • Define an index action and corresponding view template that lists all the users
  • Define a show action and corresponding view template that displays the user's profile page
  • Define a new action that renders a sign-up form with fields for the user's name and email
  • Define a create action that creates the user record in the database if their information is valid
  • Define an edit action that renders a form for editing a user's account info
  • Define an update action that updates the user record in the database
  • Define a destroy action that deletes a user's account

Just as a refresher, here's visually what we want:

If everything in this exercise flows smoothly for you, then you can know with confidence that you're ready for this follow-up course. If, on the other hand, you find some of these steps confusing or challenging, then we strongly recommend that you complete our Ruby on Rails: Level I course before going any further in this course.

1. List All Users

To get things rolling, let's start by displaying a list of users currently in the database. We'll link each user to their profile page which we'll create shortly.

If you're practicing test-driven development, at this point your next step is to write a feature spec for listing users. Think about what a list of users should display, and then write a spec for that. Or if you prefer to use our spec, click the link below to reveal it. Give it a read through, then paste it into a new list_users_spec.rb file in your spec/features directory.

Go ahead and run the feature spec to set up your goal:

rspec spec/features/list_users_spec.rb

You know you've successfully finished this feature when the spec passes!

  1. To get your bearings, print out the defined routes we have at this point:

    rails routes

    Rails 4: You must use the rake command rather than the rails command.

    rake routes

    You'll notice we already have the following routes for interacting with users:

         users GET    /users(.:format)           users#index
               POST   /users(.:format)           users#create
      new_user GET    /users/new(.:format)       users#new
     edit_user GET    /users/:id/edit(.:format)  users#edit
          user GET    /users/:id(.:format)       users#show
               PATCH  /users/:id(.:format)       users#update
               PUT    /users/:id(.:format)       users#update
               DELETE /users/:id(.:format)       users#destroy
    

    Remember that these conventional resource routes exist because the resource generator we ran earlier added the following line to the config/routes.rb file:

    resources :users
    

    So we know the URL to use to get a list of users...

  2. Browse to http://localhost:3000/users to list the existing users and you'll get an all-too-familiar error:

    Unknown action
    
    The action 'index' could not be found for UsersController
    
  3. Following the error, open the empty UsersController that was also generated by the resource generator and define an index action that fetches all the users from the database.

  4. Then create a corresponding app/views/users/index.html.erb view template. In the template, generate a list of user names with each name linked to the user's show page. As a nice header, display the number of users at the top.

  5. Now reload the index page back in your browser and you should see the two users we created previously in the console.

  6. As a finishing touch, you might consider changing the style of the user listing. In the previous course we learned how to style Rails apps using CSS and Sass. If you're keen to practice your web design skills, by all means go for it! Otherwise, feel free to use our web designer's CSS for the user pages. Just click the link below and copy/paste the CSS into the generated (and currently empty) app/assets/stylesheets/users.css.scss file:

    You'll also need to add the following line to the bottom of the list in the app/assets/stylesheets/application.css file:

    *= require users
    

    Reload and you should see a slightly different listing style. If it doesn't seem to work, make sure that the <ul> tag in your app/views/users/index.html.erb file has the CSS id users in order to trigger our styles. Refer to the solution to cross-check the markup if necessary.

  7. Finally, the feature spec should pass at this point, so run it to check your work so far:

    rspec spec/features/list_users_spec.rb

2. Show a User's Profile Page

When you click on a user's name in the user listing, we want to show their profile page. So that's our next task. For now the profile page will simply display the user's name and email. We'll fill in more profile information incrementally throughout the course.

As usual, below you'll find a feature spec to help drive your workflow or check your work at the end. If you're ready to practice writing your own specs first, this is an easy one to break the ice. Give it a go on your own, and then compare it to the spec below for a sanity check.

  1. Browse to the user listing page and try clicking on one of the user names. It should come as no surprise that you get this error:

    Unknown action
    
    The action 'show' could not be found for UsersController
    
  2. Following the error, define a show action that finds the requested user.

  3. Then create the corresponding app/views/users/show.html.erb template that displays the user's name and email. As a bonus, use the mail_to helper to link the user's email so that clicking it pops open your favorite email program with a new message addressed to the user.

  4. Now navigate from the user listing page to each user's profile page as a quick visual check. If you want to pick up our CSS styles, you'll need to use the same HTML markup as found in the solutions above.

  5. Finally, run the feature spec and make sure it passes:

    rspec spec/features/show_user_spec.rb

3. Create New User Accounts

Now that we have a user profile page to land on, we're ready to let users create new accounts using a sign-up form. To do that, we'll need to:

  • Add a route to support the custom URL http://localhost:3000/signup.

  • Generate a convenient "Sign Up" link at the top of every page.

  • Define a new action in the UsersController that renders the new.html.erb view template to display a sign-up form.

  • Define a create action in the UsersController that accepts the form data and uses it to create a new user in the database, but only if the user valid.

Wish you had those requirements expressed in an executable spec? No problem—we've got you covered!

  1. According to the existing routes, the URL for displaying the form to create a new user is http://localhost:3000/users/new. That request gets sent to the new action in the UsersController. We need to make that work, but we'd also like to support the more descriptive URL http://localhost:3000/signup. We actually want both URLs to show the sign-up form.

    In the config/routes.rb file, add a route that maps a GET request for /signup to the new action of the UsersController.

  2. Now run rails routes and you should see the custom /signup route and all the conventional resource routes for users:

        signup GET    /signup(.:format)          users#new
         users GET    /users(.:format)           users#index
               POST   /users(.:format)           users#create
      new_user GET    /users/new(.:format)       users#new
     edit_user GET    /users/:id/edit(.:format)  users#edit
          user GET    /users/:id(.:format)       users#show
               PATCH  /users/:id(.:format)       users#update
               PUT    /users/:id(.:format)       users#update
               DELETE /users/:id(.:format)       users#destroy
    

    We're going to need to generate a "Sign Up" link. Remember how to do that from the previous course? To generate a URL, we call a route helper method. In this case, the name of the route is signup, so the name of the route helper method is signup_path.

  3. We want the "Sign Up" link to show up at the top of every page, which means the link needs to get generated as part of the overall application layout.

    Look in the layout file (app/views/layouts/application.html.erb) and you'll notice that in the previous course we arranged things so that all the header markup was in its own partial file named app/views/layouts/_header.html.erb. Look in that partial file and you'll see an empty <nav> section. Hey, this looks like a great place for a "Sign Up" link! Use the route helper method to generate the link.

  4. Now reload and click the "Sign Up" link. The URL in your browser's address field should be http://localhost:3000/signup. Of course, we get an error because we don't yet have a new action, but we know how to fix that.

  5. Define a new action that instantiates a new User object for the sign-up form to use:

  6. Next, create the corresponding app/views/users/new.html.erb view template that generates a sign-up form with the following elements:

    • a text field to enter the user's name
    • an email field to enter the user's e-mail address
    • a password field to enter the user's super-secret password
    • another password field to confirm the user's password
    • a submit button
    • also use the existing app/views/shared/_errors.html.erb partial we created in the previous course to display any validation errors

    It's worth noting that you could use a standard text field for entering the email address, but using the HTML 5 email field gives a better user experience on some mobile devices. For example, iOS devices display a keyboard with the @ symbol on the primary screen.

  7. Reload just to make sure the form shows up as you'd expect before we do some refactoring...

  8. We'll want to reuse the form for editing a user, so once you have the form working go ahead and make a form partial.

  9. Reload the form to make sure it still works!

  10. Next, define a create action that uses the submitted form data to create a new user record in the database. If the user is successfully created, redirect to their profile page and show a cheery flash message to let the user know their account was created. Otherwise, if the user is invalid, redisplay the sign-up form.

  11. Now use the sign-up form to sign up a new user. Try the following combinations for posterity:

    • Leave the password and confirmation fields empty
    • Fill in a password, but leave the confirmation blank
    • Fill in a password and confirmation that do not match
    • Fill in a password and matching confirmation

    Make sure you end up creating a new user, landing up on their profile page. Home sweet home!

  12. The final step should be habit by now: run the spec and make sure it passes.

    rspec spec/features/create_user_spec.rb

4. Edit Account Information

Now that new users can sign up, they probably also want to be able to edit their account information. To let them do that, we'll need to:

  • Generate an "Edit Account" link on the user profile page.

  • Define an edit action in the UsersController that renders the edit.html.erb view template to display an edit form pre-filled with the user's account details.

  • Define an update action in the UsersController that uses the submitted form data to update the user in the database, but only if the form data is valid.

We trust you know what to do from now on when you see these tantalizing spec links. :-)

  1. First we need to put an "Edit Account" link on the user profile page. Use a route helper method to generate that link in the footer area of the show page (which we're calling the user profile page).

  2. Then define the edit action which finds the user matching the id parameter in the URL so we can populate the edit form with the user's existing information.

  3. Now that we have the user we want to edit, create the corresponding app/views/users/edit.html.erb view template. It needs to display the same form we used to create a new user, which we already have in a partial. (Don't you love it when a plan comes together?) So use that partial to generate the edit form.

  4. Back in your browser, revel in your work by clicking the "Edit Account" link for a user. You should see a form pre-filled with the user's name and email, but the password fields are blank... as they should be!

  5. Next, define the update action to use the submitted form data to update the user in the database. If the user is successfully updated, redirect to their profile page with a flash message confirming that their account was successfully updated. Otherwise, if the form data is invalid, redisplay the edit form so the user can give it another try.

  6. Then, back in your browser, change the user's name and/or email, but leave the password fields blank. You should get redirected to the user's profile page and see the updated information.

  7. Now edit the user account again, and try typing something in the password field. Submit the form and you should get password validation errors. If you type anything into the password fields, then both fields are required and must match. In other words, when updating a user the password-related validations only run if you try to change the password. That's another nice touch that comes courtesy of using has_secure_password.

  8. Now for a bit of customization. You may have noticed on "Edit Account" form that the default submit button says "Update User". If you then look at the "Sign Up" form, the submit button says "Create User". Rails is smart enough to name the button based on whether the @user object referenced in the form_for(@user) line represents an existing user already stored in the database (we're editing it) or a new user not yet stored in the database (we're creating it).

    The default button name is convenient, but suppose we want to use account vernacular and have the button say "Create Account" or "Update Account". To do that, simply locate the following line in the form partial:

    <%= f.submit %>
    

    Then replace that line with the following lines:

    <% if @user.new_record? %>
      <%= f.submit "Create Account" %>
    <% else %>
      <%= f.submit "Update Account" %>
    <% end %>
    

    All we do here is call the new_record? method that's available on all ActiveRecord objects. It returns true if the object hasn't already been saved to the database and false otherwise. Then depending on the answer, we pass a string label to the submit method. It's a small thing, but users tend to think in terms of their account and using words that align with their thinking is reassuring.

    Note: This change will cause your create_user_spec.rb spec to fail because it's looking for a "Create User" button. So you'll need to change it to click the "Create Account" button instead.

  9. Remember to check your work by running the spec!

    rspec spec/features/edit_user_spec.rb

5. Delete Accounts

We hope it doesn't happen often, but users may want to delete their account. To support that, we need to:

  • Generate a "Delete Account" link on the user profile page.

  • Define a destroy action in the UsersController that deletes the user from the database and redirects to the user listing page with a confirmation flash message.

  1. Generate a "Delete Account" link in the footer area of the user profile page.

  2. Implement the destroy action. Once the user's record has been destroyed, redirect to the application home page and flash an alert message confirming that the account was successfully deleted.

  3. Now go back to the browser and click the "Delete Account" link. You should end up on the application home page. Go to the user listing page and the user you deleted should not be displayed in the listing.

  4. This would be an ideal time to run the feature spec!

  5. Finally, you might want to use your new account management web interface to re-create the user you just deleted. :-)

6. Run All the Specs

Before checking all these new features off the list, it's reassuring to know that we didn't inadvertently break any existing features. One of the benefits of incrementally building a suite of automated tests is that at any time we can run them all in one fell swoop to get rapid feedback. Remember how to run all the specs?

Solution

The full solution for this exercise is in the user-accounts-ui directory of the code bundle.

Bonus Round

Want to challenge yourself? These optional bonus exercises take things up a notch and draw on skills you learned in this course and the previous course.

Show "Member Since" Date

As a nice touch, on the user's profile page show the month and year that the user became a member (created an account) on our site. Format the "member since" date as "January 2015", for example.

Add a Username Field

In addition to a full name and email, some sites also allow users to set a unique username. It's your online nickname or screenname. For example, Mike's Twitter username is "clarkware".

Add a username field to the users database table, and allow users to specify a username when creating (and editing) their account. Usernames must be present and only consist of letters and numbers (alphanumeric characters) without spaces. Also, no two users can have the same username in the database. Treat usernames as being case-insensitive.

Try it on your own first (after all, we're in the bonus round!) and then follow the steps below if you need a hand.

  1. Generate a migration to add a username column to the users table.

  2. Declare appropriate validations in the User model.

  3. Update the form partial to include a text field for entering the username.

  4. Don't forget to add the username field to the list of permitted parameters so that the username can be assigned from form data.

  5. Finally, make sure to update the model and feature specs accordingly so they continue to pass!

Add a Profile Image

If you want to give the user profile page a bit more personality, you might consider adding a profile image for each user. A popular way to do that is by integrating with the free Gravatar service. Gravatar lets you upload your preferred profile image (called a global avatar image) to the Gravatar site and associate that image with a particular email address. Then when you create a user account on another site using that same email address, the site can use the Gravatar service to show your preferred profile image. It's really convenient because you can register your profile image with Gravatar once and the image automatically follows you to any Gravatar-enabled site.

It's relatively easy to Gravatar-enable an app, and we'll get you started...

  1. First, to access a user's profile image, we need to generate an MD5 hash of the user's email address. To do that, add the following method to your User model:

    def gravatar_id
      Digest::MD5::hexdigest(email.downcase)
    end
    

    That method simply returns a string that represents the hashed value for the email address. For example, for Mike's email address we'd get back the string 58add23fa01eae6d736adce86e07ae00. For every unique email address, the method will return a consistent and unique hashed value. Think of it as your unique Gravatar id, which is why we named the method as such.

  2. Then to request the associated profile image that's stored on Gravatar's site, we use a URL with the form http://secure.gravatar.com/avatar/gravatar-id where the gravatar-id part is replaced with a particular user's gravatar id. For example, Mike's profile image is at http://secure.gravatar.com/avatar/58add23fa01eae6d736adce86e07ae00. Open that URL in a new browser window and you get Mike's mugshot. Now, to actually show Mike's profile image on his profile page, we need to generate an image tag for the image that lives on Gravatar's site.

    To do that, write a custom view helper called profile_image_for that takes a user object as a parameter. It needs to generate a string that represents the URL for that user's Gravatar image and then use that URL to generate and return an image tag for the image. Put the helper method in the users_helper.rb file.

  3. Next, update the user profile page to call the profile_image_for helper so that the user's profile image is shown on the page.

  4. Reload a user profile page and you should either see the default Gravatar image (a blue square) or an actual profile image if the user's email has already been associated with a Gravatar image. Now might be a good time to create your own Gravatar image!

Check the Log File

We've been diligent about not storing plain-text passwords in our database, but we also need to be careful not to expose them in other areas of our application.

For example, every time a form is submitted, we've seen that Rails automatically records the submitted form data in the log file. In development mode, everything is logged in the log/development.log file which is also displayed in the command window where your app is running.

Check out the log file and scroll back to the part where you signed up a new user. You should see something like this:

Parameters: {"utf8"=>"✓", "authenticity_token"=>"Ur/HVsiN+hwb3An6lvx+CpXxPmyp4JAujKzzHEaUfPg=", "user"=>{"name"=>"Larry", "email"=>"larry@example.com", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}, "commit"=>"Create Account"}

In particular, notice that the submitted password and password_confirmation form parameters were masked as [FILTERED] so that the actual values aren't displayed in the log file. This is another example of the Rails defaults trying to help us do the right thing. It works because of the following line in the config/initializers/filter_parameter_logging.rb file:

Rails.application.config.filter_parameters += [:password]

That line simply appends the key :password to the filter_parameters array. Then, before the params hash gets logged, the values for all the keys matching the regular expression /password/ get replaced with the string [FILTERED]. It's as if someone used a big black marker to hide all the classified information!

Passwords are the most common parameter that need to be filtered from the log files, so Rails takes care of that for you. But you'll want to consider filtering other sensitive parameters specific to your application.

Force SSL in Production

When a user types in her password and submits the sign-up form, the form data (including her plain-text password) travels over the network before reaching our server. That's not a problem when we're running in development mode: the server is running on our machine and we're the only user. But if you plan to deploy this application to a production server, then you'll need to ensure that sensitive data entered on your site via a user's browser is always transferred back to your server through an encrypted connection.

To do that, open the config/environments/production.rb file and uncomment the following line:

config.force_ssl = true

This setting, which only effects your app when it's running in production mode, forces all access to the app over SSL. If a browser tries to make a non-SSL request to the app, Rails will automatically redirect the browser to the same URL using the HTTPS protocol.

For this to work, you'll need to have SSL configured properly on your production server. That task is way beyond the scope of this course, and likely something you'll want to take up with your favorite system administrator. If you're using Heroku as we did in the previous course and you don't require a custom domain name, then they've already done the grunt work for you. Just deploy your app with the change above and you're good to go. Otherwise, to configure Heroku to run SSL on a custom domain, check out the Heroku documentation on SSL.

Wrap Up

Nicely done! Folks are now able to create user accounts complete with super-secret passwords. Working incrementally through the process of designing a web interface for managing user accounts allowed us to review the following Rails fundamentals:

  • Creating a new resource using the generator
  • Applying a migration file
  • Declaring validations in the model
  • Using the Rails console to create records in our database
  • Defining actions to support the conventional resource routes: index, show, new, create, edit, update, and destroy
  • Linking pages together
  • Using partials to reduce view-level duplication
  • Adding custom URLs and using route helper methods
  • Providing feedback with flash messages
  • Writing and running specs
rails5-i

Again, this should be familiar ground. All resources in Rails essentially follow the same routes and conventions we used in this exercise. If this exercise felt like a review for you, then you're ready to take on this course. If instead you found yourself struggling with the syntax or concepts in this exercise, then before going any further we recommend completing the Rails Level I course.

With all this now in place, in the next section we'll allow users to actually sign in to their account. Open sesame...

All course material, including videos and source code, is copyrighted and licensed for individual use only. You may make copies for your own personal use (e.g. on your laptop, on your iPad, on your backup drive). However, you may not transfer ownership or share the material with other people. We make no guarantees that the source code is fit for any purpose. Course material may not be used to create training material, courses, books, and the like. Please support us by encouraging others to purchase their own copies. Thank you!

Copyright © 2005–2024, The Pragmatic Studio. All Rights Reserved.