Listing Users - Profile and Index of Profiles

By:, On:

This tutorial assumes you have already completed:

  1. Adding custom fields to devise

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

Index of users

User's profile

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

If you login you will be able to mark this tutorial as finished to track your progress



Comments

  • On: Mohamed Saleem wrote:

    This was one amazing tutorial for me. I have been searching something like this for a long time. Thanks you very much..
  • On: Brunitob wrote:

    There is a ";", you need to remove it. Then its perfect

    .users .panel-body {
    padding: 0;
    p {
        margin: 10px;
        overflow-x: scroll;
    }
    &;:hover {
        background-color: #fcfcfc;
        box-shadow: 0 0 3px #b7b7b7;
    }
    

    }

  • On: Brunitob wrote:

    Before hover.

  • On: Muzyka Bartek wrote:

    Thanks! Already fixed.

Comment

You can login to comment