はきだめ

主にプログラミングに関することをかきます

【Rails】twitter gemを使ってタイムラインの表示&ツイートをする

先日、ずっと読み進めていたRailsチュートリアルを読み終えました。次は何をしようか考えていたところ、twitterというgemを使うと簡単にAPIを叩くことが出来るということを聞いたので、ずっと作りたいと思っていたオリジナルのtwitterクライアントを作ることにしました。とりあえず、タイムラインの表示、ツイート機能などは出来たみたいなのでメモしておきます。

Oauth認証

# gemfile
gem 'omniauth'
gem 'omniauth-twitter'
gem 'twitter'
gem 'settingslogic'
gem 'dotenv-rails', require: 'dotenv/rails-now'
gem 'honoka-rails'

上記のgemを入れてbundleインストールします。Railstwitterクライアントを作る上で上の3つは必須です。settingslogicは定数を一元管理、dotenvは環境変数を設定、honokaはいい感じにスタイルを当てるために入れました。

# config/initializers/0_settings.rb
class Settings < Settingslogic
  source "#{Rails.root}/config/settings.yml"
  namespace Rails.env
end

先頭に0をつけることでsettingslogicのinitializerが先に呼ばれるようになるらしいです。

# config/settings.yml
defaults: &defaults

development:
  <<: *defaults
  twitter:
    consumer_key: <%= ENV['CONSUMER_KEY'] %>
    consumer_secret: <%= ENV['CONSUMER_SECRET'] %>

test:
  <<: *defaults

production:
  <<: *defaults
# .env
CONSUMER_KEY = '◯◯◯'
CONSUMER_SECRET = '◯◯◯'

githubにsettingもまとめてあげてしまいたかったので.envにtwitter developerで登録したkeyを書いてgitignoreで隠しました。.envが使えるのは先程入れたdotenvのおかげです。

# config/routes.rb
Rails.application.routes.draw do
  root 'home#index'
  get 'home/index'
  get '/auth/:provider/callback', to: 'sessions#callback'
  post '/auth/:provider/callback', to: 'sessions#callback'
  get '/logout', to: 'sessions#destroy', as: :logout
  get '/home', to: 'top#timeline', as: :home
  post '/home/tweet', to: 'top#tweet', as: :tweet
end
controller
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  helper_method :current_user

  def login_required
    if session[:user_id]
      @current_user = User.find(session[:user_id])
    else
      redirect_to home_path
    end
  end

  private

    def current_user
      @current_user ||= User.find(session[:user_id]) if session[:user_id]
    end
end
# app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def callback
    auth = request.env['omniauth.auth']
    user = User.find_by(provider: auth["provider"], uid: auth["uid"]) || User.create_with_omniauth(auth) #ユーザーがいなければ作る。
    session[:user_id] = user.id
    session[:oauth_token] = auth['credentials']['token']
    session[:oauth_token_secret] = auth['credentials']['secret']
    redirect_to home_path
  end

  def destroy
    reset_session
    redirect_to root_path
  end
end
# app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
  end
end
model
# app/model/user.rb
class User < ApplicationRecord
  def self.create_with_omniauth(auth)
    create! do |user|
      user.provider = auth['provider']
      user.uid = auth['uid']
      user.screen_name = auth['info']['nickname']
      user.name = auth['info']['name']
    end
  end
end

Oauth認証したアカウントはuserテーブルに入れていきます。以後カレントユーザーなどはデータベースから取り出していきます。

view
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
  <head>
    <title>Ballooon</title>
    <%= csrf_meta_tags %>

    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
      <% if current_user %>
        <%= current_user.name %> <%= link_to 'ログアウト', logout_path %>
      <% else %>
        <%= link_to 'ログイン', '/auth/twitter' %>
      <% end %>
    <%= yield %>
  </body>
</html>

これで認証の部分は完成です。http://127.0.0.1:3000にアクセスしログインという文字をクリックするとよく見かけるあの画面に飛ばされます。

f:id:kurome-stdio:20170902130315p:plain

これでOauth認証は完成です。/auth/twitterというパスは'omniauth gem'が用意してくれているので認証が成功しコールバック先のURLに遷移したときの処理をsession_controllerに書くという流れです。

タイムラインの表示&呟く

controller
# app/controllers/top_controller
class TopController < ApplicationController
  before_action :login_required, only: [:timeline, :tweet]
  include Common
  require 'twitter'

  def tweet
    client = client_new
    client.update(params[:text])
    redirect_to home_path
  end

  def timeline
    client = client_new
    @user = client.user
    @tweets = client.home_timeline(include_entities: true)
  end

end
# app/controllers/concern/common.rb
module Common
  extend ActiveSupport::Concern

  def client_new
    Twitter::REST::Client.new do |config|
      config.consumer_key = Settings.twitter.consumer_key
      config.consumer_secret = Settings.twitter.consumer_secret
      config.access_token = session[:oauth_token]
      config.access_token_secret = session[:oauth_token_secret]
    end
  end

end

Twitter::REST::Client.newの部分はAPIを叩く際に必要な決まり文句みたいな感じです。ツイートするのにもタイムラインを取得するのにも一々REST APIを叩かなければいけません。APIを叩くまで常に休んでいる状態なのでRESTって名前なんでしょうか...。今後使うかは分かりませんがStreaming APIというものもあるそうです。とりあえずTwitter::REST~~で始まる部分はよく使うことになりそうなのでモジュールに切り出しました。

view
<!-- app/views/top/timeline.html.erb -->
<div class="col-xs-3 container sidebar well well-sm">
  <%= image_tag(@user.profile_banner_uri_https, class: "img-responsive") %>
  <%= image_tag(@user.profile_image_uri_https('original'), class: "profile-image") %> プロフィール編集<br>
  <%= @user.name %><br>
  @<%= @user.screen_name %><br>
  <div>
    <span class="col-xs-4">
      ツイート<br>
      <%= @user.tweets_count %>
    </span>
    <span class="col-xs-4">
      フォロー<br>
      <%= @user.friends_count %>
    </span>
    <span class="col-xs-4">
      フォロワー<br>
      <%= @user.followers_count %>
    </span>
  </div><br>
  <%= @user.description %>
  <%= @user.location %><br>
  <%= @user.website %><br>
  <%= form_tag(tweet_path, method: "post") do %>
    <%= text_field_tag :text, '', class: "form-control input-area" %>
    <%= submit_tag("Tweet", class: "btn btn-primary") %>
  <% end %>
</div>
<div class="col-xs-9 col-xs-offset-3">
  <% @tweets.each do |tweet| %>
    <div class="post-panel well well-sm">
      <div class="tweet-image">
        <%= link_to image_tag(tweet.user.profile_image_url_https) %>
      </div>
      <div class="post">
        <%= tweet.user.name %> @<%= tweet.user.screen_name %><br />
        <%= tweet.text %><br />
        RT <%= tweet.retweet_count %> いいね <%= tweet.favorite_count %><br />
        <%= tweet.created_at %><br />
        <div class="post-image">
          <% if tweet.media? %>
            <% tweet.media.each do |media| %>
              <%= image_tag(media.media_url_https) %>
            <% end %>
          <% end %>
        </div>
      </div>
    </div>
  <% end %>
</div>

Twitter::REST::Client.newしたものをclientに代入するとclient.update(ツイート),client.user.name(ユーザー名)といった感じで情報を取ってくることが出来ます。

style
/* app/assets/stylesheets/custom.scss */
@import "bootstrap-sprockets";
@import "honoka";

/*timeline*/
.post-panel {
  padding: 5px 5px 5px 5px;
  margin-bottom: 0px;
}
.post {
  padding-left: 65px;
}
.tweet-image{
  margin-top: 5px;
  margin-bottom: 5px;
  float: left;
  img {
    border-radius: 50%;
    border: 5px solid #fff;
  }
}
.post-image{
  img {
    max-height: 250px;
    max-width: 300px;
  }
}

.login-section{
  float: right;
  margin-right: 15px;
}

/*sidebar*/
.sidebar {
  margin-top: 25px;
  position: fixed;
}
.profile-image {
  border-radius: 50%;
  border: 5px solid #fff;
  width: 100px;
  height: 100px;
  margin: -40px 0px 0px 10px;
}

暫定のcssですが、今後もっと改善させる予定です。
ここまでのコードで先程の認証を抜けるとこんな感じになります。

f:id:kurome-stdio:20170902184415p:plain

課題

  • スマホiPadサイズに対応してない
  • クライアントとしての機能が足りてない(タイムラインの表示もブラウザを再読込する必要がある。)
  • アクセスするたびにOauth認証しないと機能を使うことが出来ない。

参考

RailsでOmniauthを使ってTwitterログインする - Qiita

【Ruby on Rails】twitter apiを利用してoauth認証・タイムラインを表示する | レビューログ

Railsで定数を一元管理する(settingslogic) - Qiita

ダメ男のブログ: rails4 twitter APIを操作する その2 timeLineを取得

File: README — Documentation for twitter (6.1.0)