Building Ribbit in Rails

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!

Example

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 the gem binary, which you can then use to install Rails. Just use this command:

This 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:

This 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.

One 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:

Now, we're ready to make our first commit!


Step 2: Prepping the UI

The interface tutorial introduced you to Ribbit's images and stylesheet. Download those assets and copy the gfx 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:

That's all! Let's make another commit:

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:

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:


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:

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:

We 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.

We now need to migrate our database so that it's set up to store users. We accomplish this by running the following command:

This 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:

Most 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:

Next, 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.

For the username field, we want to ensure that it exists and is unique; no two users can have the same username:

Finally, the email field not only needs to exist and be unique, but it also needs to match a regular expression:

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:

That 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.

Next, let's generate the URL for the avatar by writing another method (put it under the one above):

We need to call this method before we save the user to the database. Add this call to the top of the file:

And 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:


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 open config/routes.rb, you'll find a line like this:

This 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.

As 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:

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:

We 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.

If @user.save returns true, we redirect the viewer to... the @user object itself?

This actually redirects to the path for that user, which will be /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:

This 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:

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:

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:

You 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:


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.

Here, 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:

We 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:

This 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:

We want to change the sessions/create route to POST, like this:

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.

We 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 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 the form_tag method to create a form that posts to the seesions_create_path.

Note that we can't use the 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:

Believe 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:


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:

This 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:

The 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:

This 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:

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):

Notice 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:

We 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.

I 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:


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:

The 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:

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:

Finally:


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:

This 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:

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:

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:

These 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:

Let's commit these changes before tweaking the UI.

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?

We'll replace these placeholder values, like so:

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.

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:

This 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:

Pretty 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:

Now, 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:


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:

Now, for app/views/users/index.html.erb:

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:

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:

Obviously, 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:

We need to make a route for this method, in order to use it. Open config/routes.rb and add the following:

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:

Let's change it to this:

We should also add a link to the buddies page in app/views/layouts/application.html.erb:

And now, we'll commit these changes:


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:

Change that line to this:

We now must install this gem locally, in order to update Gemfile.lock. We do this by running:

And we commit:

We can now create our Heroku application. In our project directory, run:

And finally:

That 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!

Tags:

Comments

Related Articles