前情提要:
如果今天想要對google登入或者facebook登入,做popup頁面
也就是點擊登入按鈕後,不是進入特定頁面,而是彈出一個視窗出來,這樣怎麼做呢?


安裝devise

照著下面的流程做

(1) 先安裝devise

$ bundle add devise

(2) 產生devise設定檔案

$ rails g devise:install

(3) 測試信件會寄送到local:3000

config/environment/development.rb

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

Ps. 在開發檔案放上這一段code,可以讓寄送信件的時候寄到localhost:3000,如果之後正式上線,在production.rb這個檔案,要放上正是網域的位置,寄送信件才會是正確位置

(4) 創建User的model

$ rails g devise User
$ rails db:migrate

Ps. 這樣新增後,之後想要登入,就可以用current_user這個方法,如果今天不想要用User也可以,改成rails g devise Manager就好,之後就會變成這樣,current_manager

(5) 更改devise外觀

$ rails g devise:views

(6) 客製化controller

後面那個scope,假設今天你剛剛前面的model是用User,這邊就把scope改成user

$ rails generate devise:controllers [scope]

來生成user的詳細controller,輸入rails g devise:controller user後,會生成這幾個controller的檔案給你

$ rails generate devise:controllers users

...
create  app/controllers/users/confirmations_controller.rb
create  app/controllers/users/passwords_controller.rb
create  app/controllers/users/registrations_controller.rb
create  app/controllers/users/sessions_controller.rb
create  app/controllers/users/unlocks_controller.rb
create  app/controllers/users/omniauth_callbacks_controller.rb

增加User欄位

我想要讓登入者在註冊的時候,可以多註冊一個欄位,要怎麼加呢?

(1) 在user migration新增一行

class class DeviseCreateUsers < ActiveRecord::Migration[6.1]
  def change
    ...

    t.string :name

    ...
  end
end

(2) 把migration重新設定

$ rails db:migrate:reset

(3) 加上強參數

到剛剛新增的user controller,來增加幾段code,這樣新增好後,就可以讓使用者在註冊新會員時,新增name欄位

app/controllers/users/registrations_controller.rb

> module Users
>   class RegistrationsController < Devise::RegistrationsController
>     before_action :configure_sign_up_params, only: [:create]
> 
>     ...略
> 
>     private
> 
>     # If you have extra params to permit, append them to the sanitizer.
>     def configure_sign_up_params
>       devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
>     end    
> 
>   end
> end

(4) 設定畫面

設定登入的HTML

views/shared/_navbar.html.erb

> <nav class="fixed top-0 left-0 z-50 w-full px-3 py-4 bg-white shadow md:flex md-items-center md:justify-between">
>   <div class="flex items-center justify-between w-full">
>     <span class="text-2xl cursor-pointer">
>       <%= link_to "租屋評價", root_path %>
>     </span>
>   <ul class="flex" data-controller="nav">
>     <div class="flex">
>       <div class="flex items-center">
>         <% if current_user %>          
>           <li><%#= link_to "更新暱稱", edit_admin_user_path(current_user.id), class:"mr-2 text-lg hover:text-major" %></li>
>           <li><%= image_tag current_user.avatar %></li>
>           <%#= image_tag current_user.avatar.variant(:thumb) %>
>           <li><%= link_to current_user.email, root_path, class:"mr-2 text-lg hover:text-major" %></li>
>           <li class= "text-lg btn-primary">
>             <%= link_to '登出', destroy_user_session_path, method: "delete" %>
>           </li>
>         <% else %>
>           <li class= "mr-2 text-lg hover:text-major">
>             <%= link_to '註冊', new_user_registration_path, class: "btn-primary" %>
>           </li>
>           <li class= "text-lg hover:text-major">
>             <%#= link_to '登入', new_user_session_path, class: "second-btn", data: {action: "click->nav#popup"} %>
>             <button class= "second-btn", data-action= "click->nav#popup">
>               <p>登入</p>
>             </button>
>             
>           </li>  
>         <% end %>
>       </div>
>     </div>
>   </ul>
> </nav>

渲染navbar的程式碼

views/layouts/applications.html.erb

> <html>
>   ...略
> 
>   <body>
>     <div>
>       <p class="notice"><%= notice %></p>
>       <p class="alert"><%= alert %></p>    
> 
>       <%= render "shared/navbar" %>
>       <%= yield %>
>     </div>    
>   </body>
> </html>

到devise註冊的畫面修改程式碼,照下面這樣寫,倒註冊頁面的時候,會多出一個name的欄位

views/devise/registrations/new.html.erb

> ... 略
>
>  <div class="field">
>    <%= f.label :name %><br />
>    <%= f.text_field :name, autofocus: true, autocomplete: "email" %>
>  </div>
>  
> ... 略

!!!!最重要的地方,變更路徑

Rails.application.routes.draw do
  # 原本的路徑
  # devise_for :users

  # 變更後的路徑
  devise_for :users, controllers: { registrations: 'users/registrations' }


  # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html

  # Defines the root path route ("/")
  root "home#index"
end

設定google登入

(1) 安裝需要的gem

gem "omniauth", "~> 2.1"
gem "omniauth-google-oauth2", "~> 1.1"
gem "omniauth-facebook", "~> 9.0"
gem "omniauth-rails_csrf_protection", "~> 1.0"

執行安裝上面幾的gem

$ bundle

(2) 申請google API接口

照個這個教學申請google登入需要的ID、密碼

(3) 設定環境變數

把剛剛得到的ID、密碼設定成環境變數

> GOOGLE_CLIENT_ID=XXXX
> GOOGLE_CLIENT_SECRET=XXXX

(4) 在以下檔案加入環境變數

config/initializers/devise.rb

Devise.setup do |config|
  .....
  config.omniauth :google_oauth2, ENV['GOOGLE_CLIENT_ID'], ENV['GOOGLE_CLIENT_SECRET']
end

(5) 幫剛剛的User Model,新增一格給google ID

$ rails g migration add_omniauth_to_users provider:string uid:string

上面那樣輸入後,會產生這這個檔案

> class AddOmniauthToUsers < ActiveRecord::Migration[6.1]
>   def change
>     add_column :users, :provider, :string
>     add_column :users, :uid, :string
>   end
> end
$ rails db:migrate

(6) 在User的model中建立類別方法,等等要登入用、還有幾個devise要用到的東西

model/user

> class User < ApplicationRecord
>   
>   devise :database_authenticatable, :registerable,
>          :recoverable, :rememberable, :validatable,
>          :omniauthable, omniauth_providers: [:facebook, :google_oauth2]
> 
> 
> 
>   // 加上這一段
>   def self.create_from_provider_data(provider_data)
>    where(email: provider_data.info.email).first_or_create do |user|
>      user.email = provider_data.info.email
>      user.password = Devise.friendly_token[0, 20]
>      user.name = provider_data.info.last_name
>      user.provider = provider_data.provider
>      user.uid = provider_data.uid
>    end
>   end
> end

(7) 增加對應的路徑

routes

> Rails.application.routes.draw do
>   // 增加這一段
>   devise_for :users, controllers: { omniauth_callbacks: "users/omniauth_callbacks" }

>   root "home#index"
> end

(8) 在controller增加相對應方法

app/controllers/user/omniauth_callbacks_controller.rb

> class User::OmniauthCallbacksController < Devise::OmniauthCallbacksController
> 
>   # 新增此段第三方登入的方法
>   def google_oauth2
>     @user = User.create_from_provider_data(request.env["omniauth.auth"])
>     if @user.persisted?
>       flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Google"
>       sign_in_and_redirect @user, :event => :authentication
>     else
>       session["devise.google_data"] = request.env["omniauth.auth"]
>       redirect_to new_user_registration_url
>     end
>   end
>   # 及這段第三方登入失敗的處理方法
>   def failure
>     redirect_to root_path
>   end
>   
> end

Ps. 這樣都設定好之後,就可以實際登入看看了,應該會成功才對!!

登入/註冊使用POPUP的方法

(1) 新增popup區塊

Ps. 一開始這個section是隱藏的,當我們點擊右上角的登入後,才跳出這個popup,因此要把此區塊的hidden刪掉

_navbar.html.erb

> <!-- ads/popup.html.erb -->
> <section class="hidden" data-controller="nav-pop-up">
>   <div class="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
>     <%# <i class= "absolute text-xs fa-solid fa-x"></i> %>
>     <!-- 背景是半透明的黑色蓋板廣告 -->
>     <div class="p-20 bg-white rounded-lg w-96">
> 
>       <!-- 白色蓋板廣告的內容 -->
> 
>       <%# 關閉icon,因為是用絕對定位,所以今天如果改變白色框框的大小,叉叉icon都要再調整過 %>
>       <i class= "absolute text-xs fa-solid fa-x right-[47em] top-[25em]"></i>
>             
>       <h2 class="mb-4 text-2xl font-bold">社群登入</h2>
> 
>       <%# 原始的登入code %>
>       <%#= button_to "google登入", user_google_oauth2_omniauth_authorize_path, data: {turbo: "false"}, class: "btn-primary rounded-full w-full " %>    
>           
>       <%# google登入按鈕 %>
>       <%= button_to user_google_oauth2_omniauth_authorize_path, data: {turbo: "false"}, class: "inline-flex px-6 py-2 cursor-pointer hover:bg-blue-100 items-center 
>       rounded-full w-full border bg-white flex text-black" do %>        
>         <div class="w-1/6 mr-8">
>           <img src="/assets/google_logo.png" class="object-cover w-full h-full">
>         </div>
>         <div class="">
>           <span>google登入</span>
>         </div>                     
>       <% end %>
> 
>     </div>
>   </div>
> </section>

(2) 把navbar按鈕加上controller、action

  1. 先在navbar設定controller
  2. 把社群登入按鈕,加上action
_navbar.html.erb

> <nav class="fixed top-0 left-0 z-50 w-full px-3 py-4 bg-white shadow md:flex md-items-center md:justify-between" data-controller="nav-bar">
>    ...略
>    <button class= "mx-5 second-btn", data-action="click->nav-bar#login">
>      <p>社群登入</p>
>    </button>   
> </nav>

(3) 設定stimulus js,要讓兩個js溝通

nav_bar_controller.js

>  login(){
>    const evt = new CustomEvent("popup")
>    window.dispatchEvent(evt)
>  }

(4) pop頁面接收剛剛那一個action,並且多設定一個target,當我們按下登入按鈕,要把這個section的hidden css給刪掉

_navbar.html.erb

> <section class="hidden" data-controller="nav-pop-up"
>                         data-nav-pop-up-target="page"
>                         data-action="popup@window->nav-pop-up#showPop">
> 
> </section>                        

(5) 設定nav-pop-up的js,當我們接收到按下登入按鈕的動作時,刪除hidden

nav_pop_up_controller.js

> showPop() {    
>   this.pageTarget.classList.remove("hidden")
> }

(6) 針對頁面的叉叉,增加一個target,按下叉叉的時候,要把hidden加上去,所以我們對icon增加一個action

_navbar.html.erb

> <section class="hidden" data-controller="nav-pop-up"
>                         data-nav-pop-up-target="page"
>                         data-action="popup@window->nav-pop-up#showPop">
>   ...略

>   <i class= "absolute text-xs fa-solid fa-x right-[47em] top-[25em] cursor-pointer" data-action="click->nav-pop-up#close"></i>

>   ...略
> </section>                        

(7) 寫叉叉action的JS

nav_pop_up_controller.js

> close() {
>   this.pageTarget.classList.add("hidden")
> }

結論

這樣做就完成google登入的popup頁面設定,不過要記得,rails 6 和 rails 7 的設定有點不一樣,因為 rails 7 turbo 的關係,所以今天想要點連結用POST action送出的時候,方式有改變。

rails 6

google登入連結,用post送出

> <%= link_to "google登入".html_safe, user_google_oauth2_omniauth_authorize_path, method: :post %>  

rails 7

google登入連結,用 post 送出,並且把 turbo 關掉

> <%= button_to "google登入", user_google_oauth2_omniauth_authorize_path, data: {turbo: "false"}, class: "btn-primary rounded-full w-full " %>    

在 button_to 按鈕中,加上其他文字 or 圖片

> <%= button_to user_google_oauth2_omniauth_authorize_path, data: {turbo: "false"}, class: "inline-flex px-6 py-2 cursor-pointer hover:bg-blue-100 items-center 
> rounded-full w-full border bg-white flex text-black" do %>        
>   <div class="w-1/6 mr-8">
>     <img src="/assets/google_logo.png" class="object-cover w-full h-full">
>   </div>
>   <div class="">
>     <span>google登入</span>
>   </div>                     
> <% end %>

rails 7 特別注意事項

rails 7 登入正確範例

多新增一個google登入連結在pop頁面上,如果今天你是用rails 7想要做popup的登入頁面,有一個地方要修改,像下面這樣

> <section>
>   <div class="fixed top-0 left-0 z-50 flex items-center justify-center w-full h-full bg-gray-500 opacity-70">    
>     <div class="w-2/5 py-10 bg-white">
>       <p>123</p>      

>       -下面這一行-
>       <%= button_to "google登入", user_google_oauth2_omniauth_authorize_path, data: {turbo: "false"} %>  

>     </div>
>   </div>
> </section>

Ps. 一定要用button_to、把turbo關掉,要不然會遇到CORS的問題

rails 7 登入錯誤範例

(1) 一樣使用 link_to - method: :post

> <%= link_to "google登入".html_safe, user_google_oauth2_omniauth_authorize_path, method: :post %>      

會噴出以下錯誤

Not found. Authentication passthru.

(2) 改成rails 7 專用的寫法

> <%= link_to "google登入".html_safe, user_google_oauth2_omniauth_authorize_path, data: {turbo_method: "post"} %>      

會噴出以下錯誤

Access to fetch at 'xxx' (redirected from 'http://localhost:3000/users/auth/google_oauth2') from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

(3) 把turbo關掉

> <%= link_to "google登入".html_safe, user_google_oauth2_omniauth_authorize_path, method: :post, data: {turbo: false} %>      

會噴出以下錯誤

Not found. Authentication passthru.