Rails3.2のウェブサービスにOAuthを導入したこと。にせほボタンの解

かとりょーです。
あー。書くのに時間がかかってしまった……。
予告( http://katryo.hatenablog.com/entry/2012/05/05/122513 )通り、にせほボタンの解説です。
にせほボタンはこちら。 http://nisehobutton.heroku.com/
OAuthを導入する練習のために作りました。ついでにtwitter連携の練習にもなりました。これからOAuthやtwitter連携を始める人は、読むと参考になります。

にせほボタンの概要

  • ボタンを押すと、ユーザーのアカウントでツイートする
  • TwitterOAuth認証する
  • Rails3.2.3製
  • herokuにデプロイした

詳しくはここ http://katryo.hatenablog.com/entry/2012/05/05/122513 に書きました。
GitHubはこちら。https://github.com/katryo/oauthtest

参考

Railscastの#241 simple OmniAuthを参考にしました。
http://railscasts.com/episodes/241-simple-omniauth?language=ja&view=asciicast

準備

Twitter Developers
とりあえず、 https://dev.twitter.com/ でアプリ登録します。
Access levelはRead and writeに設定します。
あとで設定は変えられるので、Callback URLも適当なURLを入れておいてください。これを書いておかないと、あとでOAuthが動いてくれません。

OmniAuth認証の仕組み

OmniAuthを使います。

Gemfileに

source 'https://rubygems.org'

gem 'rails', '3.2.3'

# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'

#gem 'omniauth'
gem 'twitter'
gem 'omniauth-twitter'

# Gems used only for assets and not required
# in production environments by default.
group :assets do
  gem 'sass-rails',   '~> 3.2.3'
  gem 'coffee-rails', '~> 3.2.1'
  gem 'twitter-bootstrap-rails'

  # See https://github.com/sstephenson/execjs#readme for more supported runtimes
  # gem 'therubyracer', :platform => :ruby

  gem 'uglifier', '>= 1.0.3'
end

# Heroku
group :production do
    gem 'pg'
end

group :development do
gem 'sqlite3'
end


という風に書き加えます。
これで

$ bundle install --without production

したあと、Gemfile.lockを見てみると、oAuth関連のgemは

    oauth (0.4.6)
    omniauth (1.1.0)
      hashie (~> 1.2)
      rack
    omniauth-oauth (1.0.1)
      oauth
      omniauth (~> 1.0)
    omniauth-twitter (0.0.11)
      multi_json (~> 1.3)
      omniauth-oauth (~> 1.0)


となりました。
種類が多くて内実がよくわからないです。もしかして、いらないもの入っている?

まず/config/initializers/omniauth.rbというファイルを作成し、そこに

Rails.application.config.middleware.use OmniAuth::Builder do
		provider :twitter, 'CONSUMER_KEY', 'CONSUMER_SECRET' 
end

と書きます。'CONSUMER_KEY'と'CONSUMER_SECRET' の''の中には、https://dev.twitter.com/ でのアプリ登録でもらえる'CONSUMER_KEY'と'CONSUMER_SECRET'を入れましょう。

にせほボタン | Twitter Developers

この'CONSUMER_KEY'と'CONSUMER_KEY'を他人に知られると、「そのアプリのふりをする」いたずらをされる恐れがあるので、公開しないようにしてください。

で、このOAuthログインページまでユーザーを誘導するパスが"/auth/twitter"なので、にせほボタンのログインページにはこんなerbを書きました。

<%= link_to 'にせほボタンを始める', "/auth/twitter", :class => 'btn btn-large' %>


実際にブラウザで見るとこんな図です。
にせほボタン-1


ユーザーがこのボタンをクリックするとTwitterhttps://api.twitter.com/oauth/authorize に移動して、認証を許諾するとコールバックされて、にせほボタンのウェブページに戻ってきます。このとき、戻ってくる先のウェブページを設定しておく必要があります。おなじみ https://dev.twitter.com/ で、この画像のようにCallback URLを設定しておいてください。僕の場合は、"http://nisehobutton.heroku.com/contents/buttons/"に設定しました。

にせほボタン | Twitter Developers-1


ユーザーがログインした状態を保つため、sessionsコントローラをまず作ります。

$ rails g controller sessions

userモデルも作ります。モデル内の要素はprovider, uid, name, と指定します。

$ rails g model user provider:string uid:string name:string

さっそくデータベースをマイグレートして、

$ rake db:migrate

"/model/user.rb"にはこう書きます。

class User < ActiveRecord::Base
  attr_accessible :name, :provider, :uid

  def self.create_with_omniauth(auth)
  create! do |user|
    user.provider = auth["provider"]
    user.uid = auth["uid"]
    user.name = auth["info"]["name"]
  end
  end
end

OAuthでもらってきたauth["provider"]やauth["uid"]などのユーザーの情報を、Userテーブルに入れるというメソッドがself.create_with_omniauth(auth)ですね。


sessions_controller.rbには以下のように書きます。

#coding: utf-8
class SessionsController < ApplicationController
  def create
    auth = request.env["omniauth.auth"]
    user = User.find_by_provider_and_uid(auth["provider"], auth["uid"]) || User.create_with_omniauth(auth)
    session[:user_id] = user.id
   
    Twitter.configure do |config|
      config.oauth_token = auth['credentials']['token']
      config.oauth_token_secret = auth['credentials']['secret']
    end
    redirect_to '/contents/buttons', :notice => "認証しました!"
  end

  def destroy
    session[:user_id] = nil
    redirect_to '/', :notice => "認証を外しました"
  end
end

createでセッション情報を作り、destroyで認証を外します。実際のにせほボタンでは認証を外す機能がない(2012/07/08の段階では)ので、destroyは使わないのですが一応残してあります。

userには、すでにOAuthで認証ずみの場合は認証したユーザーのprovider(さっき登場したauth["provider"]が入っている)と同じくuid(auth["uid"]が入っている)をもとに特定したユーザーが入り、認証していない場合はUser.create_with_omniauth(auth)でUserクラスのcreate_with_omniauthメソッドを使って、新たにユーザーを作成します。



これでようやくOAuthは完成です。OAuthの仕組みを使って認証・Twitter連携ができます。

Twitter連携

せっかく手に入れたOAuthなので、Twitterと連携してツイートできるようにします。
Gemfileに

gem 'twitter'

と書いてbundleしておいたので、すでに"The Twitter Ruby Gem"( http://twitter.rubyforge.org/ )は導入されています。これの機能を使って、ユーザーにツイートさせます。
config/initializers/twitter.rb というファイルを作って、

Twitter.configure do |config|
  config.consumer_key = 'CONSUMER_KEY'
  config.consumer_secret = 'CONSUMER_SECRET'
end

と書きます。'CONSUMER_KEY', 'CONSUMER_SECRET' にはOAuthと同じく、 https://dev.twitter.com/ でもらった文字列を入れます。
これで、OAuth認証したユーザーのツイートを操れます。


app/controllers/contents_controller.rbはこんな風にしました。

#encoding: utf-8
class ContentsController < ApplicationController
  def index
  end

  def buttons

    if current_user

      @user = Twitter.user.screen_name
      @random_number = Time.now.sec % 4
      if params[:button_1]

        if @random_number == 2 
          Twitter.update("にせほー! にせほー! そこにいるの?おしいー! おしいー! 明日があるの! 3分間\ハイ!/リプライを\ハイ!/か・ぞ・え・て・たー\人☆工☆知☆能☆/\( ゜ヮ゜)>\(゜ヮ゜)/ \(゜ヮ゜)/ <(゜ヮ^ )/ #にせほボタン http://nisehobutton.heroku.com/")
        else
          Twitter.update("@nisehorn @nisehorrn @nisehorrrn @nisehorrrrn にせほー #にせほボタン http://nisehobutton.heroku.com/") 
        end
        session[:button_pushed_1] = true

      elsif params[:button_2]
        if @random_number == 3
          Twitter.update("ロックスターはオワコン。時代はレッドブル #にせほボタン http://nisehobutton.heroku.com/")
        else
          Twitter.update("ロックスター・エナジードリンクなう #にせほボタン http://nisehobutton.heroku.com/")
        end

        session[:button_pushed_2] = true
      elsif params[:button_3] 
        Twitter.update("ゆ #にせほボタン http://nisehobutton.heroku.com/") 
        session[:button_pushed_3] = true
      elsif params[:button_4]
        Twitter.update("#zekitterは神 #にせほボタン http://nisehobutton.heroku.com/") 
        session[:button_pushed_4] = true
      elsif params[:button_5]
        Twitter.update("アイエエエ!? ニンジャ!? ニンジャナンデ!? #にせほボタン http://nisehobutton.heroku.com/") 
        session[:button_pushed_5] = true

      elsif params[:button_6]
        Twitter.update("たいぷかのんほー #にせほボタン http://nisehobutton.heroku.com/") 
        session[:button_pushed_6] = true

      end
      @msg = 'あなたのアカウントでツイートできました。たぶん。'
    else
      redirect_to '/'

    end
  end

  def show

  end
end

解説

Twitter.update("ツイートの本文")
メソッドを使って、OAuthでトークンを渡してくれたユーザーにツイートさせる、シンプルな仕組みです。

@user = Twitter.user.screen_name

は、twitterユーザーのスクリーンネーム(たとえばkatryo)を取ってきて、/views/contents/index.html.erbで

ようこそ<%= render :text => @user %>さん。
このページはあなたのために生まれました。

と書くことで、

にせほボタン-2

こんなApple製品の箱を開けたあと風のページにする狙いです。

@random_number = Time.now.sec % 4

は、「1/4の確率をで特別なツイートになる」仕組みのためにやってます。現在時刻を4で割った余りが2や3のとき、別のツイートになります。
うーん、今見ると、ifじゃなくてcaseを使ったほうがrubyらしいコードになった気がする。直すのめんどいので次回に生かすことにしますー。

デプロイ

herokuにデプロイしました。herokuはPostgreSQLしか使えないので、Gemfileにdevelopmentとproductionを分けて、このように書いておいて、

group :production do
    gem 'pg'
end

group :development do
gem 'sqlite3'
end

デプロイ前に

$ bundle install --without development

とします。
デプロイしてから、DBのマイグレーションをheroku runコマンドでします。

$ heroku run rake db:migrate

herokuへのデプロイでハマるのは、assetsとDB周りが大半です。特にRails3.1から導入されたasset pipelineは比較的新しい機能のため、本によっては対応してないことがあるので古めの本を読んでる人は注意が必要です。
asset pipelineは要するにCSSや画像、Javascriptをこれまでのような/publicでなく/app/assetsに入れておき、デプロイ時に/assetsに圧縮して入れる仕組みです。ウェブページのレンダリング時のリクエスト回数を減らせるので高速化できるそうです。
Railscastにasset pipelineを紹介した記事があるので、見ておくといいです。 http://railscasts.com/episodes/279-understanding-the-asset-pipeline?language=ja&view=asciicast
こっちはRails公式の解説 http://guides.rubyonrails.org/asset_pipeline.html

かつてherokuはasset pipelineに標準対応していなかったのでアプリ作成時に

heroku create アプリの名前 --stack cedar

と、stack cedar(cedarはただのコードネーム。1つ前のバージョンはbambooだった)を指定しないといけなかったのですが、2012年6月でstack cedarが標準になったので、今は単純に

heroku create アプリの名前

とすれば、勝手にstack cedarのアプリを作ってくれます。
herokuの公式マニュアルに色々書いてあるので読みましょう。 https://devcenter.heroku.com/articles/cedar/
herokuの設定やデプロイ順序は頻繁に変わるので、日本語で書かれたブログを探すよりもheroku公式マニュアルを読んだほうが速いことが多いです。

デプロイしてもアプリが動かないときは、

$ heroku logs

でログを見て、プリコンパイルしたりDB設定を変えながら対応すると時間を無駄にしなくて済むと思います。
ひがしのてらこや。公式サイト( http://higashinoterakoya.herokuapp.com/ )のように、DBの内容を表示するタイプのアプリでは、DB内にレコードが1つもないとsomething went wrongが出ます。なので、とりあえずDBに何か入れましょう。

heroku run rails console

とすれば、heroku上でRuby対話環境を起動できます。
もしUserテーブルにレコードを追加したいなら、

User.create(name:'katryo', password:'secret')

とすれば、ユーザー情報が入ります。
ユーザーが入ったことを確認したいなら、このあと

User.count

とすれば、ユーザー数を表示してくれます。

ではでは!