Listing Users - Profile and Index of Profiles
By: Lukasz Muzyka, On:
This tutorial assumes you have already completed:
We allow our users to register now, however, users can not interact among each other yet. If we're considering any social or collaboration features we will have to at some point allow users to see each other's profiles. In this tutorial, we will work on creating users list and their individual pages.
- User's Index
- User's Profiles
Step 1: Create User's Index
Previously, we have created static pages controller.rb
. It contained some custom actions like "home" or "contact." In the case of our users, we will resort to a more standard approach that we called REST API. It really sounds scary when you read it for the first time, but in reality this is fairly simple concept. API stands for Application Programming Interface. REST API, is a standard way to ask our server for information (or send information to the server) by using URL. Not just any URL but a standard one. Tutorial about creating posts in blogging application tutorial has more information on this topic.
Step 1.2: Create Controller
We will start by creating controller to manipulate our users.
bash
$ rails generate controller users
create app/controllers/users_controller.rb
invoke erb
exist app/views/users
invoke test_unit
create test/controllers/users_controller_test.rb
invoke helper
create app/helpers/users_helper.rb
invoke test_unit
create test/helpers/users_helper_test.rb
invoke assets
invoke coffee
create app/assets/javascripts/users.js.coffee
invoke scss
create app/assets/stylesheets/users.css.scss
Let's open newly created controller and take a look:
app/controllers/users_controller.rb
class UsersController < ApplicationController
end
Step 1.3: Create Index Action
We will begin by creating index action to display all users registered at our website. Add the following changes:
app/controllers/users_controller.rb
class UsersController < ApplicationController
def index
@users = User.all
end
end
Step 1.4: Create HTML File
We can now proceed with creating HTML template for our index. Create new file inside app/views/users
and call it the same name as our action - index.html.erb
and add the following code:
app/views/users/index.html.erb
<ul>
<% @users.each do |user| %>
<li><%= user.email %></li>
<% end %>
</ul>
Step 1.5: Change Routes File
In order to be able to open this page, we need to modify our routes.rb
file so the application knows how to map URL and the controller:
config/routes.rb
devise_for :users
resources :users, only: [:index, :show]
#existing code
Start your application and go to the link http://localhost:3000/users
You should see list of all the users registered in your application.
Step 1.6: Add links
We also want to add links to menu to be able to reach index:
app/views/layouts/_menu.html.erb
<nav class="navbar navbar-default" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<%= link_to "Demo App", root_path, class: 'navbar-brand' %>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse-1">
<ul class="nav navbar-nav navbar-right">
<li><%= link_to 'Members', users_path %></li>
<% if current_user %>
<li><%= link_to 'Edit Profile',edit_user_registration_path %></li>
<li><%= link_to 'Logout', destroy_user_session_path, method: :delete %></li>
<li class="round-image-50"><%= image_tag(current_user.avatar.url(:thumb)) %></li>
<% else %>
<li><%= link_to 'Login', new_user_session_path %></li>
<% end %>
</ul>
</div>
</div>
</nav>
Step 2: Create User's Profiles
Now that we have created a way to see a list of all users, we want to see their actual profile. We will create a new action in the users controller and call it "show":
app/controllers/users_controller.rb
class UsersController < ApplicationController
# existing code
def show
@user = User.find(params[:id])
end
end
Step 2.2: Create Template
And create matching template inside app/views/users
:
app/views/users/show.html.erb
<h1><%= @user.email %></h1>
<ul>
<li>Name: <%= @user.name %></li>
<li>Date of birth:<%= @user.date_of_birth %></li>
</ul>
Step 2.3: Replace Index of emails
In order to easily reach this page we should turn our index of emails into a list of links leading to the profiles:
app/views/users/index.html.erb
<ul>
<% @users.each do |user| %>
<li><%= link_to user.email, user_path(user) %></li>
<% end %>
</ul>
Step 3: Refining
Everything should work by now. You might notice, however, that those profiles and index need a bit of polishing.
- Instead of using email we want to use name
- If some information is not present, we don't want to show it
- Instead of date of birth we want to display age
Let's tackle them one by one.
Step 3.2: Using name instead of email
When we created user with devise, we didn't use name from the very beginning. We added it later. We still don't require user to input their name. They may do it, but it's not mandatory. Therefore we're running into a risk, that when user has no name, we will have an empty spot. To mitigate that we will write "instance method" for user class
. Let's write code and discuss what it does:
app/models/user.rb
class User < ActiveRecord::Base
#existing code
def full_name
if self.name.blank?
self.email
else
self.name
end
end
end
With this method we can take "instance" of a User class (ex. current_user
) and run .full_name
command on it. This will return Name, if name is not blank, and if it is it will fallback to email. There is a more concise way of writing it by using "ternary operator". It can be a little confusing so before you use it, make sure you understand how it works. Also, using "self
" is optional, I use it for clarity. The code below is just an example.
app/models/user.rb
def full_name
name.blank? ? email : name
end
Step 3.3: Displaying only non-empty records
In user's profile we're now attempting to display both name and date of birth. But what if user didn't fill out this information? We need a way to hide those records, otherwise, our website will not be very presentable. Technically, we could write something like this:
app/views/users/show.html.erb
<h1><%= @user.email %></h1>
<ul>
<% unless @user.name.blank? %>
<li>Name: <%= @user.name %></li>
<% end %>
<% unless @user.date_of_birth.blank? %>
<li>Date of birth:<%= @user.date_of_birth %></li>
<% end %>
</ul>
Fortunately, we can write it in a more concise way using content_tag
:
app/views/users/show.html.erb
<h1><%= @user.email %></h1>
<ul>
<%= content_tag(:li, ("Name: " + @user.name)) unless @user.name.blank? %>
<%= content_tag(:li, ("Date of birth: " + @user.date_of_birth)) unless @user.date_of_birth.blank? %>
</ul>
We did that because we don't want to display the whole "li
" tag when content is empty.
Step 3.4: Displaying age instead of date of birth
We will use some "Rails Magic" here.
app/views/users/show.html.erb
<h1><%= @user.email %></h1>
<ul>
<%= content_tag(:li, ("Name: " + @user.name)) unless @user.name.blank? %>
<%= content_tag(:li, ("Age: " + distance_of_time_in_words(Time.now, @user.date_of_birth) + " old")) unless @user.date_of_birth.blank? %>
</ul>
Note how we use "distance_of_time_in_words(one_date, other_date)
"
This is pretty hard core use of Ruby inside of our HTML. It rarely gets more complex than that.
Step 4: Making it look nicer
I want to leave the design to you, but to kick-start your development process I will give you a starting point with some basic responsive design. Try to resize the window to see how it works.
app/views/users/index.html.erb
<div class="container">
<div class="users row">
<% @users.each do |user| %>
<div class="panel panel-default">
<%= link_to user_path(user) do %>
<div class="panel-body">
<%= image_tag(user.avatar.url(:medium)) %>
<p><%= user.full_name %></p>
</div>
<% end %>
</div>
<% end %>
</div>
</div>
app/views/users/show.html.erb
<div class="row profile">
<div class="col-sm-4 col-md-3">
<%= image_tag(@user.avatar.url(:medium), class: 'avatar') %>
<h1><%= @user.full_name %></h1>
<ul>
<%= content_tag(:li, ("Name: " + @user.name)) unless @user.name.blank? %>
<%= content_tag(:li, ("Age: " + distance_of_time_in_words(Time.now, @user.date_of_birth) + " old")) unless @user.date_of_birth.blank? %>
</ul>
</div>
<div class="feed">
<h3>Social Feed Coming</h3>
<p>Put a couple of lines of text here and see how it scrolls</p>
</div>
</div>
app/assets/stylesheets/custom.css.scss
// Columns
.users.row {
-moz-column-width: 15em;
-webkit-column-width: 15em;
-moz-column-gap: 0;
-webkit-column-gap: 0;
}
@media (min-width: 992px) {
.users.row {
-moz-column-width: 13.5em;
-webkit-column-width: 13.5em;
}
}
// users index
.feed {
width: 100%;
text-align: center;
}
.panel {
display: inline-block;
padding: 0;
margin: 0 0 10px 0;
width:98%;
img { width: 100%; }
}
// users show
.profile {
text-align: center;
.avatar { max-width: 100%; }
ul {
list-style: none;
padding: 0;
}
}
.users .panel-body {
padding: 0;
p {
margin: 10px;
overflow-x: scroll;
}
&:hover {
background-color: #fcfcfc;
box-shadow: 0 0 3px #b7b7b7;
}
}
@media (min-width: 768px) {
.feed {
text-align: center;
position: absolute;
right: 0;
top:53px;
bottom:0;
border-left: 1px dotted #e4e4e4;
width: 63%;
overflow-y: scroll;
padding-top: 50px;
}
.profile {text-align: left}
}
It should look pretty much like this:
Index of profiles
User's profile
Step 4.2: Add JQuery and Fix CSS
One more thing we need to remember here, is that, because we're using position:absolute;
for our feed, it will interfere with our flash messages that appear above the menu. We can easily fix that with a little bit of jQuery. Let's add a little code to the flash messages:
app/views/layouts/_flash_messages.html.erb
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %> alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<%= value %>
</div>
<% end %>
<script>
$('.alert').fadeOut(4000);
</script>
And fix the CSS:
app/assets/stylesheets/custom.css
$navbar-default-border: #e5e5e5;
$navbar-margin-bottom: 50px;
@import "bootstrap";
.field_with_errors { @extend .has-error; }
.alert-notice { @extend .alert-info; }
.alert-alert { @extend .alert-danger; }
.alert {
top: 50px;
left:0;
right: 0;
position:absolute;
}
// existing code
Comments
Comment
You can login to comment
On: Mohamed Saleem wrote:
On: Brunitob wrote:
There is a ";", you need to remove it. Then its perfect
}