Adding Profile pages
Every User on Twitter has their own Profile page which will show their own tweets and info. To create a profile page for our Users, we’ll have to generate a controller. The reason we’re doing this now is because Devise already took care of the login/registration methods in the background. Let’s generate an empty User controller.
rails g controller Users
Always remember that Rails typically uses a singular name for models, e.g., User, and plural names for controllers, like UsersController.
Next let’s add a route for our profile:
# routes.rbresources :users doget 'profile'end
We’ll need the user id, info and tweet count from our profile method. Let’s write it in users_controller.rb.
class UsersController < ApplicationControllerdef profile@userid = params[:user_id]@userinfo = User.find(params[:user_id])@usercount = @userinfo.tweets@tweets=Tweet.allendend
Now, the model and controller are taken care of, let’s add the view. Create a file called profile.html.erb in app/views/users and add the following:
<div><h1><%= @userinfo.email %></h1><h2>You have tweeted <%= @usercount.size %> time(s).</h2></div><div><% @tweets.reverse.each do |tweet| %><% if @userinfo == tweet.user %><p><tr><div class="tweet"><h3 class="black"><td><%= link_to tweet.user.email, user_profile_path(tweet.user) %></td></h3><h2><td><%= tweet.body %></td></h2><h3><td><%= tweet.created_at %></td></h3><span class="twitter-button"><%= link_to "Delete", tweet_path(tweet.id), :confirm => "Are you sure?", :method => :delete %></span></div></tr></p><% end %><% end %></div>
Here, if @userinfo == tweet.user checks the author of the tweet and only shows tweets of that user in the profile page.
Now, our profile page is ready, so let’s add the ‘My Profile’ link in our navbar. Add the following line after the new tweet link in application.html.erb:
<%= link_to 'My profile', user_profile_path(current_user.id), :class => 'navbar-link' %> |
Also, lets add the profile link in every tweet. Add the following line in index.html.erb instead of <%= tweet.user.email %>:
<h1><%= link_to tweet.user.email, user_profile_path(tweet.user) %></h1>
Adding Likes
The best part about Rails is we don’t have to write everything from scratch. There are tons of gems to help us out. One of them is acts_as_votable. Check out it’s documentation at https://github.com/ryanto/acts_as_votable.
Since we are building a Twitter clone, we want to add likes next. Using acts_as_votable makes it really easy to add likes/votes to a model. The gem creates a votes table to store the likes info.
Just add the following to your Gemfile:
gem 'acts_as_votable', '~> 0.11.1'
And follow that up with a bundle install.
Next, to generate and run the migration just run:
rails generate acts_as_votable:migrationrails db:migrate
Now, we need to tell the gem, which model is likable and which model can like them. So, add the following likes in tweet.rb and user.rb.
class Tweet < ApplicationRecordbelongs_to :useracts_as_votableend
For user.rb:
class User < ApplicationRecordhas_many :tweetsdevise :database_authenticatable, :registerable,:recoverable, :rememberable, :trackable, :validatableacts_as_voterend
Now, the gem can identify that Users can like Tweets. Next, let’s create methods and routes for likes and dislikes.
Add the following in your routes.rb:
resources :tweets domember doput "like", to: "tweets#like"put "dislike", to: "tweets#dislike"endend
Here, we’re adding two “PUT” routes, one for liking and one for disliking, with controller methods assigned to each of them. A member block is used when we need an extra route in our model, especially when the ID of the model is required. We’ll be needing the id of the tweet to either like or dislike the tweet.
Now let’s add the methods in tweets_controller.rb:
def like@tweet = Tweet.find(params[:id])@tweet.liked_by current_userredirect_to '/'enddef dislike@tweet = Tweet.find(params[:id])@tweet.disliked_by current_userredirect_to '/'end
liked_by and disliked_by are provided by acts_as_votable for easily updating the like count.
Now that the model and controller are taken care of, let’s add the view. We’ll need a heart image and the count of the likes beside it.
In our index.html.erb file, add the following before the delete button:
<% if current_user.liked? tweet %><%= link_to dislike_tweet_path(tweet.id), method: :put do %><%= image_tag("like.png", :alt => "Like", height: 18, width: 20) %><%= tweet.get_likes.size %><% end %><% else %><%= link_to like_tweet_path(tweet), method: :put do %><%= image_tag("dislike.png", :alt => "Like", height: 18, width: 20) %><%= tweet.get_likes.size %><% end %><% end %>
We’re using helpers from both the gems - Devise (to get the current user), and from Acts_As_Votable (get_likes.size - to get the count of likes from the User). We check if the current user has already liked it and then accordingly display the like or dislike icon.
Add the like button to the tweet format in users/profile.html.erb similarly.
Add the respective images in the app/assets/images folder:
dislike.png:
like.png:
Adding more Identity to our User model
Twitter identifies accounts with their usernames or handles instead of email-ids. So let’s add usernames and bios to our User model.
First let’s run some migrations:
rails g migration AddIdentityToUsers
Add the following lines in the migration files:
class AddIdentityToUsers < ActiveRecord::Migration[5.1]def changeadd_column :users, :username, :stringadd_column :users, :bio, :stringendend
Run the migration.
rails db:migrate
Devise allows you to completely change Devise defaults or invoke custom behavior by passing a block. Add the following in users_controller.rb.
class UsersController < ApplicationControllerdef profile@userid = params[:user_id]@userinfo = User.find(params[:user_id])@usercount = @userinfo.tweets@tweets=Tweet.allendbefore_filter :configure_permitted_parametersprotected# my custom fields are :name, :heard_howdef configure_permitted_parametersdevise_parameter_sanitizer.for(:sign_up) do |u|u.permit(:name, :heard_how, :email, :password, :password_confirmation)enddevise_parameter_sanitizer.for(:account_update) do |u|u.permit(:name, :email, :password, :password_confirmation, :current_password)endendend
Let's generate views we can customize.
rails generate devise:views users
Now, we generated the new views to work on. But devise will still use its own template to generate views. To let devise know to use our views, in the file config/initializers/devise.rb add the following block of code (this was line 232 for us)
config.scoped_views = true
Now, all our files have been generated. Let's edit the login page by replacing the email field with the following code.
app/views/users/sessions/new.html.erb
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %><div class="field"><%= f.label :username %><br /><%= f.text_field :username, autofocus: true, autocomplete: "username" %></div>
Let's update our registration page and edit profile page by adding the username and bio fields in it.
app/views/users/registrations/new.html.erb
<div class="field"><%= f.label :username %><br /><%= f.text_field :username, autofocus: true, autocomplete: "username" %></div><div class="field"><%= f.label :bio %><br /><%= f.text_field :bio, autocomplete: "bio" %></div>
app/views/users/registrations/edit.html.erb
<div class="field"><%= f.label :username %><br /><%= f.text_field :username, autofocus: true, autocomplete: "username" %></div><div class="field"><%= f.label :bio %><br /><%= f.text_field :bio, autocomplete: "bio" %></div>
We're all set. The only issue here is that our current login page asks for an email id to login. Add the following line in config/initializers/devise.rb to change it to username.
config.authentication_keys = [ :username ]
Now since we’ve collected the details, let’s display them. Add the following to profile.html.erb:
<h1><%= @userinfo.username %></h1><h3><%= @userinfo.bio %></h3><h2>You have tweeted <%= @usercount.size %> time(s).</h2>
Also change the following line in our tweet layouts in both our index.html.erb and profile.index.erb from:
<td><%= link_to tweet.user.email, user_profile_path(tweet.user) %></td>
to
<td><%= link_to tweet.user.username, user_profile_path(tweet.user) %></td>
And with this, we are done with our Twitter Clone Project.