Let's Build: With Ruby on Rails - A Twitter Clone
Tags: rails, ruby, beginners, tutorial
Published: 2019-01-30 04:33:59

Welcome to our next Let's Build! This build introduces a well known social media giant Twitter into the mix as inspiration for the project. We will be creating a knock-off of sorts called **Twittter**. 

This app will most notably demonstrate a user role system giving your web application and native application feel all within the browser. I'll be going a bit faster than the [previous build](https://web-crunch.com/lets-build-with-ruby-on-rails-blog-with-comments/) as it was more of a foundational exercise where I explained common concepts revolving around the Ruby on Rails ecosystem. We'll make use of Rails generators to help scaffold some of the things I've already covered. If you haven't watched the [first Let's Build](https://web-crunch.com/lets-build-with-ruby-on-rails-blog-with-comments/), I invite you to do so in order to understand what we are doing.

#### Download the source code

{% github: https://github.com/justalever/twittter %}

### What we are building

The app itself will feature a basic CRUD principle where we can create, read, update, and destroy *Tweeets*. In essence, this is the same as my previous build where we created posts for a blog.  On top of the *Tweeets*, I introduce a new gem called [Devise](https://github.com/plataformatec/devise) which makes creating an entire user role and authentication system easy. Combined with this gem we can authenticate users who want to author *Tweeets*. A user's *Tweeets* are then also tied to their account. The end result is a public facing site with a stream of tweets from different users. Users that have an account can login to create their own *Tweeets* to add to the public stream.

### What we won't be covering

Twitter is a pretty elaborate application. I won't be covering replies, retweets, likes, and quite a few other foundational features of the famous social media giant. Instead, I invite you to tinker with how to possibly add those features. Similar to the [blog with comments build](https://web-crunch.com/lets-build-with-ruby-on-rails-blog-with-comments/) I did, see if you can figure out how to add replies to the application on your own. (Tip: It's the same as adding comments to the blog post as I did in the previous build ;) ). I may revisit this app to add more features to another exercise but I truthfully ran out of time! Let me know if you'd like to see me extend this further. 

There are also a few gems in the wild for "liking" posts. [Here's a favorite of mine](https://github.com/schneems/Likeable)

**Note:** One big error I noticed after calling it quits on this build was that any logged in user could edit **any** other user's tweeets. This is a big security flaw as a user's abilities should only lie within their own account. See if you can find a way to only allow the current logged in user to edit their own tweets and not other users. I may revisit this series to extend and address these issues. 

### Watch Part 1

{% youtube 5gUysPm64a4 %}


### Watch Part 2

{% youtube ZxkbFOe3lRY %}

### Watch Part 3

{% youtube V4h7-hR_WME %}

### Ruby gems used in this build

- [Better Errors](https://rubygems.org/gems/better_errors) - For better errors

- [Bulma](https://github.com/joshuajansen/bulma-rails) - for easy CSS. Feel free to roll your own styles and/or use a different framework.

- [Guard](https://github.com/guard/guard) - Useful for live reloading our `scss`, `js`, `css`, and `erb` files, although it's capable of much more!

  *Guard is required for the Guard LiveReload gem to work*

- [Guard LiveReload](https://github.com/guard/guard-livereload)

- [Simple Form](https://github.com/plataformatec/simple_form) - For simple forms!

- [Devise](https://github.com/plataformatec/devise) - Effortless user roles and authentication

- [Gravatar_image_tag](https://github.com/mdeering/gravatar_image_tag) - Integrated image tag which spits out gravatars.

  

### How I Extended Devise

As promised in the video I talk about extending the Devise gem to bypass the default registrations controller and apply our own. I [referenced a blog post](https://jacopretorius.net/2014/03/adding-custom-fields-to-your-devise-user-model-in-rails-4.html) which helped me wrap my head around what's really going on. I definitely invite you to check it out to understand it a bit better yourself. To add any additional fields to the Devise model you use, this step is 100% required. 

In my case, I added the `name` and `username` fields to the registration process. I needed to create a registrations controller which contains the following:

<pre class="language-ruby"><code>
# app/controllers/registrations_controller.rb

class RegistrationsController &lt; Devise::RegistrationsController 

	private

	def sign_up_params 
		params.require(:user).permit(:name, :username, :email, :password, :password_confirmation)
	end

	def acount_update_params 
		params.require(:user).permit(:name, :username, :email, :password, :password_confirmation, :current_password)
	end

end
</code></pre>

The controller extends from the default Devise registrations controller. We append the `name` and `username` fields to the `user` model/table and make use of Rails strong parameters to make it all tie together.

#### Adding the fields to the database

We still require a couple of new columns in our `users` table inside our database to get this to work. To add these you need to run a migration. Run the following: 

<pre class="language-bash"><code>$ rails g migration AddFieldsToUsers</code></pre>

Then head to `db/migrate/XXXXXXXXXXXXXX_add_fields_to_users.rb` to find the new migration. When it's all said and done I added the following:

<pre class="language-ruby"><code>
class AddFieldsToUsers &lt; ActiveRecord::Migration[5.1]
  def change
  	add_column :users, :name, :string
  	add_column :users, :username, :string
  	add_index :users, :username, unique: true
  end
end
</code></pre>

We are adding two columns to our `users` table which consist of the `name` and `username` field we want to add to our registration forms. I go one step further and make sure the `username` field is authored as unique which basically just means that no two users can use the same username.

With those fields and migration in place, you can go ahead and run `rails db:migrate` to make it all tie together. 

#### Modifying the devise views

In `app/views/devise/registrations/new.html.erb` you can add the following updated code to reflect our most recent changes.

<pre class="language-markup"><code>
&lt;!-- app/views/devise/registrations/new.html.erb --&gt;
&lt;div class="section"&gt;
	&lt;div class="container"&gt;
	&lt;div class="columns is-centered"&gt;

		&lt;div class="column is-4"&gt;
			
		&lt;h2 class="title is-2"&gt;Sign Up&lt;/h2&gt;

		&lt;%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %&gt;
	  &lt;%= f.error_notification %&gt;

	  &lt;div class="field"&gt;
	  	&lt;div class="control"&gt;
	    &lt;%= f.input :name, required: true, autofocus: true, input_html: { class:"input" }, wrapper: false, label_html: { class:"label" } %&gt;
	  	&lt;/div&gt;
		&lt;/div&gt;

		&lt;div class="field"&gt;
	  	&lt;div class="control"&gt;
	    &lt;%= f.input :username, required: true, input_html: { class:"input" }, wrapper: false, label_html: { class:"label" } %&gt;
	  	&lt;/div&gt;
		&lt;/div&gt;

		&lt;div class="field"&gt;
	  	&lt;div class="control"&gt;
	    &lt;%= f.input :email, required: true, input_html: { class:"input" }, wrapper: false, label_html: { class:"label" } %&gt;
	  	&lt;/div&gt;
		&lt;/div&gt;
  
		  
		&lt;div class="field"&gt;
			&lt;div class="control"&gt;
				&lt;%= f.input :password, required: true, input_html: { class:"input" }, wrapper: false, label_html: { class:"label" }, hint: ("#{@minimum_password_length} characters minimum" if @minimum_password_length) %&gt;
			&lt;/div&gt;
		&lt;/div&gt;

		&lt;div class="field"&gt;
			&lt;div class="control"&gt;
				&lt;%= f.input :password_confirmation, required: true, input_html: { class: "input" }, wrapper: false, label_html: { class: "label" } %&gt;		
			&lt;/div&gt;
		&lt;/div&gt;

		&lt;div class="field"&gt;
			&lt;div class="control"&gt;
		 		&lt;%= f.button :submit, "Sign up", class:"button is-info is-medium" %&gt;
		 	&lt;/div&gt;
		&lt;/div&gt;

		&lt;% end %&gt;
			&lt;br /&gt;
			&lt;%= render "devise/shared/links" %&gt;
		&lt;/div&gt;
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;</code></pre>

Here I make use of Bulma as well as add our `name` and `username` fields to the form. You will want to repeat this change on the `edit.html.erb` file in the same directory.

<pre class="language-markup"><code>
&lt;!-- app/views/devise/registrations/edit.html.erb --&gt;

&lt;section class="section"&gt;
  &lt;div class="container"&gt;
    &lt;div class="columns is-centered"&gt;
      &lt;div class="column is-4"&gt;
        
      &lt;h2 class="title is-2"&gt;Edit &lt;%= resource_name.to_s.humanize %&gt;&lt;/h2&gt;
      &lt;%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %&gt;
        &lt;%= f.error_notification %&gt;

          &lt;div class="field"&gt;
            &lt;div class="control"&gt;
              &lt;%= f.input :name, required: true, autofocus: true, input_html: { class: "input"}, wrapper: false, label_html: { class: "label" } %&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class="field"&gt;
            &lt;div class="control"&gt;
              &lt;%= f.input :username, required: true,  input_html: { class: "input"}, wrapper: false, label_html: { class: "label" } %&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class="field"&gt;
            &lt;div class="control"&gt;
              &lt;%= f.input :email, required: true, input_html: { class: "input"}, wrapper: false, label_html: { class: "label" } %&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class="field"&gt;
          &lt;% if devise_mapping.confirmable? &amp;&amp; resource.pending_reconfirmation? %&gt;
            &lt;p&gt;Currently waiting confirmation for: &lt;%= resource.unconfirmed_email %&gt;&lt;/p&gt;
          &lt;% end %&gt;
          &lt;/div&gt;

          &lt;div class="field"&gt;
            &lt;div class="control"&gt;
            &lt;%= f.input :password, autocomplete: "off", hint: "leave it blank if you don't want to change it", required: false, input_html: { class: "input"}, wrapper: false, label_html: { class: "label" } %&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class="field"&gt;
            &lt;div class="control"&gt;
            &lt;%= f.input :password_confirmation, required: false, input_html: { class: "input"}, wrapper: false, label_html: { class: "label" } %&gt;
            &lt;/div&gt;
          &lt;/div&gt;

          &lt;div class="field"&gt;
            &lt;div class="control"&gt;
              &lt;%= f.input :current_password, hint: "we need your current password to confirm your changes", required: true, input_html: { class: "input"}, wrapper: false, label_html: { class: "label" } %&gt;
            &lt;/div&gt;
        &lt;/div&gt;
   
        &lt;%= f.button :submit, "Update", class:"button is-info" %&gt;
     
      &lt;% end %&gt;

        &lt;hr /&gt;
        &lt;h3 class="title is-5"&gt;Cancel my account&lt;/h3&gt;
        &lt;p&gt;Unhappy? &lt;%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %&gt;&lt;/p&gt;
   
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/section&gt;</code></pre>

It's up to you what you want to "require" from a user. I noticed after the fact that the `edit.html.erb` form doesn't necessarily need so many required fields. Feel free to extend/modify this to your liking.

### Wrapping Up

The main purpose of this build was to reintroduce techniques and terminology used in the [previous build](https://web-crunch.com/lets-build-with-ruby-on-rails-blog-with-comments/). On top of the previous build, I went one step further and introduced authentication and user roles. We didn't build a fully functioning Twitter clone but we did take bits and pieces to create something similar. 

You are welcome to extend this project further. I may even do so myself as time carries on just to resolve some bugs I spotted along the way as well as integrate new technologies and features into the mix. I hope you enjoyed the build as much as I did. As we press forward I plan to introduce more concepts and ideas for app builds of which you could make on your own. See you in the next build!



##### Credits

Music credit in part 2 - [https://soundcloud.com/argofox](https://soundcloud.com/argofox)


#### The Series So Far

+ [01 - Introduction](https://web-crunch.com/lets-build-with-ruby-on-rails-introduction/)
+ [02 - Installation](https://web-crunch.com/lets-build-with-ruby-on-rails-installation/)
+ [03 - Build a blog with comments](https://web-crunch.com/lets-build-with-ruby-on-rails-blog-with-comments/)
+ [04 - Build a Twitter clone](https://web-crunch.com/lets-build-with-ruby-on-rails-a-twitter-clone/) - You are here

## Shameless plugs 

If you liked this post, I have many more builds on [YouTube](https://youtube.com/c/webcrunch) and my [blog](https://web-crunch.com). I plan to start authoring more here as well. Want more content like this in your inbox? [Subscribe to my newsletter](https://web-crunch.com/subcribe) and get it automatically.

<a href="https://hellorails.io" target="_blank">
![](https://thepracticaldev.s3.amazonaws.com/i/wkb5yyst7pxa1s16kr2u.jpg)
</a>
☝ Want to learn Ruby on Rails from the ground up? Check out my upcoming course called [Hello Rails](https://hellorails.io).