21: Adding the user page
Right now, the users are the main actors in the system. However, the only thing we know about them is their email. This is both unsafe and not very friendly.
So let's give our users names and pages.
Ading a user's name
The datatabase change
Adding names to users is related to changing a table. As you've probably guessed, that means changing a table. And to change a table, we create a migration. Open your terminal and type:
$ rails generate migration add_name_to_users name:string
Rails will infer the name of the database table and will create the appropriate migration for you. If you want to see what it generated, look inside the db/migrate/123123123_add_name_to_users.rb
file:
class AddNameToUsers < ActiveRecord::Migration
def change
add_column :users, :name, :string
end
end
This migration is OK, so we can safely apply the changes to our database:
$ rake db:migrate
The column now exists in the users table.
Requesting the name during registration
Let's move to the Registration form and ask for the name during the registration. Open the file app/views/devise/registrations/new.html.erb
and, after the line
<%= f.email_field :email, autofocus: true %>
add:
<%= f.text_field :name, label: "Name", required: true%>
Now launch your server (using rails server
) and logout if you were logged in. Visit http://localhost:3000/users/sign_up
and see the new field! Try to type a name and sign up. It works!
You will also notice that our form sets the name as required: true
. This is to make sure the users enter a name during registration. This is a validation on a very superficial level, we would like to add one more.
Rails has several types of validations we can use. Let's try one. Open app/models/user.rb
and add the following on the line before the last end:
validates :name, presence: true, allow_blank: false
One more thing, though. Devise is very, very strict about its parameters, so we'll have to tell Rails to also include the name in the check for the sign_up action.
To do this, open up app/controllers/application_controller.rb
and add the following before the last end:
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
end
What this code is doing is letting the user create itself with not only the default attributes, but also a name.
You can use various validations on testing the format, the length, uniqueness, and others. For more validations consult the validations guide.
Displaying the user's name on the cases and updates
Let's start by showing the users' names instead of their emails.
Open the views/cases/index.html.erb
file, locate the following fragment:
<%= "Created by: " + c.user.email %>
and replace it with
Created by: <%= c.user.name %>
Do the same in views/cases/show.html.erb
, both for the case and for the updates sections.
When you refresh the pages, for some users, you will not see names. That's because the users who have posted the content were created before the change with the name. The simple solution is to delete the old cases and recreate them.
If you still want to keep the cases, you can open the rails console
in your terminal and run the following:
User.where(name: nil).each { |user| user.update_attributes(name: "User #{user.id}") }
This line just finds all the users who don't have a name, and going through each of the users, sets the name to User user_id.
Adding the user's page
We would like to see all the user's activity. Let's start by transforming the text within the label into a link. By clicking on this link we'll jump to the user's page, where we'd like to see the user's name and a list of posts: cases and updates.
First, let's turn the user's name into a link. The first question we have to answer is where does the link lead to, or what is the URL it points to. Open config/routes.rb
and add (after get "landing" => "pages#landing"
):
resources :users, only: [:show]
This tells the Rails routing system that there is a resource called User, for which only the show action will work. That is, Rails won't generate routes that let us create, edit or delete users. You can see what the URLs are by running
$ rake routes
in your terminal. The last line of the output should be the following:
user GET /users/:id(.:format) users#show
And it means:
- the generated path is user (we'll refer to it as user_path)
- the method is GET
- the URL will be /users/:id
- the controller will be users_controller and the action will be show.
Link to the user
Great, we've generated a route, let's add a link. Open app/views/cases/index.html.erb
and find the lines similar to:
<span class="label label-default">Created by: <%= c.user.name %></span>
and replace them with:
<span class="label label-default">
<%= link_to(c.user.name, user_path(c.user)) %>
</span>
Refresh the page and see the following:
The link color clashes with the default label style, so let's apply a quick fix:
<span class="label label-default">
<%= link_to(c.user.name, user_path(c.user), style: "color: white") %>
</span>
The labels are now white. Much better!
It is not a good practice, though, to include styles in the
html.erb
file. After we've finished prototyping, we will extract the styling to the associated.scss
files.
Apply the same logic to
app/views/cases/show.html.erb
, both for the case and updates section. Make sure to use the appropriate variable in the context (instead ofc
, you may need to use the instance variable@case
or the block variableu
, respectively).
Controller and action
Now, when you click on a user link, you will get the following error
This is what we see. Rails tries to find a UsersController
(as specified in the rake routes
output), but cannot find one.
Create a new file, users_controller.rb
under app/controllers
and paste the following in the newly created file:
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
end
end
This defines a show action that returns a user based on the ID provided in the URL. Refresh the page and look at the result:
When Rails says that a template is missing, we need to create one.
The view
The template (or the view) should be located under app/views/users/show.html.erb
. Create the users
folder, then the show.html.erb
file.
Since the file is empty, the user's page will be empty. Let's populate it with information.
We would like to display two blocks of information: the most recent updates and the most recent cases created by this user:
This is a quick solution that displays the user's recent activity:
<%= render "shared/internal_navbar" %>
<div class="container">
<h2><%= @user.name %></h2>
<h3>Recent updates:</h3>
<ul class="list-group">
<% @user.updates.last(20).each do |u| %>
<li class="list-group-item">
<span class="label label-primary"><%= u.domain %></span>
<%= link_to(u.case.title, u.case) %>: <%= u.title %>
</li>
<% end %>
</ul>
</div>
<div class="container">
<h3>Recent cases:</h3>
<% @user.cases.last(20).each do |c| %>
<div class="col-lg-4 col-md-4 col-sm-6 col-xs-12">
<div class="thumbnail", style="height: 500px; overflow: hidden;">
<div class="caption">
<h3><%= c.title %></h3>
</div>
<%= link_to(cl_image_tag(c.case_image.path, width: 500, class: 'img-responsive'), c) %>
</div>
</div>
<% end %>
</div>
And this is what it looks like: