Welcome to the next installment in our Twitter clone series! In this tutorial, we’ll build Ribbit from scratch, not using PHP, but with Ruby on Rails. Let’s get started!
One quick service announcement before we get started: we won’t be styling the UI for the application in this tutorial; that was done in Build a Twitter Clone From Scratch: The Design. I’ll let you know if we have to tweak anything from that article.
Step 0: Setting up the Environment
First things first: I’m using Ruby 1.9.3 (p194) and Rails version 3.2.8 for this tutorial. Make sure that you’re running the same versions. It doesn’t matter how you install Ruby; you can use RVM ( tutorial ), rbenv, or just a regular Ruby installation. No matter the approach, each installer gives you thegem
binary, which you can then use to install Rails. Just use this command:gem install railsThis installs the latest version of Rails, and we can start building our app now that it is installed. I hope you realize that Rails is a command line tool; you’ll need to be comfortable in the terminal to be comfortable in this tutorial.
Step 1: Creating the Rails App
We begin by generating the project. In the command line, navigate to whatever directory you want the new project to reside in. Then, run this:rails new ribbitAppThis single command generates multiple files inside a folder, called
ribbitApp
. This is what Rails gives us to start with; it even installed the gems required for the project.Let’s
cd
into that directory and initialize a git repo.cd ribbitApp git initOne of the Rail-generated files is
.gitignore
. If you’re on a Mac, you’ll probably want to add the following line to this file – just to keep things clean:**.DS_StoreNow, we’re ready to make our first commit!
git add . git commit -m 'initial rails app'
Step 2: Prepping the UI
The interface tutorial introduced you to Ribbit’s images and stylesheet. Download those assets and copy thegfx
folder and less.js
and style.less
files into your app’s public
directory.Let’s add a rule to
style.less
: the style for our flash messages. Paste this at the bottom of the file:.flash { padding: 10px; margin: 20px 0; &.error { background: #ffefef; color: #4c1717; border: 1px solid #4c1717; } &.warning { background: #ffe4c1; color: #79420d; border: 1px solid #79420d; } &.notice { background: #efffd7; color: #8ba015; border: 1px solid #8ba015; } }That’s all! Let’s make another commit:
git add . git commit -m 'Add flash styling'Now, let’s create the layout. This is the HTML that wraps the main content of every page – essentially, the header and footer. A Rails app stores this in
app/views/layouts/application.html.erb
. Get rid of everything in this file, and add the following code:<!DOCTYPE html> <html> <head> <link rel="stylesheet/less" href="/style.less"> <script src="/less.js"></script> </head> <body> <header> <div class="wrapper"> <img src="/gfx/logo.png"> <span>Twitter Clone</span> </div> </header> <div id="content"> <div class="wrapper"> <% flash.each do |name, msg| %> <%= content_tag :div, msg, class: "flash #{name}" %> <% end %> <%= yield %> </div> </div> <footer> <div class="wrapper"> Ribbit - A Twitter Clone Tutorial<img src="/gfx/logo-nettuts.png"> </div> </footer> </body> </html>There are three things you should notice about this. First, every URL to a public asset (images, stylesheet, JavaScript) begins with a forward slash (
/
). This is so that we can still load the assets when we’re in “deeper” routes. Second, notice the markup for displaying the flash messages. This displays flash messages when they exist. And third, note the <%= yield %>
; this is where we insert other “sub”-templates.Okay, let’s commit this:
git add . git commit -m 'Edit application.html.erb'
Step 3: Creating Users
Of course, we can’t have a Twitter clone without users, so let’s add that functionality. This can be a complex feature, but Rails makes it a little easier for us.We naturally don’t want to store our users’ passwords as plain text; that’s a huge security risk. Instead, we’ll rely on Rails to automate an encryption process for our passwords. We begin by opening our
Gemfile
and searching for these lines:# To use ActiveModel has_secure_password # gem 'bcrypt-ruby', '~> 3.0.0'Using
has_secure_password
is exactly what we want to do, so un-comment that second line. Save the file and head back to the command line. Now we have to run bundle install
to install the bcrypt gem.We can create our user resource, once the gem has been installed.
A Rails resource is basically a model, its associated controller, and a few other files.We create a resource by using the
rails generate
(or rails g
) command:rails generate resource user username name email passworddigest avatarurlWe pass several parameters to this command, resulting in a
resource
, called user
; its model has the following five fields:username
: a unique username, the equivalent of a Twitter handle.name
: the user’s actual name.email
: their email address.password_digest
: the encrypted version of their password.avatar_url
: the path to their avatar image.
rake db:migrateThis creates our
users
table, but we don’t directly interact with the database with Rails. Instead, we use the ActiveRecord ORM. We need to add some code to app/models/user.rb
. Open that file.When Rails generated this file, it added a call to the
attr_accessible
method. This method determines which properties are readable and writable on this class’ instances. By default, all the aforementioned properties are accessible, but we want to change that:attr_accessible :avatar_url, :email, :name, :password, :password_confirmation, :usernameMost of these should make sense, but what’s with
password
and password_confirmation
? After all, we have only a password_digest
field in our database. This is part of the Rails magic that I mentioned earlier. We can set a password
and password_confirmation
field on a User
instance. If they match, the password will be encrypted and stored in the database. But to enable this functionality, we need to add another line to our User
model class:has_secure_passwordNext, we want to incorporate validation into our model. Calling
has_secure_password
takes care of the password fields, so we’ll deal with the email
, username
, and name
fields.The
name
field is simple: we just want to ensure that it is present.validates :name, presence: trueFor the
username
field, we want to ensure that it exists and is unique; no two users can have the same username:validates :username, uniqueness: true, presence: trueFinally, the
email
field not only needs to exist and be unique, but it also needs to match a regular expression:validates :email, uniqueness: true, presence: true, format: { with: /^[\w.+-]+@([\w]+.)+\w+$/ }This is a very simplistic email regex, but it should do for our purposes.
Now, what about that
avatar_url
field? We want to use the user’s email address and pull their associated Gravatar, so we have to generate this URL. We could do this on the fly, but it will be more efficient to store it in the database. First, we need to make sure that we have a clean email address. Let’s add a method to our User
class:private def prep_email self.email = self.email.strip.downcase if self.email endThat
private
keyword means that all the methods defined after the keyword are defined as private methods; they cannot be accessed from outside the class (on instances).This prep_email
method trims the whitespace at the beginning and end of the string, and then converts all characters to lowercase.
This is necessary because we’re going to generate a hash for this value.We want this method to run just before the validation process; add the following line of code near the top of the class.
before_validation :prep_emailNext, let’s generate the URL for the avatar by writing another method (put it under the one above):
def create_avatar_url self.avatar_url = "http://www.gravatar.com/avatar/#{Digest::MD5.hexdigest(self.email)}?s=50" endWe need to call this method before we save the user to the database. Add this call to the top of the file:
before_save :create_avatar_urlAnd that’s it! Now we have the mechanics of our user functionality in place. Before writing the UI for creating users, let’s commit our work so far:
git add . git commit -m 'Create user resource'
Step 5: Writing The User UI
Building a UI in Rails means that we need to be aware of the routes that render our views. If you openconfig/routes.rb
, you’ll find a line like this:resources :usersThis was added to the
routes.rb
file when we generated the user resource, and it sets up the default REST routes. Right now, the route we’re interested in is the route that displays the form for creating new users: /users/new
. When someone goes to this route, the new
method on the users controller will execute, so that’s where we’ll start.The users controller is found in
app/controllers/users_controller.rb
. It has no methods by default, so let’s add the new
method within the UsersController
class.def new @user = User.new endAs you know, Ruby instance variables begin with
@
– making @user
available from inside our view. Let’s head over to the view by creating a file, named new.html.erb
in the app/views/users
folder. Here’s what goes in that view:<img src="/gfx/frog.jpg"> <div class="panel right"> <h1>New to Ribbit?</h1> <%= form_for @user do |f| %> <% if @user.errors.any? %> <ul> <% @user.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> <% end %> <%= f.text_field :email, placeholder: "email" %> <%= f.text_field :username, placeholder: "username" %> <%= f.text_field :name, placeholder: "name" %> <%= f.password_field :password, placeholder: "password" %> <%= f.password_field :password_confirmation, placeholder: "password" %> <%= f.submit "Create Account" %> <% end %> </div>This is actually the view that serves as the home page view, when a user is not logged in. We use the
form_for
helper method to create a form and pass it the @user
variable as a parameter. Then, inside the form (which is inside the Ruby block), we first print out errors. Of course, there won’t be any errors on the page the first time around.However, any input that fails our validation rules results in an error message that is displayed in a list item.Then, we have the fields for our user properties. Since this design doesn’t have any labels for the text boxes, I’ve put what would be label text as the fields’ placeholder (using the
placeholder
attribute). These won’t display in older browsers, but that’s not relevant to our main goal here.Now, what happens when the user clicks the “Create Account” button? This form will
POST
to the /users
route, resulting in the execution of the create
method in the users controller. Back to that controller, then:def create @user = User.new(params[:user]) if @user.save redirect_to @user, notice: "Thank you for signing up for Ribbit!" else render 'new' end endWe start by creating a new user, passing the
new
method the values from our form. Then, we call the save
method. This method first validates the input; if the data is in the correct format, the method inserts the record into the database and returns true
. Otherwise, it returns false
.IfThis actually redirects to the path for that user, which will be@user.save
returnstrue
, we redirect the viewer to… the@user
object itself?
/users/
. If @user.save
returns false, we re-render the /users/new
path and display any validation errors. We also pre-populate the form fields with the the user’s previously provided information. Clever, eh?Well, if we direct the viewer to their new user profile, we need to create that page next. This triggers the
show
method in the users controller, so we’ll add that first:def show @user = User.find(params[:id]) endThis method looks at the id number in the route (for example,
/users/4
) and finds the associated user in the database. Just as before, we can now use this @user
variable from the view.Create the
app/views/users/show.html.erb
file, and add the following code:<div id="createRibbit" class="panel right"> <h1>Create a Ribbit</h1> <p> <form> <textarea name="text" class="ribbitText"></textarea> <input type="submit" value="Ribbit!"> </form> </p> </div> <div id="ribbits" class="panel left"> <h1>Your Ribbit Profile</h1> <div class="ribbitWrapper"> <img class="avatar" src="<%= @user.avatar_url %>"> <span class="name"><%= @user.name %></span> @<%= @user.username %> <p> XX Ribbits <span class="spacing">XX Followers</span> <span class="spacing">XX Following</span> </p> </div> </div> <div class="panel left"> <h1>Your Ribbits</h1> <div class="ribbitWrapper"> Ribbits coming . . . </div> </div>You’ll notice that we have a few placeholders in this view. First, there’s the form for creating a new ribbit. Then there’s the follower, following numbers, and the list of your ribbits. We’ll come to all this soon.
There’s one more thing to do in this step: we want the root route (
/
) to show the new user form for the time being. Open the config/routes.rb
file again, and add this line:root to: 'users#new'This simply makes the root route call the
new
method in the users controller. Now, we just need to delete the public/index.html
file which overrides this configuration. After you delete that, run the following in the command line:rails serverYou could also run
rails s
to achieve the same results. As you would expect, this starts the rails server. You can now point your browser to localhost:3000/
, and you should see the following:Now, fill in the form and click “Create Account.” You should be sent to the user profile, like this:
Great! Now we have our user accounts working. Let’s commit this:
git add . git commit -m 'User form and profile pages'
Step 6: Adding Session Support
Even though we implemented the user feature, a user cannot log in just yet. So, let’s add session support next.You may have recognized the way we’ve created user accounts.I’ve taken this general method from Railscast episode 250. That episode also demonstrates how to create session support, and I’ll use that approach for Ribbit.
We start by creating a controller to manage our sessions. We won’t actually store sessions in the database, but we do need to be able to set and unset session variables. A controller is the correct way to do that.
rails generate controller sessions new create destroyHere, we create a new controller, called
sessions
. We also tell it to generate the new
, create
, and destroy
methods. Of course, it won’t fill in these methods, but it will create their “shell” for us.Now, let’s open the
app/controllers/sessions_controller.rb
file. The new
method is fine as is, but the create
method needs some attention. This method executes after the user enters their credentials and clicks “Log In.” Add the following code:def create user = User.find_by_username(params[:username]) if user && user.authenticate(params[:password]) session[:userid] = user.id redirect_to rooturl, notice: "Logged in!" else flash[:error] = "Wrong Username or Password." redirect_to root_url end endWe use the
find_by_username
method on the User
class to retrieve the user with the provided username. Then, we call the authenticate
method, passing it the password. This method was added as part of the use_secure_password
feature. If the user’s credentials pass muster, we can set the user_id
session variable to the user’s ID. Finally, we redirect to the root route with the message “Logged in!”.If the user’s credentials fail to authenticate, we simply redirect to the root route and set the flash error message to “Username or password was wrong.“
Logging out fires the
destroy
method. It’s a really simple method:def destroy session[:userid] = nil redirect_to root_url, notice: "Logged out." endThis code is fairly self-explanatory; just get rid of that session variable and redirect to the root.
Rails helped us out once again and added three routes for these methods, found in
config/routes.rb
:get "sessions/new" get "sessions/create" get "sessions/destroy"We want to change the
sessions/create
route to POST, like this:post "sessions/create"We’re almost ready to add the login form to our views. But first, let’s create a helper method that allows us to quickly retrieve the currently logged-in user. We’ll put this in the application controller so that we can access it from any view file. The path to the application controller is
app/controllers/application_controller.rb
.private def current_user @current_user ||= User.find(session[:user_id]) if session[:user_id] end helper_method :current_userWe pass
User.find
the session[:user_id]
variable that we set in the sessions controller. The call to helper_method
is what makes this a helper method that we can call from the view.Now, we can open our
app/views/layouts/application.html.erb
file and add the login form. See the <span>Twitter Clone</span>
in the <header>
element? The following code goes right after that:<% if current_user %> <%= link_to "Log Out", sessions_destroy_path %> <% else %> <%= form_tag sessions_create_path do %> <%= text_field_tag :username, nil, placeholder: "username" %> <%= password_field_tag :password, nil, placeholder: "password" %> <%= submit_tag "Log In" %> <% end %> <% end %>If the user is logged in (or, if a non-
nil
value is returned from current_user
), we’ll display a logout link, but we’ll add more links here later. You might not have seen the link_to
method before; it takes the provided text and URL and generates a hyperlink.If no user is logged in, we use theNote that we can’t use theform_tag
method to create a form that posts to theseesions_create_path
.
form_for
method because we don’t have an instance object for this form (like with our user object). The text_field_tag
and password_field_tag
methods accept the same parameters: the name for the field as a symbol, the value for the field (nil
in this case), and then an options object. We’re just setting a placeholder value here.There’s a bit of a glitch in our session support: a user is not automatically logged in, after they create a new user account. We can fix this by adding a single line to the
create
method in the UserController
class. Right after the if @user.save
line, add:session[:user_id] = @user.idBelieve it or not, the above line of code finishes the session feature. You should now be able to re-start the Rails server and log in. Try to log out and back in again. The only difference between the two functions is the lack of the login form when you’re logged in. But we’ll add more later!
Let’s commit this:
git add . git commit -m 'users are now logged in upon creation'
Step 7: Creating Ribbits
Now we’re finally ready to get to the point of our application: creating Ribbits (our version of tweets). We begin by creating the ribbit resource:rails g resource ribbit content:text user_id:integer rake db:migrateThis resource only needs two fields: the actual content of the ribbit, and the id of the user who created it. We’ll migrate the database to create the new table. Then, we’ll make a few modifications to the new Ribbit model. Open
app/models/ribbit.rb
and add the following:class Ribbit < ActiveRecord::Base default_scope order: 'createdat DESC' attr_accessible :content, :userid belongs_to :user validates :content, length: { maximum: 140 } endThe
default_scope
call is important; it orders a list of ribbits in from the most recent to least recent. The belongs_to
method creates an association between this Ribbit class and the User class, making our user objects have a tweets
array as a property.Finally, we have a
validates
call, which ensures that our ribbits don’t exceed 140 characters.Oh, yeah: the flip side of the
belongs_to
statement. In the User class (app/models/user.rb
), we want to add this line:has_many :ribbitsThis completes the association; now each user can have many ribbits.
We want users to have the ability to create ribbits from their profile page. As you’ll recall, we have a form in that template. So let’s replace that form in
app/view/users/show.html.erb
, as well as make a few other changes. Here’s what you should end up with:<% if current_user %> <div id="createRibbit" class="panel right"> <h1>Create a Ribbit</h1> <p> <%= form_for @ribbit do |f| %> <%= f.textarea :content, class: 'ribbitText' %> <%= f.submit "Ribbit!" %> <% end %> </p> </div> <% end %> <div id="ribbits" class="panel left"> <h1>Your Ribbit Profile</h1> <div class="ribbitWrapper"> <img class="avatar" src="<%= @user.avatarurl %>"> <span class="name"><%= @user.name %></span> @<%= @user.username %> <p> <%= @user.ribbits.size %> Ribbits <span class="spacing">XX Followers</span> <span class="spacing">XX Following</span> </p> </div> </div> <div class="panel left"> <h1>Your Ribbits</h1> <% @user.ribbits.each do |ribbit| %> <div class="ribbitWrapper"> <img class="avatar" src="<%= @user.avatar_url %>"> <span class="name"><%= @user.name %></span> @<%= @user.username %> <span class="time"><%= time_ago_in_words(ribbit.created_at) %></span> <p> <%= ribbit.content %> </p> </div> <% end %> </div>There are three areas that we change with this code. First, we remove the HTML form at the top and replace it with a call to the
form_for
helper function. Of course, it makes sense that we only need a text area for the content (we already know the current user’s ID). Note that form_for
accepts a @ribbit
as the parameter, and we need to add that object to the users_controller#show
method (app/controllers/users_controller.rb
):def show @user = User.find(params[:id]) @ribbit = Ribbit.new endNotice that we wrap the whole form section (the
<div id="createRibbit">
) with an if
statement. If there’s no current user (meaning no one is logged in), we won’t show the ribbit form.Next, we want to display the number of the user’s ribbits. That number appears just above their follower count. Remember that our user instance has a
ribbits
property. So, we can replace our filler text with this:<%= @user.ribbits.size %> RibbitsWe need to show the user’s ribbits. We can loop over that same
ribbits
array, and display each ribbit in turn. That’s the final part of the code above.Lastly, (at least as far as ribbit creation is concerned), we need to modify the
create
method in the ribbits controller (app/controllers/ribbits_controller.rb
. The method executes when the user clicks the “Ribbit!” button.def create @ribbit = Ribbit.new(params[:ribbit]) @ribbit.userid = current_user.id</p> if @ribbit.save redirect_to current_user else flash[:error] = "Problem!" redirect_to current_user end endI know the “Problem!” error message isn’t very descriptive, but it will do for our simple application. Really, the only error that could occur is a ribbit longer than 140 characters.
So, give it a try: start the server (
rails server
), log in, go to your profile page (http://localhost:3000/users/
, but of course, any user profile page will do), write a ribbit, and click “Ribbit!”. The new ribbit should display in the ribbit list on your profile page.Okay, let’s commit these changes:
git add . git commit -m 'ribbit functionality created'
Step 8: Creating the Public Tweets Page
Next up, we want to create a public page that includes all the ribbits made by all users. Logically, that should be the ribbits index view, found at/ribbits
. The controller method for this is ribbits_controler#index
. It’s actually a very simple method:def index @ribbits = Ribbit.all include: :user @ribbit = Ribbit.new endThe first line fetches all the ribbits and their associated users, and the second line creates the new ribbit instance (this page will have a ribbit form).
The other step, of course, is the template (
app/view/ribbits/index.html.erb
). It’s similar to the user profile template:<% if current_user %> <div id="createRibbit" class="panel right"> <h1>Create a Ribbit</h1> <p> <%= form_for @ribbit do |f| %> <%= f.textarea :content, class: 'ribbitText' %> <%= f.submit "Ribbit!" %> <% end %> </p> </div> <% end %> <div class="panel left"> <h1>Public Ribbits</h1> <% @ribbits.each do |ribbit| %> <div class="ribbitWrapper"> <a href="<%= user_path ribbit.user %>"> <img class="avatar" src="<%= ribbit.user.avatar_url %>"> <span class="name"><%= ribbit.user.name %></span> </a> @<%= ribbit.user.username %> <span class="time"><%= time_ago_in_words(ribbit.created_at) %></span> <p> <%= ribbit.content %> </p> </div> <% end %> </div>In this template, the avatar image and the user’s name are surrounded by a link that points to the user’s profile page. Let’s also add a link to the public tweets page. Just before the logout link in
app/view/layouts/application.html.erb
, add this:<%= link_to "Public Ribbits", ribbits_path %>Finally:
git add . git commit -m 'added the public ribbits page'
Step 9: Following Other Users
It wouldn’t be a Twitter clone if we couldn’t follow other users, so let’s work on that feature next.This is a little tricky at first. Think about it: our
User
records need to to follow other User
records and be followed by other User
records. It’s a many-to-many, self-joining association. This means we’ll need an association class, and we’ll call it “Relationship”. Start by creating this resource:rails g resource relationship follower_id:integer followed_id:integer rake db:migrateThis model only needs two fields: the follower’s ID, and the followed’s ID (note: the terminology here can get a little confusing. In our case, I’m using the term “followed” to mean the user being, well, followed).
Next up, we want to relate the
User
model with this Relationship
model, which we do from both sides. First, in the Relationship
class (app/models/relationship.rb
), we want to add these two lines:belongs_to :follower, classname: "User" belongs_to :followed, classname: "User"The first line relates a
User
record to the follower_id
field, and the second line relates a User
record to the followed_id
field. It’s important to include the class name, because Rails can’t infer the class from the property names (‘follower’ and ‘followed’). It can, however, infer the correct database fields (follower_id
and followed_id
) from those names.Now, in the
User
class (app/model/user.rb
), we have to first connect each user model to its associated relationships:has_many :follower_relationships, classname: "Relationship", foreign_key: "followed_id" has_many :followed_relationships, classname: "Relationship", foreign_key: "follower_id"We need to create two associations, because we have two sets of relationships per user: all the people following them and all the people they follow. And no, those foreign keys shouldn’t be switched. The
follower_relationship
association is responsible for all of your followers. Hence, it needs the followed_id
foreign key.Then, we can use those relationships to get to the followers on the other side of them:
has_many :followers, through: :follower_relationships has_many :followeds, through: :followed_relationshipsThese give our user records the
followers
and followeds
methods. They’re both methods that return the arrays of our followers or the people we follow, respectively.Finally, let’s add two methods to our user model that helps us with the UI:
def following? user self.followeds.include? user end def follow user Relationship.create follower_id: self.id, followed_id: user.id endLet’s commit these changes before tweaking the UI.
git add . git commit -m 'created user relationships infrastructure'Now, the UI is all on the user profile pages, which is
app/views/users/show.html.erb
. We’ll start with something simple: the follower and following count. See where we have this?<span class="spacing">XX Followers</span> <span class="spacing">XX Following</span>We’ll replace these placeholder values, like so:
<span class="spacing"><%= @user.followers.count %> Followers</span> <span class="spacing"><%= @user.followeds.count %> Following</span>We have the follow/unfollow button under these counts, but there are a few states we need to consider. First, we don’t want to show any button if the user either is viewing their own profile or if they’re not logged in. Second, we want to display an “Unfollow” button if the user already follows this profile’s owner.
<% if current_user and @user != current_user %> <% if current_user.following? @user %> <%= form_tag relationship_path, method: :delete do %> <%= submit_tag "Unfollow" %> <% end %> <% else %> <%= form_for @relationship do %> <%= hidden_field_tag :followed_id, @user.id %> <%= submit_tag "Follow" %> <% end %> <% end %> <% end %>
A Rails resource is a basically a model, its associated controller, and a few other files.Put this code just under the paragraph that holds the above spans.
The forms are the more complex parts here. First, if the current user already follows the viewed user, we’ll use
form_tag
to create a form that goes to the relationship_path
. Of course, we can’t forget to set the method
as delete
because we’re deleting a relationship.If the current user doesn’t follow the viewed user, we’ll create a
form_for
the current relationship. We’ll simply use a hidden field to determine which user to follow.If you’re paying attention, you’ll know that something’s missing: the ability to manipulate a relationship instance from this view. We need a
Relationship
instance. If the current user doesn’t already follow this user, we need to create a blank relationship. Otherwise, we need to have a relationship on hand to delete! Back to app/controllers/users_controller.rb
, and add the following to the show
method:@relationship = Relationship.where( follower_id: current_user.id, followed_id: @user.id ).first_or_initialize if current_userThis is a bit different from the usual way of finding or creating a record. This initializes a blank
Relationship
instance if no records are found that match the where
parameters. Of course, we only want to do this if there is a current_user
.The routes for this model are enabled by the
resources :relationship
line in the config/routes.rb
, so we don’t have to worry about that.Now, in
app/controllers/relationships_controller.rb
, we’ll start with the new
method:def create @relationship = Relationship.new @relationship.followed_id = params[:followed_id] @relationship.follower_id = current_user.id</p> if @relationship.save redirect_to User.find params[:followed_id] else flash[:error] = "Couldn't Follow" redirect_to root_url end endPretty standard stuff by now, right? We’ll create the relationship, save it, and redirect back to the user’s profile.
The
destroy
method is also simple:def destroy @relationship = Relationship.find(params[:id]) @relationship.destroy redirect_to user_path params[:id] endNow, create another user (or four) and have a few users follow other users. You should see the text of the follow buttons change, as well as the follower / following count.
Great! Now we can commit this feature:
git add . git commit -m 'Following other users is now working'
Step 10: Creating a Few Other Pages
There are a few other simple pages that we want to add. First, let’s create a page to list all the registered users. This would be a great place to find new friends, see their pages, and eventually follow them. Logically, this should be the/users
route, so we’ll use the UsersController#index
method:def index @users = User.all endNow, for
app/views/users/index.html.erb
:<div id="ribbits" class="panel left"> <h1>Public Profile</h1> <% @users.each do |user| %> <div class="ribbitWrapper"> <a href="<%= user_path user %>"> <img class="avatar" src="<%= user.avatar_url %>"> <span class="name"><%= user.name %></span> </a> @<%= user.username %> <p> <%= user.ribbits.size %> Ribbits <span class="spacing"><%= user.followers.count %> Followers</span> <span class="spacing"><%= user.followeds.count %> Following</span> </p> <% if user.ribbits.first %> <p><%= user.ribbits.first.content %></p> <% end %> </div> <% end %> </div>Finally, let’s add a link to this page to the top of our template. Let’s also add a link to the logged-in user’s profile. Right beside the “Public Ribbits” link, add:
<%= link_to "Public Profiles", users_path %> <%= link_to "My Profile", current_user %>Next is the buddies page. This is where a user goes to view the ribbits of the people they follow; we’ll also redirect users to this page when they’re logged in and view the home page.
Strangely, finding the correct place in the code for this page is a bit tricky. After all, each page in our Rails app must be based on a method in one of our controllers. Best practice dictates that each controller has six REST methods that control a resource. In this case, we want to view the ribbits of a subset of users, which, at least to me, seems to be a bit of an edge case. Here’s how we’ll handle it: let’s create a
buddies
method in the UsersController
:def buddies if current_user @ribbit = Ribbit.new buddies_ids = current_user.followeds.map(&:id).push(current_user.id) @ribbits = Ribbit.find_all_by_user_id buddies_ids else redirect_to root_url end endObviously, there’s nothing to show if a user isn’t logged in, so we’ll check the
current_user
. If we’re not logged in, we redirect to the root URL (/
). Otherwise, we create a new ribbit (for our new ribbit form).We then need to find all ribbits from the current user and the people they follow.We can use the
followeds
array property, and map it to only retrieve the user ids. Then, we push in the current user’s ids as well and finally retrieve the ribbits from those users.Let’s store the template in
app/views/users/buddies.html.erb
; it’s very similar to our public ribbits template:<% if current_user %> <div id="createRibbit" class="panel right"> <h1>Create a Ribbit</h1> <p> <%= form_for @ribbit do |f| %> <%= f.textarea :content, class: 'ribbitText' %> <%= f.submit "Ribbit!" %> <% end %> </p> </div> <% end %> <div class="panel left"> <h1>Buddies' Ribbits</h1> <% @ribbits.each do |ribbit| %> <div class="ribbitWrapper"> <a href="<%= user_path ribbit.user %>"> <img class="avatar" src="<%= ribbit.user.avatar_url %>"> <span class="name"><%= ribbit.user.name %></span> </a> @<%= ribbit.user.username %> <span class="time"><%= time_ago_in_words(ribbit.created_at) %></span> <p> <%= ribbit.content %> </p> </div> <% end %> </div>We need to make a route for this method, in order to use it. Open
config/routes.rb
and add the following:get 'buddies', to: 'users#buddies', as: 'buddies'Now, we can go to
/buddies
and see the page!There’s something else we want to do with this, however. If a logged-in user goes to the root URL, we need to redirect them to
/buddies
. Remember, this route is currently:def new @user = User.new endLet’s change it to this:
def new if current_user redirect_to buddies_path else @user = User.new end endWe should also add a link to the buddies page in
app/views/layouts/application.html.erb
:<%= link_to "Buddies' Ribbits", buddies_path %>And now, we’ll commit these changes:
git add . git commit -m 'added buddies page'
Step 11 Deploying to Heroku
The last step is to deploy the application. We’ll use Heroku.The last step is to deploy the application. We’ll use Heroku. I’m going to assume that you have a Heroku account, and that you’ve installed the Heroku toolbelt (the command line tools).
We run into a problem before we even begin! We’ve been using a SQLite database, because Rails uses SQLite by default. However, Heroku doesn’t use SQLite; it uses PostgreSQL for the database. We have to make a change to our Gemfile, a change that actually breaks our local copy of the app (unless you install and configure a PostgreSQL server). Here’s my compromise: I’ll show you how to do it here, and you can play with my deployed version. But you don’t have make the change on your local project.
Thankfully, switching Rails to PostgreSQL is very simple. In our
Gemfile
, there’s a line that looks like this:gem "sqlite"Change that line to this:
gem "pg"We now must install this gem locally, in order to update
Gemfile.lock
. We do this by running:bundle installAnd we commit:
git add . git commit -m 'updated Gemfile with Postgres'We can now create our Heroku application. In our project directory, run:
heroku create git push heroku masterAnd finally:
heroku openThat opens your browser with your deployed Heroku application. You can play with my deployed copy.
That’s It!
And there you go! We just built a really simple Twitter clone. Sure, there are dozens of features we could add to this, but we nailed the most important pieces: users, ribbits, and followings.Remember, if Rails isn’t your racket, check out the other tutorials in this series! We have a whole line-up of Ribbit tutorials using different languages and frameworks in the pipes. Stay tuned!
No comments:
Post a Comment