こたつとみかんとプログラミング

33才実務未経験ですがウェブエンジニアにジョブチェンジするために勉強したことをアップするためのブログです。

rubocop-airbnb 導入方法まとめ

rubocop 本体は設定項目が多いので、ライトな rubocop-airbnb を導入することにした。
以下、導入方法のまとめと、無視したいエラーの書き方についてまとめ。

導入方法

1. gemのインストール

gemfile の開発環境とテスト環境に rubocop-airbnb を追加し、bundle install

group :development, :test do
  gem 'rubocop-airbnb'
end

2. rubocop-airbnb の設定

ルートディレクトリに .rubocop.yml.rubocop_airbnb.yml の2ファイルを作成し、以下の内容を記述

# .rubocop.yml

inherit_from:
  - .rubocop_airbnb.yml

# Rails用に最適化
Rails:
  Enabled: true

# 文字数上限
LineLength:
  Max: 130

AllCops:
  Exclude:
    - 'db/**/*'
    - 'config/**/*'
    - 'script/**/*'
    - 'node_modules/**/*'
    - 'bin/*'
    - '**/Gemfile'
    - 'vendor/**/*'
    - '.git/**/*'

Exclude で除外フォルダ・除外ファイルを設定している。

# .rubocop_airbnb.yml
require:
  - rubocop-airbnb


以上で設定は終了。
以下、実行して起動(エラーチェック&エラー内容書き出し)すれば完了。

$ bundle exec rubocop --require rubocop-airbnb

特定ファイルのみエラーを無視する方法

今回のケースは、 devise 導入しているアプリにおいて、devise 公式で案内されている記述にエラーが吐き出されてしまった例。
スキルが高ければリファクタリングの方法がわかるのかもしれないが、現状の自分ではできなかったので、エラーを無視することに。
方法は意外と簡単だった。rubocop.yml に記述を設定すればいいだけ。
今回は2つのエラーを除外している。

inherit_from:
  - .rubocop_airbnb.yml

# Rails用に最適化
Rails:
  Enabled: true

# 文字数上限
LineLength:
  Max: 130

# devise
Airbnb/ClassOrModuleDeclaredInWrongFile:
  Exclude:
    - 'app/controllers/Users/*'

Rails/HelperInstanceVariable:
  Exclude:
    - 'app/helpers/application_helper.rb'

AllCops:
  Exclude:
    - 'db/**/*'
    - 'config/**/*'
    - 'script/**/*'
    - 'node_modules/**/*'
    - 'bin/*'
    - '**/Gemfile'
    - 'vendor/**/*'
    - '.git/**/*'


以下、参考記事
rubocop−airbnbを使うにあたって - Qiita
[Rubocop] 特定のルールを特定のファイル(ディレクトリ)でのみ無効にする | qs Developers

Docker 環境に Entrykit と ChromeDriver を導入する

課題の Docker 環境に導入されていた Entykit と、system spec を利用するための ChromeDriver を自分の環境にも導入したのでメモ。

FROM ruby:2.6.5

ENV ENTRYKIT_VERSION 0.4.0

# Entrykitのインストール
RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink

# yarnのインストール
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

# chromedriverのインストール
RUN CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` \
  && wget -N http://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip -P ~/ \
  && unzip ~/chromedriver_linux64.zip -d ~/ \
  && rm ~/chromedriver_linux64.zip \
  && chown root:root ~/chromedriver \
  && chmod 755 ~/chromedriver \
  && mv ~/chromedriver /usr/bin/chromedriver \
  && sh -c 'wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' \
  && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'

RUN apt-get update -qq && apt-get install -y \
  build-essential \
  google-chrome-stable \
  libpq-dev \
  nodejs \
  yarn \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

# 作業ディレクトリの作成、設定
RUN mkdir /ketsuatsu_app
WORKDIR /ketsuatsu_app

# ローカルのGemfileを追加
COPY Gemfile /ketsuatsu_app/Gemfile
COPY Gemfile.lock /ketsuatsu_app/Gemfile.lock
ENV BUNDLER_VERSION 2.1.2
RUN gem install bundler -v $BUNDLER_VERSION
RUN bundle install --no-deployment
COPY . /ketsuatsu_app

ENTRYPOINT [ \
  "prehook", "bundle install -j3 --path vendor/bundle", "--", \
  "prehook", "ruby -v", "--", \
  "prehook", "node -v", "--" \
]

Entykit で追加したもの

Entrykit のファイルを展開して実行。

RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink

ここら辺はテンプレ通り。ファイルをダウンロードし、展開し、Entrykit のコマンドを実行できるようにしている。

ENTRYPOINT の記述

ENTRYPOINT [ \
  "prehook", "bundle install -j3 --path vendor/bundle", "--", \
  "prehook", "ruby -v", "--", \
  "prehook", "node -v", "--" \
]

ENTRYPOINT は、コンテナ起動時に実行される命令を指定する場所。
今回は bundle install と、ruby, node.js のバージョン確認を指示している。
bundle install がないと、もし Docker をビルドした後に新しく gem を追加した場合、次のコンテナ起動時に gem がないと怒られてしまうとのこと。
buildが時間かかりすぎなせいで、検証できていないが、そういうことらしい。

ChromeDriver で追加したもの

ChromeDriver 自体のインストール

RUN CHROMEDRIVER_VERSION=`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE` \
  && wget -N http://chromedriver.storage.googleapis.com/$CHROMEDRIVER_VERSION/chromedriver_linux64.zip -P ~/ \
  && unzip ~/chromedriver_linux64.zip -d ~/ \
  && rm ~/chromedriver_linux64.zip \
  && chown root:root ~/chromedriver \
  && chmod 755 ~/chromedriver \
  && mv ~/chromedriver /usr/bin/chromedriver \

ここでは ChromeDriver の最新版を取得、展開した後、指定の場所へ移動している。

署名を追加

ChromeDriver を使うために必要。

  && sh -c 'wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' \
  && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'

Google の署名鍵をダウンロードしている。
そして、後述で google-chrome-stable をインストールしている。

System spec の準備

spec/supportフォルダの有効化

rails_helper.rb ファイル内の以下の記述をコメントアウト

Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

** Capybaraの設定
>|ruby|
# spec/support/capybara.rb
Capybara.server = :puma, { Silent: true }

Capybara.register_driver :chrome_headless do |app|
  options = ::Selenium::WebDriver::Chrome::Options.new

  options.add_argument('--headless')
  options.add_argument('--no-sandbox')
  options.add_argument('--disable-dev-shm-usage')
  options.add_argument('--window-size=1400,1400')

  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end

Capybara.javascript_driver = :chrome_headless

# Setup rspec
RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :rack_test
  end

  config.before(:each, type: :system, js: true) do
    driven_by :chrome_headless
  end
end

以上でsystem specが作動するようになる。

参考記事
GitHub - progrium/entrykit: Entrypoint tools for elegant, programmable containers
RailsのDocker環境にEntrykitを導入し、bundle installを自動実行させる方法 | Enjoy IT Life
Entrykit のすすめ - Qiita
【rails】Docker環境でSystemSpecの導入の仕方。 - 技術系ブログ
RailsのSystemTest(headless chrome)をDocker上で動かす - Qiita
Rails + postgres + chromedriverのdocker-compose環境を作る - Qiita

Docker compose で rails6 の環境構築

docker の公式と Qiita などの参考を参考に、一からアプリを docker 上で立ち上げてみた。
なお、環境は rails6, DB は postgresql で設定。

1. 必要ファイルの作成

まず最初に、アプリを作成するディレクトリを作成し、そのディレクトリに移動する。

$ mkdir myapp
$ cd myapp

アプリのルートディレクトリ上で5つのファイルを作成する。

  • Dockerfile
  • Gemfile
  • Gemfile.lock
  • entrypoint.sh
  • docker-compose.yml

以下、各々のファイルについて抜粋して説明。

Doclerfile
FROM ruby:2.6.5

RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
  && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

RUN apt-get update -qq && apt-get install -y \
    build-essential \
    libpq-dev \
    nodejs \
    vim \
    yarn \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

curl コマンドで yarn のパッケージを呼び出しているのは、rails6 の webpacker に必要なため。

パッケージをインストールするときは、apt-get update と apt-get install を1行にまとめる。
行をまとめることで古いパッケージ情報のキャッシュ利用を避けることができる。
オプション -qq は、エラー以外は表示しない。 -y は、すべての問い合わせに yes と答えるという意味。
インストールするパッケージは、アルファベット順になっていると良い。
今回使用しなかったが、--no-install-recommends オプションを使って必要最小限のインストールを一度に複数行うことができるらしい。
apt-get clean でキャッシュをクリーンにし、 /var/lib/apt/lits を削除することで、イメージのサイズを減らすことができる。

docker の公式では -qq がついているけど、ベストプラクティスでは -qq がついていない。どちらが良いのだろうか?-qq は冗長だという記事も散見された。

Doclerfile, Demfile.rock
source 'https://rubygems.org'
gem 'rails', '~>6'

公式のまま。(バージョンは rail6 に置き換え)
Gemfileの内容は、後でrails newをすると書き換えられるので、とりあえず rails だけで良い。
Gemfile.rockは空で良い。

entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

公式のまま。
set -e はエラーが発生するとスクリプトを終了するオプション
rm -f で前のサーバーが立ち上げたまま終了してしまった場合に残るファイルを削除。
exec "$@" でCMDで渡されたコマンドを実行しています。(rails server)

docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: postgres
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

公式のまま。
[version] docker-composeのバージョン。現在の最新は3。
[services] この下のハッシュにサービスを作りましょう。なんでも良いですが通常はwebとdbと名付けます。

  • image 使用するimage
  • volumes ディレクトリのマウント設定(dbデータなどを残せる)
  • build Dockerfile などがあるパス(基本的にカレントディレクトリ)
  • command コマンド(pid削除してからrails s)
  • ports ポート番号。[ホスト:コンテナ]で設定。
  • depends_on 依存関係を示していて、起動順を制御。ここではdb→webへと起動します。

2. アプリの作成とビルド

docker 上で rails new してアプリ作成

$ docker-compose run web rails new . --force --no-deps --database=postgresql --skip-test --webpacker
  • force ファイルが存在する場合、上書き。
  • no-deps リンクしたサービスを起動しない設定。
  • database=postgresql rails のデフォルトは sqlite3 のため、postgresqlを指定。
  • skip-test RSpec導入のため、minitest(テストディレクトリの作成)をスキップする
  • webpacker rails6対応。webpackerをインストールする。

rails関連ファイルができて、webpackerのインストールに成功したら、イメージをビルドする。

$ docker-compose build

3. データベースの作成と接続

rails new で作成された config/database.yml を編集して、接続先を修正。

# config/database.yml

default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: postgres
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

docker 上でデータベースを作成する

$ docker-compose run web rake db:create


特に、Dockerfile のベストプラクティスは Dockerfile で何をしているのかよくわかったので勉強になった。
次回は、作ったDockerfile に Entrykit を導入すると便利そうなので、追加してみたいと思う。

参考記事
Quickstart: Compose and Rails | Docker Documentation
Dockerfile のベストプラクティス — Docker-docs-ja 1.9.0b ドキュメント
Dockerfile に apt, apt-get, source コマンドを書く時のTips | cloud.config Tech Blog
MacにDocker+Rails6+MySQLで開発環境構築 - Qiita
DockerでRuby on Railsの環境構築を行うためのステップ【Rails 6対応】 - Qiita
Dockerを使ってRails6環境の構築をしてみる - Qiita
DockerでのRuby on Rails環境構築を一つずつ詳解する - Qiita

いつも rails server を止める方法を忘れる

rails server を立ち上げて、そのまま terminal 上で control + c で止めることができるけれど、私はたびたびこの作業を忘れて開発を終了してしまい、別の環境にアクセスするときにあああ前のサーバーが立ち上がったままだ! control + c が使えない!どうしようとあたふたする。そして方法をググっては忘れる。
たぶん、毎回忘れる原因は、そのコマンドの意味がわかってないまま使っているからだと思うので、今回立ち戻りできるように、また理解できるようにコマンドの意味を調べてみる。

起動中のサーバー一覧を表示する方法2選

ps コマンドを使う方法

$ ps aux | grep rails
ps コマンドについて

「process status」の略。Linux上で現在動作しているプロセスを一覧で表示するコマンド。

auxオプション

psコマンドのオプションで、a, u, x オプションをつなげたもの
すべての種類のプロセスの知り得るだけの情報がすべて網羅される。
ちなみに、、、

  • aオプション:端末操作のプロセスを表示する
  • uオプション:CPUやメモリの使用率なども表示する
  • xオプション:現在、実行しているプロセスを表示する

grep オプション

grepはファイル中の文字列を検索するコマンド
「global regular expression print(ファイル全体から/正規表現に一致する行を/表示する)」の略。

以上から、ps aux | grep rails は起動しているプロセスからrailsを検索して表示するコマンドとなる。

lsof コマンドを使った方法

lsof -i :3000
lsof コマンドについて

「list open files」の略。つまり、オープンしているファイルを一覧表示するコマンド。
オプション指定せずにこれだけだとめちゃ出てくる可能性あるので注意。

  • iオプション:ネットワークソケットを対象にする。

以上から、オプションで -i :3000 と指定してあげることでローカルサーバーの処理を呼び出すことができる。

サーバーを止める方法

起動中のサーバーのプロセス番号(PID)がわかったら、あとはそれを kill してあげることで止めることができる。

kill -9 (PID番号)
kill コマンドについて

起動中のプロセスを止めるコマンド

  • 9オプション:プロセスの強制終了命令


起動中のプロセスを検索=>止めたいサーバーの PID を kill でサーバーを止めることができる。

devise で使えるメソッドとカスタマイズについて

devise を使って今アプリを作っているけれど、devise は重い gem のひとつであり導入は慎重に、、みたいな記事を見つけた。サンプルで作るにはいいけど、いざ実務で作るとなった時は一から自分で作ったほうがいいのかも。

devise で使えるヘルパーメソッドについて

(Userモデルの場合)

  • before_action :authenticate_user!
  • user_signed_in?
  • current_user
  • user_session

よく使いそうな便利なメソッドが4つ用意されている。
場合によってはカスタマイズも必要。

before_action :authenticate_user!

ログイン判定に使う。コントローラーの先頭に設置。ログインユーザーにしか実行できない処理がある場合使う。
only と併用して使うこと多い。

class ArticlesController < ApplicationController
  before_action :authenticate_user!, only: [:show]

  def index
  end

  # showのときだけ authenticate_user! が発動。
  def show
  end
end
user_signed_in?

コントローラー、ビュー問わず利用でき、 authenticate_user! より細かいログイン判定の条件分岐が可能。

current_user

現在ログインしているユーザー

user_session

ユーザーのセッション情報を設定・取得できる。
今のところ使ったことがないので使う機会があったら調べて更新する。

authenticate_user! のカスタマイズ

便利なカスタマイズ方法を見つけたのでメモ。
authenticate_user! でログインしていなかった場合、デフォルトだと new_user_session_path(/users/sign_in)にリダイレクトされてしまうが、自分でログインページを自作した場合などは、そのページに飛ばすようにしたい。
github の公式 wiki ではこちらで方法が案内されている。
けれど、上記は少し面倒なので、直接自分でメソッドを作るほうが簡単に実装できる。

before_action :authenticate

def authenticate
  redirect_to new_user_registration_url unless user_signed_in?
end

いい方法を発見できて嬉しい。

git に保存された履歴をなかったことにする

1人で開発していると、レビューしてくれる人がいないわけで、railstutorial にあるように自分でブランチをマスターに merge してmaster を push しているのだが、git のリポジトリには容量があることが判明。push 失敗。原因は、サンプルアプリの機能の一つとしてPDF出力を作ろうとして追加した wicked_pdf と wkhtmltopdf-binary の gem のせいだった。これがめちゃ重くて、さらに libXrender などソフトをさらに追加しなければいけないようだったので docker でやろうと思い(docker でできるのかな??)、gem をインストールしたまま commit, merge, push までしてしまった。

merge と commit を取り消してローカルで gem に関連するファイルをすべて消去して再度挑戦。できない。調べたら、ローカルでファイルを消したからって git には履歴として残ってしまうということだった。

git に保存された履歴をなかったことにする

$ git filter-branch --tree-filter "rm -f [消したいファイルパス]" HEAD
$ git filter-branch --tree-filter "rm -f -r [消したいディレクトリパス] " HEAD
filter-branch

歴史の書き換えができる。最強のオプションと称され、大量のコミットの書き換えを機械的に行いたい場合に使う。

--tree-filter と --index-filter について

すべてのスナップショットから指定したファイル・ディレクトリを消去するには --tree-filter
ワークツリーにファイルを残したい場合は --index-filter を使う。

スナップショットとは

ファイルの変更履歴を管理する仕組み。変更されたファイルの差分のみを記録するのではなく、git が管理するすべてのファイル情報を記録すること。変更されていないファイルはそのまま記録せず、代わりに以前のバージョンを参照する情報を記録する。


全部の関連ファイルを履歴から削除して push したら、めちゃ早く push できた。
それにしてもPDF機能、どうしようかな。wicked_pdf と wkhtmltopdf-binary のファイルが重い場合の対処法を調べてみたけれどほとんどなかった。とりあえず CSV 出力機能だけつけて、一旦諦める。

参考にした記事
git filter-branch とは? - Qiita
Ubuntu Git その4 - スナップショットとは・データはチェックサムで管理される - kledgeb

devise を使用した facebook と twitter 認証について

devise を使用した facebooktwitter 認証について手順とはまった点をまとめる。

やりたいこと

  1. facebook または twitter でログインできるようにする。
  2. 次に利用する際、立ち戻れるようにまとめる。

環境

必要な gem の追加、および bundle install

gem 'omniauth-facebook'
gem 'omniauth-twitter'

(devise は既にgemfileに追加されている前提)

モデルにカラムを追加

User モデルに Omniauth 用のカラムを用意する。認証には、provideruid が必要。もし名前や画像カラムなども用意したい場合は、 name:string image:string も加える。

$ rails g migration AddOmniauthToUsers provider:string uid:string
$ rails db:migrate

プロバイダと KEY の設定

連携するプロバイダとそのKEY, SECRET_KEY を宣言。KEYは直接書かないようにする。今回はこちらの記事を参考に、Encrypted Credentials に記述した。

# config/initializers/devise.rb

Devise.setup do |config|
  config.omniauth :facebook, Rails.application.credentials.facebook[:FACEBOOK_KEY], Rails.application.credentials.facebook[:FACEBOOK_SECRET], scope: 'email', info_fields: 'email'

  config.omniauth :twitter, Rails.application.credentials.twitter[:TWITTER_API_KEY], Rails.application.credentials.twitter[:TWITTER_API_SECRET]
end

モデルにてオムニ認証を可能にする設定

Userモデルにてオムニ認証ができるように下記を追加。下のメソッドは、先ほど追加した provider と uid カラムからユーザーを探し、見つからなかったら、ランダムなパスワードと、その他情報を auth の情報を代入して DB に追加するというメソッド。

# app/models/user.rb

devise :omniauthable, omniauth_providers: %i[facebook twitter]

# omniauthのコールバック時に呼ばれるメソッド
  def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.email = User.dummy_email(auth)
      user.password = Devise.friendly_token[0, 20]
      # user.name = auth.info.name
      # user.image = auth.info.image
    end
  end

private

  def self.dummy_email(auth)
    "#{auth.uid}-#{auth.provider}@example.com"
  end

プロバイダ(今回の場合はTwitter)によってはemail情報を持たないので、パスワードとともにダミーを設定。

routes の設定

プロバイダに認証のリクエストを投げると、必要な情報を付与してプロバイダからリンクが返ってくる。それがコールバック。どのコントローラーでOmniauthコールバックを実装するかをDeviseに伝える。

# config/routes.rb

devise_for :users, controllers: { omniauth_callbacks: 'users/omniauth_callbacks' }

controller でコールバックの処理を設定

コールバックをどう処理するのかcontrollerで指示する。下では、callback_for(provider) という共通のメソッドを作って、各プロバイダに適用している。

# app/controllers/Users/omniauth_callbacks_controller.rb

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def twitter
    callback_for(:twitter)
  end

  def facebook
    callback_for(:facebook)
  end

  # common callback method
  def callback_for(provider)
    @user = User.from_omniauth(request.env["omniauth.auth"])
    if @user.persisted?
      sign_in_and_redirect @user, event: :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format?
    else
      session["devise.#{provider}_data"] = request.env["omniauth.auth"].except("extra")
      redirect_to new_user_registration_url
    end
  end

  def failure
    redirect_to root_path
  end
end

必要な View にプロバイダへのリンクを設置。

<%= link_to "Sign in with Facebook", user_facebook_omniauth_authorize_path, class: "snslogin-btn-fb" %>
<%= link_to "Sign in with Twitter", user_twitter_omniauth_authorize_path, class: "snslogin-btn-tw" %>

以上、devise への設定と Model, Controller, View と routes の設定ができたら連携が完了する。

はまった点1:会員登録に必要な条件を満たしているか

私の場合、devise のデフォルトで指定されている email の他に、ユーザー名、利用規約への同意をモデルにて必須にしてバリデーションをかけていた。そのためマニュアル通りだけではなく、必要な条件をすべて渡してあげる必要があった。

# app/models/user.rb

devise :omniauthable, omniauth_providers: %i[facebook twitter]

# omniauthのコールバック時に呼ばれるメソッド
  def self.from_omniauth(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.email = User.dummy_email(auth)
      user.password = Devise.friendly_token[0, 20]
   <b>user.account_name = auth.info.name</b>
      <b>user.accepted = "true"</b>
    end
  end

private

  def self.dummy_email(auth)
    "#{auth.uid}-#{auth.provider}@example.com"
  end
はまった点2: Twitter は email情報を request.env["omniauth.auth"] にデフォルトでは持たない。

当初、なぜか Facebook はできたのに Twitter が認証できずはまってしまった。これは、user.email = auth.info.email でプロバイダのemail情報をユーザーモデルの email に格納しようとしたが、Twitter はデフォルトで email を含んでいないため認証ができなかった。どんな情報を格納できるのか、各プロバイダの request.env["omniauth.auth"] をチェックするようにする。

やはり公式が一番。。。。
https://github.com/simi/omniauth-facebook
https://github.com/arunagw/omniauth-twitter

その他、参考にした記事
devise + omniauth で google/facebook/twitterアカウントでログイン認証を実装した際のメモ - Qiita
DeviseとOmniauthでtwitter,facebook ログイン機能 - Qiita