プログラミング学習 備忘録

Railsを学習していく上での技術メモ。学んだことや解決したエラーなどを記録していきます。

【決断くん】Twitter認証機能&自動投稿機能の実装

現在作っているアプリ、決断くんにtwitter認証機能と自動投稿機能を実装してみました。
理由としては2つあります。

・決断くんのこだわりポイントの1つ、twitter自動投稿機能を実装するためには、twitter連携が不可欠だから。 ・twitterFacebookなどのAPIを扱えるようになりたかったから。
実装については公式のwikiと頼もしい先輩方のQiitaの記事を参考に行ったためあまり特筆することはないので、学んだことや少し詰まったところだけ備忘録としてまとめます。

参考にさせていただいたQiita記事
【Rails】SorceryでTwitter認証 - Qiita

【2019年版】RailsアプリからTwitterに更新内容を自動投稿!RailsとTwitterの連携機能を実装 - AUTOVICE



認証機能の実装

Userモデルのemail属性のnull: false制約を解除

Twitterにはemail登録をしていないユーザーもたくさんいるので、email属性がnull: falseのままだと登録段階でemailアドレスを引っ張ってこれずにバリデーションエラーが起こると予想される。

なので、email属性のnull: false制約を解除した。

#ChangeColumnNotnullマイグレーション内
class ChangeColumnToNotNull < ActiveRecord::Migration[6.0]
  def change
    change_column :users, :email, :string, null: true
  end
end


null: trueでもつけるのかな?とか思ってたらほんとにそうでびっくり。null: trueをつけてrails db:migrateしてやることでnull: falseを上書きして解除できた。
null: falseを後付けすることはあっても解除することはなかなかないので使う機会は限られるかもしれないが、覚えておこう。

エラー【OAuth::Unauthorized 400 bad request】

トップ画面からTwitterログイン画面に遷移する際に起こった。
Debuggerを使ったり色々な記事やwikiを見てみたが解決せず、結構時間を取られた。結論としては、credentials.yml.encを正しく読み込めていない or 記述できていなかったせいでエラーが起こっていたと判明。

そのため非推奨ではあるが、いったんsorcery.rbにkeyをベタ書きで記述するとうまく動いた。

#エラーがでたコード
  config.twitter.key = Rails.application.credentials.dig(:twitter, :key)
  config.twitter.secret = Rails.application.credentials.dig(:twitter, :secret_key)
#credentials.yml.enc内(vi)
twitter:
  key: '自分のAPI key'
  secret_key: '自分のAPI secret key'

Sorcery.rbのコードを下のように直書きにした。

#正常に動いたコード
  config.twitter.key = '自分のAPI key'
  config.twitter.secret = '自分のAPI secret key'

これについては明日原因究明して追記したいところ。

callback時にログインに失敗してしまう問題

#問題のコード
  def callback
    provider = params[:provider]
    if @user = login_from(provider)
      redirect_to root_path, success: "#{provider.titleize}でログインしました"
    else
      begin
        @user = create_from(provider)
        reset_session
        auto_login(@user)
        redirect_to root_path, success: "#{provider.titleize}でログインしました"
      rescue StandardError
        redirect_to root_path, danger: "#{provider.titleize}でのログインに失敗しました"
      end
    end
  end

なぜか必ずrescue StandardErrorに流れてしまっていた。
Debugger起動させて1つずつ処理を見ていくと、crypted_passwordが空になっているのが問題と判明。
(Twitter連携に取り掛かるまでは通常のログイン機能のままだったので当然だが、DBとモデル両方でpassword属性にnull: falseをかけていた。)
そこでemail同様、password属性のnull: falseを一旦外してみると見事ログイン成功!

正直、twitterで強制的に呟かれることで決断に強制力を持たせるのがこのアプリの醍醐味というかおもしろポイントなので、普通のログイン機能はもう削除しちゃっていいかな。。。

自動投稿機能の実装

Twitter developerへの登録や設定は全て終了していたので、こちらは意外なほどあっさりと実装が完了した。railsの偉大さを改めて実感。
実装できたのはdeveloperに登録しているtwitterアカウントへの呟き機能のみだった。ユーザーのアカウントに強制的に呟かせるには、個人のaccess_tokenとaccess_token_secretを取得する必要がある。一応できるらしいが、アカウントをのっとるようなものなので「twitterでシェア!」ボタンの実装だけに止めることに。一応developerアカウントに自動で呟く機能の実装方法は下記に残しておく。

choice_controllerのコード

自分の場合のコードはこんな感じに。

# choice_controller内
  def create
    options = []
    options.push(@choice.option_1, @choice.option_2, @choice.option_3, @choice.option_4, @choice.option_5)
    options.reject!(&:blank?)
    @choice.result = options.sample
    if params[:back]
      render :new
    elsif @choice.save
# \rは改行のコード
      @client.update("#{current_user.name}は、\r#{@choice.title}に対して、\r#{@choice.result}ことを決めた!!!!")
      flash[:success] = 'この決断をtwitterに投稿しました。必ず実行してください。'
      redirect_to "/choices/result/#{@choice.id}"
    end
  end
.
.
.
    private

  def twitter_client
    @client = Twitter::REST::Client.new do |config|
      config.consumer_key = "自分のAPI key"
      config.consumer_secret = "自分のAPI secret key"
      config.access_token = "自分のaccess token"
      config.access_token_secret ="自分のsecret access token"
    end
  end


エラー【NameError in ChoiceController#update uninitialized constant ChoiceController::Twitter

上のコードで実際に動かした際に出たエラー。updateがうまく動いていない。
原因はものすごく単純だった。 gem 'twitter'をインストールしていなかった。
凡ミスには注意。



今回の振り返り

とうとうこのアプリのこだわりポイントであるtwitter自動投稿機能を実装できました。
APIと聞くと自分の中では難しいイメージがあったので、無事に実装できて良かったです。
外部アプリケーションとの連携は、今やどのサービスにも導入されているので今回の経験はとても役に立つと思います。

また、最初にsorceryで通常のログイン機能を実装した後、twitter認証を追加したのですが、そのせいでカラムの変更やモデルの変更などに時間を裂くことになりました。
当たり前のことですが、コーディングに入る前に、実装する機能やDB設計をしっかりと確立させておかないと面倒なことになると実感しました。

2021/01/25 追記
コンテストまでにデプロイを目指していたが、時間が足りず、仕事のため予選会に出れなくなってしまいました。基本機能が完成したこのタイミングでいったん実装を終了し、新しい知識を身に付けるため中断していた課題に戻ることにしました。