Building Ribbit in Rails

Building Ribbit in Rails

Tutorial Details
  • Difficulty: Intermediate
  • Completion Time: 2 Hours

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:

    gem install rails

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:

    rails new ribbitApp

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.

    cd ribbitApp
    git init

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:

    **.DS_Store

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

    .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 password_digest avatar_url

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:

    rake db:migrate

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:

    attr_accessible :avatar_url, :email, :name, :password, :password_confirmation, :username

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:

    has_secure_password

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.

    validates :name, presence: true

For 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: true

Finally, 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
end

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.

    before_validation :prep_email

Next, 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"
    end

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

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:

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

    resources :users

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.

    def new
        @user = User.new
    end

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:

<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
end

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:

def show
  @user = User.find(params[:id])
end

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:

    <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 server

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:

    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 destroy

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:

    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
    end

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:

    def destroy
        session[:userid] = nil
        redirect_to root_url, notice: "Logged out."
    end

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:

    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_user

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

    session[:user_id] = @user.id

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:

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

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:

class Ribbit < ActiveRecord::Base
  default_scope order: 'createdat DESC'
  attr_accessible :content, :userid
  belongs_to :user

  validates :content, length: { maximum: 140 }
end

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:

    has_many :ribbits

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:

    <% 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.avatar_url %>">
            <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
    end

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:

    <%= @user.ribbits.size %> Ribbits

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.

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
end

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:

    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
    end

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:

    <% 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:migrate

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:

    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_relationships

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:

def following? user
    self.followeds.include? user
end

def follow user
    Relationship.create follower_id: self.id, followed_id: user.id
end

Let’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_user

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:

    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
end

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:

    def destroy
        @relationship = Relationship.find(params[:id])
        @relationship.destroy
        redirect_to user_path params[:id]
    end

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:

    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
    end

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

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:

    <% 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
    end

Let’s change it to this:

    def new
        if current_user
            redirect_to buddies_path
        else
            @user = User.new
        end
    end

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

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

And finally:

    heroku open

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: ribbit
Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://brocknunn.com Brock Nunn

    Super nice tutorial Andrew, getting a bit more into backend through rails is a resolution of sorts for me this year. Are you going to be setting ribbit up in laravel? In that, how do you feel rails and laravel will coexist in the coming years?

    • andrew8088
      Author

      I believe so, but another staff member is writing it.

      To be honest, I haven’t really looked into Laravel too deeply, but I’m sure there will always be devs preferring Ruby or PHP, so I don’t think there’ll will be too much conflict (well, at least not more than usual :) ).

    • Farhad Ghayour

      +1! I really hope Tuts+ guys get the Laravel version of this tutorial up soon, as I, too, am in the same situation as you.

    • jeff_way

      I’ll do a Laravel version at some point.

      • http://twitter.com/M_saqib Muhammad Saqib

        why not python/Django version :)

  • Binary_Star

    Great Tut! While I’m not a Ruby dev, it’s good to see this type of post. I’d really enjoy a Python/Flask or Python/Django version of this tut. Keep up the good work guys!

    • larryg968

      Im really looking forward to the django version as well as a django course on tuts premium

  • http://www.facebook.com/cmeiring Constant Meiring

    if you just change the sqlite gem to pg, your local dev environment will stop working if you don’t set up Postgres first. You would rather want to do something like the following in your gem file:

    group :development, :test do
    gem ‘sqlite3′
    end

    group : production do
    gem ‘pg’
    end

    • andrew8088
      Author

      Thanks! I feel kinda silly now for having forgotten about that.

  • http://twitter.com/erikvieg Erik Vieg

    In deploying, you could introduce the idea of groups :development & :production. This would allow them to use both local development environment and Heroku.

  • Victor Bastos

    Andrew, this serie will have a Sinatra version?

    • andrew8088
      Author

      I believe so, but another staff member is writing it.

      • Victor Bastos

        José Mota maybe?

    • Hey

      I want to know about more Sinatra tips, too.

  • ingo fahrentholz

    wohooo finally good new rails tuts :D

  • moiz

    Really enjoyed the tutorial.

    I’m from .NET background, but the syntax of ruby ad rails has amazed me.

    One small request, if possible also publish a version of this tutorial with ASP.NET MVC 4.

    Thanks and regards.

  • Yanek

    Ruby has the ugliest syntax I have ever seen.

    • Jacob Krustchinsky

      Well seeing that a large number of developers agree this is some of the most compact and cleanest syntax out there, I’ll take what you say with a grain of salt.

    • kuxharinxha

      hahahhahahahahahhaha this is the funnies thing i have ever heard in my entire life.

    • http://twitter.com/JonnoTheBonno John Crossley

      I’m a php developer so i have been used to the c style syntax but i actually like ruby, it’s very expressive and nice to use.

    • Ian Simmons

      Maybe it’s a matter of what your used to. I’m finding it hard to get used to because I like curly braces and semi colons giving visual structure to my code and I’d rather use {{}} in my templates (those percent signs make my eyes strain) but it’s starting to look nicer the more I get used to it.

    • Won Word

      LOL. You need to get out and see the world more.

      Here’s a little perl program I whipped up. How do you like its syntax?


      use strict;
      use Acme::Buffy;

      BUffY bUFFY BUffY bUFFY bUfFy buFfY BUFfY buFFY BuFfy Buffy bUffY BuFFY Buffy BufFy BUFfy BUfFY BufFy BufFY bUFfY BufFY BUffY BuFFy BUffy bUffy BuFfY BuFFy BUFfY BufFY buffy buFFy BUFfy BuFFY bUffY BuFfy bUFfY bufFY buFfY BuFFy BUffy bUffy bUfFy buffy BUFfy buFFY BufFy BufFY bUFfy BuFFY buffy bufFy bUffy BUFfY bUFFy BuffY BuFfy BUfFY buFfY Buffy bUFfY bUfFY BUffY Buffy bUffY bUfFY BUffY Buffy bUffY bufFY buffy buffY BUFfY BufFY BUffY BuffY BUFfy BUfFy buffy buffY BuFfy bUfFY BufFy BuFFy BUFfy bufFy BufFy BuFFy BUFfy bufFy BUFFy BufFY bUFfy BUfFY BufFY Buffy bUffY bufFY buffy buffY BUFfY BufFY BUffY BuffY BUFfy BUfFy buFFY BufFY bUFfy bufFy BUfFY bufFy buffy bUffy buffY BufFy BUFfY bUfFY bUFFy BuffY BUFfy bufFy BuffY buffy bUffY bUfFy buffy buFfy BUffY bUFFy bUfFy buffy BUFfy buFFY BufFy BufFY bUFfy BuFFY buffy bufFy bUffy BUFfY bUFFy BuffY BuFfy BUfFY bUffy buFFy BUffy bUffy

  • http://twitter.com/erikvieg Erik Vieg

    The initial User generate is missing the underscores for passworddigest (should be password_digest) and avatarurl (should be avatar_url):

    rails generate resource user username name email password_digest avatar_url

    • jeff_way

      Fixed

  • http://twitter.com/erikvieg Erik Vieg

    Not sure why my comment got deleted, but the rails generate command is wrong. The correct fields are labelled in the list below but the command itself is wrong. It is missing the underscores for password_digest and avatar_url:

    rails generate resource user username name email passworddigest avatarurl

    should be:

    rails generate resource user username name email password_digest avatar_url

    • andrew8088
      Author

      Ah, thanks! I’m guessing the underscores were removed when we converted this from Markdown to HTML.

  • http://www.facebook.com/gustavo.guichard Gustavo Guichard

    Wow, thanks Andrew! You’ve just covered the two things I was missing the most in net.tuts lately:
    - Deeply explained Tutorials
    - Non-php universe
    That made me feel like loving net.tuts again!

    Cheers!

  • http://www.jsxtech.com Jaspal Singh aka jsxtech

    Great! waiting for django/python version.

  • pixelBender67

    This is awesome, and a great follow up to the PHP version, lets do a python or C# version next :D

  • Gabriele

    Please, Node Js study case!

  • http://www.webmaster-source.com redwall_hp

    I haven’t had time to read either post thoroughly and follow along, but I like the idea of having several tutorials building the same app with different languages and frameworks. I assume a Python/Django one is in the works?

  • http://twitter.com/jeff_pz_cr Jeffrey Briceno

    Nice this is my kick start for learn finally RoR

  • whoisandie

    Thats a good one ! Would be waiting for the laravel version as well :)

  • pippo

    i tried the demo and the email is alwais invalid :(

    • andrew8088
      Author

      Hmm, that’s strange; I made the email regex pretty general, I though. You can email me at andrew8088 AT gmail DOT com and I’ll change the regex to work with your address.

  • MPinteractiv

    That’s cheating , the php version did not use a framework and made the php code look insecure and sloppy , would have been fair to use raw ruby cgi to code that app , php version came with 0 battery included.

    • http://twitter.com/IncendiaryMedia Fire

      That’s the basic php version, the Laravel ( PHP Framework ) version of this guide is still coming

      • Chuck

        So we need a raw ruby version, haha

  • BaylorRae

    For those interested in how polymorphic routes work in Rails, Ryan Bigg wrote an article explaining it.

    http://ryanbigg.com/2012/03/polymorphic-routes/

  • Lucky Bhumkar

    Oh! it’s too good!

  • Steven Cahay

    So, my comment earlier was deleted because I posted the fact that this tutorial is a direct ripoff of Michael Hartl’s http://ruby.railstutorial.org/ free e-book. Andrew, you should be ashamed of yourself. This is absolutely pathetic.

    • jeff_way

      ?? No comments are deleted. How is this a rip off of a Getting Started with Rails book??

      • Steven Cahay

        Because this is basically an extremely minimized version of the exact same thing. The exact techniques are used for almost everything here… I recognized even variable names being the same – it was obvious the E-Book was open in a different browser window while this one was being written – that’s all I’m saying.

      • jeff_way

        I’m sorry; I’m still not getting it. Similar conventions might be followed, but that’s only adhering to best practices. Maybe I’m missing something.

      • andrew8088
        Author

        Just for the record, over a year and a half ago I did go through Michael’s screencasts, but I never looked at them (or the book) while working on this project.

      • http://www.jeffrey-way.com Jeffrey Way

        Even if you had, I’m still not sure how that book’s content transfers over to this Ribbit article, other than the basic fundamentals.

      • Jason

        Rails concepts are often applied in similar ways, so it wouldn’t surprise me if somethings overlap, especially if andrew learned form the book. Either way though, Michael Hartl’s tutorial http://ruby.railstutorial.org/ruby-on-rails-tutorial-book is an extremely well done and quite thorough look at Rails. I’d definitely recommend it to anyone interested in Rails.

  • L

    Running brakeman on this I got one warning on that regex to validate email (user model) http://brakemanscanner.org/docs/warning_types/format_validation/ . I’m not really good with regular expressions so is it right?

  • http://twitter.com/daniel_wg Daniel G

    Seems like a good tutorial, but writer, be aware of typo’s… found “rooturl” (root_url), “createdat” (created_at). People can easily be confused. But else, good job!

  • Kalman Hazins

    Good tutorial!

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

    That is not really correct. attr_accessible method has nothing to do with which properties are writable on an instance of a class. Rather, it serves as protection against mass asssignment attack. See the following railcast (http://railscasts.com/episodes/26-hackers-love-mass-assignment).

    To illustrate what I am tallking about – let’s say you have a Person active record model with a ‘username’ property. If you don’t specify username with attr_accessible


    Person.create(username: "some_username")

    will not work, but


    person = Person.new
    person.username = "some_username"
    person.save

    will work just fine.

    attr_accessible is definitely one of the most misunderstood features in Rails 3 and it will be going away in Rails 4.

  • Andrew A. Lee

    I would love to see a Node “from scratch” version of this tutorial!

  • http://twitter.com/IncendiaryMedia Fire

    Nice guide, I noticed some small issues ( Likely due to some article conversion ), specifically in the ruby code itself:


    @ribbit.userid = current_user.id
    ...
    @relationship.follower_id = current_user.id

    But otherwise this is pretty nice, I like this comparison of doing the exact same project in multiple languages. It’s a great way for someone who understands one language to get a good feel for another language by straight comparison.

  • http://twitter.com/gskharmujai George Kharmujai

    great post.. just by reading it i can somehow get a feel of rails..and will try developing a simple app using this tutorial as a base.

  • arnonate

    Any idea why I’m getting this routes.rb:4: syntax error, unexpected ‘:’, expecting kEND

    root to: ‘users#new’? Wants me to change my syntax to root :to => ‘users#new’? I’m very new to rails.

    • java

      did you figure out what the problem is

  • http://twitter.com/kuldarkalvik Kuldar

    Could you take a moment and double-check the code, Andrew? It’s absolutely impossible for a beginner to make heads or tails out of it when there are missing underscores and typos all over the place.

    For some pointers – classname should be class_name, rooturl is some places should be root_url, there are bunch of userid’s which should be user_id, there are trailing paragraph tags in controller code etc.

    Really appreciate the tutorial but come on, dude.

    • http://www.facebook.com/people/Brad-Arner/100001483446174 Brad Arner

      I second this!!

      The mistakes in the code are rather atrocious. As a beginner trying to make it through tutorials on Rails it is incredibly frustrating to realize that it isn’t that I typed something wrong that caused a problem. No…the problem is that I typed everything correctly according to the tutorials authors!!

      I might post a corrected version of the code on my blog once I figure it all out. I really hate when errors cause hours of frustrating, mind-numbing corrections.

    • Nick

      Agreed. While a good tutorial, the typos really detract from being able to learn a lot from this.

    • Tom Grim

      I have a git repo with the complete code here (minus typos) for those interested:

      https://github.com/tomgrim1/ribbit

  • http://www.facebook.com/tangtailam Eric Tang

    Thank you for your nice tutorial! I am a newbie, not sure why I just run your code, but got this error:

    Showing C:/Sites/ribbit/app/views/users/index.html.erb where line #14 raised:

    undefined method `content’ for nil:NilClass

    Extracted source (around line #14):

    11: Followers
    12: Following
    13:
    14:
    15:
    16:
    17:

  • Salik

    1
    2git add .
    git commit -m ‘initial rails app’Had some problem in these commands, tried “initial rails app” and worked wanted to share with all of you

    • Ian Simmons

      yeah I found this issue on windows using git through cmd.

      git commit -m ‘add known_issues.txt’

      gives error:

      error: pathspec ‘known_issues.txt” did not match any file(s) known to git.

      But using double quotes for commit messages works.

  • http://www.fortisthemes.com/ denis

    Just going through your code, on the sessions controller, under the create method your code is
    ” redirect_to rooturl, notice: “Logged in!” ,
    this will result in an error, first of all its root_url (with the underscore), this will also redirect back to the home page which is not what you want, instead, replace ‘root_url’ with ‘user’ . This will redirect the user to their profile page. This is for those copy pasters :) . Great tutorial though, keep up the good work :)

  • http://www.facebook.com/thanh.huynh.3192 Thành Huỳnh

    I think it had problem with Follow/Unfollow function, I don’t understand where params[:id] in destroy action of Relationship controller came from. When I pressed Follow/Unfollow multiple times, it just only worked in the first time. After that, it had error like: “Couldn’t find Relationship with id=1″ . The Online demo application has the same error.

  • fredy

    The tutorial is very nice an so useful, but if you try to read some variables names on Google Chrome you can’t notice the variable name underscore separation within, instead you see the variable like a separate phrase.

    Thanks for the tutorial!

  • Rodrigo Ortiz Moreno

    Hi, i’m having an error undefined method `key?' for nil:NilClass it has been stopping me for hours, can somebody help me, thanks.

  • Rodrigo Ortiz Moreno

    i get this error, after step 5 undefined method `key?' for nil:NilClass Please Help me!