Rails4.2とdeviseでFacebookとTwitter認証をテストした時に[Routing Error uninitialized constant]が発生

RailsとdeviseでFacebookTwitter認証をテストしたらログアウト時にエラー

参考情報

RailsとdeviseでFacebookTwitter認証を行う際に、以下のページを参考にした

Rails4 で Devise と OmniAuth で、Twitter/Facebook のOAuth認証と通常フォームでの認証を併用して実装 | EasyRamble
※ほとんどこのページを参考

環境

Rails 4.2.0
ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-darwin14.0]
devise 3.4.1
omniauth 1.2.2
omniauth-facebook 1.4.1
omniauth-twitter 1.0.1

エラー内容

ログインはできるがログアウト時に以下のエラーが発生

Routing Error
uninitialized constant Users::SessionsController
エラー対応

sessions_controllerでエラーが出ているので、sessions_controller.rbファイルを作成

devise_test/app/controllers/users/sessions_controller.rb

class Users::SessionsController < Devise::SessionsController

end

※中身は空でOK

これでログアウトエラーは解決!

作業内容まとめ

実行コマンド

プロジェクト作成
$ rails new devise_test
Gemfileに追記

project/Gemfile

...

  gem 'devise'
  gem 'omniauth', '~> 1.2.1'
  gem 'omniauth-twitter', '~> 1.0.1'
  gem 'omniauth-facebook', '~> 1.4.0'
end
モジュールをインストール
$ cd devise_test
$ bundle install
トップページを作成
$ rails g controller welcome index
deviseをインストール
$ rails generate devise:install
      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4. If you are deploying on Heroku with Rails 3.2 only, you may want to set:

       config.assets.initialize_on_precompile = false

     On config/application.rb forcing your application to not access the DB
     or load models when precompiling your assets.

  5. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================
deviseのユーザモデルを作成
$ rails g devise User
      invoke  active_record
      create    db/migrate/20150103071332_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users
deviseのユーザモデルにOmniAuth用のカラムを追加
$ rails generate migration AddOmniauthColumnsToUsers uid provider name
      invoke  active_record
      create    db/migrate/20150103071704_add_omniauth_columns_to_users.rb
migrationファイルを修正
class AddOmniauthColumnsToUsers < ActiveRecord::Migration
  def change
    add_column :users, :uid, :string,      :string, null: false, default: ""
    add_column :users, :provider, :string, :string, null: false, default: ""
    add_column :users, :name, :string
    
    add_index :users, [:uid, :provider], unique: true
  end
end
migrate実行
$ rake db:migrate
== 20150103071332 DeviseCreateUsers: migrating ================================
-- create_table(:users)
   -> 0.0150s
-- add_index(:users, :email, {:unique=>true})
   -> 0.0017s
-- add_index(:users, :reset_password_token, {:unique=>true})
   -> 0.0008s
== 20150103071332 DeviseCreateUsers: migrated (0.0178s) =======================

== 20150103071704 AddOmniauthColumnsToUsers: migrating ========================
-- add_column(:users, :uid, :string)
   -> 0.0015s
-- add_column(:users, :provider, :string)
   -> 0.0011s
-- add_column(:users, :name, :string)
   -> 0.0014s
== 20150103071704 AddOmniauthColumnsToUsers: migrated (0.0043s) ===============
bootstrapのファイルコピー&定義

bootstrap-3.3.1-dist.zipのファイルを以下にコピー

devise_test/vendor/assets/javascripts/bootstrap.min.js
devise_test/vendor/assets/stylesheets/bootstrap.min.css

bootstrapの定義を追加

devise_test/app/assets/javascripts/application.js

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap.min
//= require_tree .

devise_test/app/assets/stylesheets/application.css

 *= require_tree .
 *= require_self
 *= require bootstrap.min
 */
user.rbの編集
class User < ActiveRecord::Base
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :omniauthable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable

  def self.find_for_facebook_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create(name:     auth.extra.raw_info.name,
                         provider: auth.provider,
                         uid:      auth.uid,
                         email:    auth.info.email,
                         password: Devise.friendly_token[0,20]
                        )
    end
    user
  end
 
  def self.find_for_twitter_oauth(auth, signed_in_resource=nil)
    user = User.where(:provider => auth.provider, :uid => auth.uid).first
    unless user
      user = User.create(name:     auth.info.nickname,
                         provider: auth.provider,
                         uid:      auth.uid,
                         email:    User.create_unique_email,
                         password: Devise.friendly_token[0,20]
                        )
    end
    user
  end
 
  # 通常サインアップ時のuid用、Twitter OAuth認証時のemail用にuuidな文字列を生成
  def self.create_unique_string
    SecureRandom.uuid
  end
 
  # twitterではemailを取得できないので、適当に一意のemailを生成
  def self.create_unique_email
    User.create_unique_string + "@example.com"
  end
 
end
devise定義ファイルにFacebookTwitterのアプリキーを登録
Devise.setup do |config|

...

  # When using omniauth, Devise cannot automatically set Omniauth path,
  # so you need to do it manually. For the users scope, it would be:
  # config.omniauth_path_prefix = '/my_engine/users/auth'
  # API key
  config.omniauth :facebook, "App ID", "App Secret"
  config.omniauth :twitter,  "Consumer Key", "Consumer Secret"
end
route.rbの設定
Rails.application.routes.draw do
  devise_for :users, :controllers => {
    :sessions      => "users/sessions",
    :registrations => "users/registrations",
    :passwords     => "users/passwords",
    :omniauth_callbacks => "users/omniauth_callbacks" 
  }

  root to: 'welcome#index'
end
トップ画面作成
<!DOCTYPE html>
<html>
<head>
  <title>deviseテスト</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>

  <header class="navbar navbar-default" role="navigation">
    <div class="container">
      <div class="navbar-header">
        <%= link_to 'deviseテスト', root_path, class: 'navbar-brand' %>
      </div>
      <div class="navbar-text pull-right">
        <% if user_signed_in? %>
          Logged in as <strong><%= current_user.name %></strong>.
          <%= link_to 'Edit profile', edit_user_registration_path, :class => 'navbar-link' %> |
          <%= link_to 'Sign out', '/users/sign_out', :method => :delete, :class => 'navbar-link'  %>
        <% else %>
          <%= link_to "Sign up", new_user_registration_path, :class => 'navbar-link'  %> |
          <%= link_to "Login", new_user_session_path, :class => 'navbar-link'  %> |
          <%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) , :class => 'navbar-link' %> |
          <%= link_to "Sign in with Twitter", user_omniauth_authorize_path(:twitter) , :class => 'navbar-link' %>
        <% end %>
      </div>
    </div>
  </div>
</header>

<div class="container">
  <% if notice %>
    <p class="alert alert-success"><%= notice %></p>
  <% end %>
  <% if alert %>
    <p class="alert alert-success"><%= alert %></p>
  <% end %>
  <%= yield %>
</div>
</body>
</html>
OmniauthCallbacksControllerコントローラーを作成

以下のファイルを作成
devise_test/app/controllers/users/omniauth_callbacks_controller.rb

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    # You need to implement the method below in your model (e.g. app/models/user.rb)
    @user = User.find_for_facebook_oauth(request.env["omniauth.auth"], current_user)
 
    if @user.persisted?
      set_flash_message(:notice, :success, :kind => "Facebook") if is_navigational_format?
      sign_in_and_redirect @user, :event => :authentication
    else
      session["devise.facebook_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end
 
  def twitter
    # You need to implement the method below in your model
    @user = User.find_for_twitter_oauth(request.env["omniauth.auth"], current_user)
 
    if @user.persisted?
      set_flash_message(:notice, :success, :kind => "Twitter") if is_navigational_format?
      sign_in_and_redirect @user, :event => :authentication
    else
      session["devise.twitter_data"] = request.env["omniauth.auth"].except("extra")
      redirect_to new_user_registration_url
    end
  end
end

このままだとログアウト時にエラーが発生する

ログインはうまくいくが、ログアウトすると以下のエラーが発生する。

Routing Error
uninitialized constant Users::SessionsController
SessionsControllerクラスを作成

エラー対応のため、以下のファイルを作成

devise_test/app/controllers/users/sessions_controller.rb

class Users::SessionsController < Devise::SessionsController

end

今回作成したプロジェクトは以下
yuriken27/rails_devise_test · GitHub