Repost / Retweet / Reblog

Published Jan 21, 2017Last updated Feb 22, 2017
Repost / Retweet / Reblog

Today we're going to talk about Reposting in your application like you'd see on Twitter for example with the retweets.

We're going to talk about how to build your databases to do this and we'll use a self-referential associations to do so. We'll even talk about how Twitter gives the ability to add a comment on the retweeted tweet and references the original tweet.

We're starting with an application that has Devise installed with users and then we'll generate our scaffold for the Tweets.

rails g scaffold Tweet user_id:integer body:text tweet_id:integer

So because we're using a self-referential association we're adding the tweet_id to the model here and we'll use that to include or keep track of if it's a retweet or not by seeing if the tweet_id has a value.

rake db:migrate

Once we run our migrations we'll go into the config/routes.rb file and set a root route for the tweets index.

root to: 'tweets#index'

app/models/user.rb

has_many :tweets

Here we're setting up the user to have many tweets and then in the tweet model we'll set up the tweet to belong to a user and belong to a tweet, which is what we'll use to know if it belongs to a tweet then it must be a retweet.

app/models/tweet.rb

belongs_to :user
belongs_to :tweet

In our tweets controller we'll make sure we have a before_action to authenticate the user is logged in and then we'll update the create action to ensure that the tweet is created on the current_user.

app/controllers/tweets_controller.rb

  before_action :authenticate_user!

  ...

  def create
    @tweet = current_user.tweets.new(tweet_params)

    ...
  end

Then we'll go edit the views for the form when you create a tweet, we want to remove the tweet_id and user_id from the view and only allow them to send the body. You can find the form in app/views/tweets/_form.html.erb. Make sure you leave the body text_area and the submit button but remove the tweet_id and user_id fields.

Now if you try and submit a tweet you'll receive an error that the Tweet must exist. This is because with Rails 5 the belongs_to now is required. But don't worry because by editing the Tweet model you can make the tweet optional.

app/models/tweet.rb

belongs_to :user
belongs_to :tweet, optional: true

Now we'll go sign up for a second user and create our first tweet from the second account and you'll be able to see they have different User IDs on the tweets index.

We're going to add a Retweet button into the tweets index view which we will display on tweets that aren't our own so we can retweet other users tweets.

app/views/tweets/index.html.erb

<td><%= link_to "Retweet", retweet_tweet_path(tweet), method: :post if user_signed_in? && tweet.user_id != current_user.id %></td>

In my view I replaced the edit and delete links with a retweet link. We're checking before we display the retweet link if the user is signed in and we are making sure that the user the tweet belongs to isn't the current_user because we don't want you to be able to retweet your own tweets. I've also used the retweet_tweet_path which we haven't actually defined a route for so we need to do that now.

config/routes.rb

resources :tweets do
  member do
    post :retweet
  end
end

Now we should go to our tweets_controller and add the retweet action for the app to make the post to.

app/controllers/tweets_controller.rb

def retweet
  tweet = current_user.tweets.new(tweet_id: @tweet.id)
  if tweet.save
    redirect_to tweets_path
  else
    redirect_to :back, alert: 'Unable to retweet'
  end
end

Now make sure you modify the set_tweet before_action at the top of the file and add the :retweet action to it so that it sets the @tweet instance variable for the retweet action when creating the record.

before_action :set_tweet, only: [:show, :edit, :update, :destroy, :retweet]

If the tweet_id exists on the tweet record we'll use that to change how the views are displayed, thus allowing us to display a different view for the retweeted tweets.

Now if you go to the tweets index and click retweet on a tweet you should see a new tweet created for your user id and there will be no text but it will have a tweet_id on it.

We're using the same model for these because we want them to be queryable as if they are all the same. We use the extra column to accomplish this.

Now let's modify that view for the tweet index and we'll clean it up to make a bit better of a UI for it.

app/views/tweets/index.html.erb

<h1> Tweets </h1>
<% @tweets.each do |tweet| %>
  <hr />
  <div>
    <div>Posted by: <%= tweet.user.email %></div>
    <p><%= tweet.body %></p>
    <div><%= link_to "Retweet", retweet_tweet_path(tweet), method: :post if user_signed_in? && tweet.user_id != current_user.id %></div>
  </div>
<% end %>

<%= link_to 'New Tweet', new_tweet_path %>

Now it looks a little better. But if you notice we don't actually have any text under the tweet that is technically a retweet. So you can set this up by using a partial for it.

We'll create a method called tweet_type on the Tweet model and use that to help differ the partials templates dynamically.

app/models/tweet.rb

def tweet_type
  if tweet_id? && body?
    "quote-tweet"
  elsif tweet_id?
    "retweet"
  else
    "tweet"
  end
end

Now in the app/views/tweets/index.html.erb view we can render a partial instead and then we'll go create that.

<h1> Tweets </h1>
<% @tweets.each do |tweet| %>
  <hr />

  <%= render partial: "tweets/#{tweet.tweet_type}", locals: {tweet: tweet} %>

<% end %>

<%= link_to 'New Tweet', new_tweet_path %>

The tweet_type will then make it look for the partial according to the type of tweet.

We'll take the existing code and move it to a _tweet.html.erb partial

app/views/tweets/_tweet.html.erb

<div>
  <div>Posted by: <%= tweet.user.email %></div>
  <p><%= tweet.body %></p>
  <div><%= link_to "Retweet", retweet_tweet_path(tweet), method: :post if user_signed_in? && tweet.user_id != current_user.id %></div>
</div>

Now we'll do a retweet partial.

app/views/tweets/_retweet.html.erb

<div>
  <div>Retweeted by <%= tweet.user.email %></div>
  <%= render partial: "tweets/tweet", locals: {tweet: tweet.tweet} %>
</div>

But if you look we're passing in a tweet.tweet which basically means we're passing in the tweet's tweet which is what we use to tell if the tweet is actually a retweeted tweet from the tweet_id column.
It can be confusing but now we've used the tweet partial and now tweets will look similar with just a different wrapper around to display the information that it's a retweeted tweet.

Feel free to change those views however you think looks nice, but there's an implementation for the retweet/reposting/reblogging functionality.

Your challenge should you choose to accept is to try and implement the quoting tweet retweet functionality that Twitter has on your site.

Credits for GoRails

This transcript was written by Andrew Fomera.

Discover and read more posts from Victor H
get started