複数権限でのdeviseの実装(管理者機能の作成)

管理者機能の作成

今回は、Gemのdeviseを使用して複数権限の管理者権限の作成を実施していく。(Member(会員)とAdmin(管理者))

前提条件

作業環境

エディエタ:AWS

ブラウザ:Google Chrome

Ruby : 3.1.2

Rails : 6.1.7.4

deviseはインストール済み

作業は下記を参照(deviseのインストールまで)

deviseの導入 - takifugu’s blog

作業手順

  1. モデルの作成

  2. コントローラの作成

  3. ビューの作成

  4. ルーティングの記述

  5. 不要な記述の削除

モデルの作成

会員用の情報・管理者用の情報を保存するための会員・管理者テーブルを作成するために下記のコマンドを実行。

$ rails g devise Member  #会員モデルの作成

$ rails g devise Admin  #管理者モデルの作成

上記を実行すると下記のようにファイルが作成される。

$ rails g devise Member
Running via Spring preloader in process 12143
      invoke  active_record
      create    db/migrate/20230905012027_devise_create_members.rb
      create    app/models/member.rb
      invoke    test_unit
      create      test/models/member_test.rb
      create      test/fixtures/members.yml
      insert    app/models/member.rb
       route  devise_for :members

$ rails g devise Admin
Running via Spring preloader in process 12249
      invoke  active_record
      create    db/migrate/20230905012038_devise_create_admins.rb
      create    app/models/admin.rb
      invoke    test_unit
      create      test/models/admin_test.rb
      create      test/fixtures/admins.yml
      insert    app/models/admin.rb
       route  devise_for :admins

作成されたマイグレーションファイルに必要なカラムを追加しマイグレーションする。

$ rails db:migrate
コントローラの作成

deviseのデフォルト設定のまま使用する場合は作成する必要はないが、

deviseのデフォルト設定を変更・追加が必要な場合、この手順でコントローラを作成する。

下記のコマンドを実行する。

$ rails g devise:controllers public  #会員用

$ rails g devise:controllers admin  #管理者用

上記を実行すると下記のようにファイルが作成される。

$ rails g devise:controllers public
Running via Spring preloader in process 29804
      create  app/controllers/public/confirmations_controller.rb
      create  app/controllers/public/passwords_controller.rb
      create  app/controllers/public/registrations_controller.rb
      create  app/controllers/public/sessions_controller.rb
      create  app/controllers/public/unlocks_controller.rb
      create  app/controllers/public/omniauth_callbacks_controller.rb

$ rails g devise:controllers admin
Running via Spring preloader in process 29914
      create  app/controllers/admin/confirmations_controller.rb
      create  app/controllers/admin/passwords_controller.rb
      create  app/controllers/admin/registrations_controller.rb
      create  app/controllers/admin/sessions_controller.rb
      create  app/controllers/admin/unlocks_controller.rb
      create  app/controllers/admin/omniauth_callbacks_controller.rb

上記を実行すると下記のように注意文が出てくることがありますが、後でルーティングを設定する際に変更するため、

この段階では気にしなくて良いです。(※1)

(※1)コントローラの作成時に下記のような注意文が出る。

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

  Ensure you have overridden routes for generated controllers in your routes.rb.
  For example:

    Rails.application.routes.draw do
      devise_for :users, controllers: {
        sessions: 'users/sessions'
      }
    end

上記を直訳すると、

まだセットアップしていない場合は、手動でセットアップする必要があります: routes.rbで生成されたコントローラのルートをオーバーライドしていることを確認してください。 例えば下記のようなコードにしてください。

となります。この段階では、このようにオーバーライドした形になっていないため、後で記述を変更します。

ビューの作成

次に、会員・管理者用の VIew ファイルを作成します。下記のコマンドを実行する。

$ rails g devise:views publics  #会員用

$ rails g devise:views admins  #管理者用

上記を実行するとファイルが作成される。作成ファイルが多いため、記述は省略する。

ここで作成されたファイル名(publicsとadmins)をコントローラ名と同じに変更する。 (コントローラをpublics、adminsで作成しても良いが、名称を同じにしないと、uninitialized constantエラーが発生するため、注意)

publics → public admins → admin

この記述に伴い、ビューのフォルダ内記述を変更する。(publics→public、admins→adminに変更)

例: views/public/registrations/new.html.erb

[変更前]  <%= render "publics/shared/links" %>

[変更後]  <%= render "public/shared/links" %>
ルーティングの記述

次にルーティングを記述していく。

現在は下記のようなルーティングとなっている。

config/routes.rb

Rails.application.routes.draw do
  devise_for :admins
  devise_for :members

end

先ほども記述したようにこの状態では、作成したコントローラの記述を変更したとしてもその処理を実行することができない。

また、今回はURLも変更するため、この状態では変更することがでない。

そのため、下記の記述に変更していきます。

config/routes.rb

Rails.application.routes.draw do
  # 会員用
  devise_for :members, controllers: {
    registrations: "public/registrations",
    passwords: 'public/passwords',
    sessions: 'public/sessions'
  }

  # 管理者用
  devise_for :admin, skip: [:registrations, :passwords] ,controllers: {
    sessions: "admin/sessions"
  }
end

上記のように記述します。

今回の場合、管理者側では、ログイン機能のみを作成するため、

skipオプションを使用して不要なルーティングを削除しています。

ルーティングの確認。

$ rails routes

上記のように管理者側では、ログイン機能のみのルーティングが作成されます。

不要な記述の削除

ルーティング作成時に管理者側では、ログイン機能のみを実装しましたが、

この状態では、デフォルトのビューに必要ない機能の記述があるため、リンク先がないなどのエラーが発生します。

そのため、管理者側のログイン機能以外の記述を削除していきます。

app/views/admin/shared/_links.html.erb

<%- if controller_name != 'sessions' %>
  <%= link_to "Log in", new_session_path(resource_name) %>
<% end %>

--------------------------------ここから削除--------------------------------
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
  <%= link_to "Sign up", new_registration_path(resource_name) %>
<% end %>

<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
  <%= link_to "Forgot your password?", new_password_path(resource_name) %>
<% end %>

<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
  <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
<% end %>

<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
  <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
<% end %>

<%- if devise_mapping.omniauthable? %>
  <%- resource_class.omniauth_providers.each do |provider| %>
    <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %>
  <% end %>
<% end %>
--------------------------------ここまで削除--------------------------------

上記で記述を削除完了し、サーバーにアクセスすると、ログイン画面が表示される。

以上。

Railsチュートリアル 学習記録(1.3.4)

Railsチュートリアルを進めていく、学習の記録として残しておく。(無料の過去版テキストを使用しています。Rails 5.1対応 ※記事では、rails7で実装しています。)

※回答は、2023年8月29日現在のものです。

演習問題

  1. リスト 1.12のhelloアクションを書き換え、「hello, world!」の代わりに「hola, mundo!」と表示されるようにしてみましょう。

app/controllers/application.rbの記述を下記のように書き換える。

app/controllers/application.rb

class ApplicationController < ActionController::Base

  def hello
    render html: "hello, world!"  #変更前
    ↓
    render html: "hola, mundo!"  #変更後
  end
end
  1. Railsでは「非ASCII文字」もサポートされています。「¡Hola, mundo!」にはスペイン語特有の逆さ感嘆符「¡」が含まれています(図 1.24)26 。「¡」文字をMacで入力するには、Optionキーを押しながら1キーを押します。この文字をコピーして自分のエディタに貼り付ける方が早いかもしれません。

app/controllers/application.rbの記述を下記のように書き換える。

app/controllers/application.rb

class ApplicationController < ActionController::Base

  def hello
    render html: "hola, mundo!"  #変更前
    ↓
    render html: "¡Hola, mundo!"  #変更後
  end
end
  1. リスト 1.12のhelloアクションを参考にして、2つ目のアクションgoodbyeを追加しましょう。このアクションは、「goodbye, world!」というテキストを表示します。リスト 1.14のルーティングを編集して、ルートルーティングの割り当て先をhelloアクションからgoodbyeアクションに変更します(図 1.25)。

ルーティングをgoodbyeアクションがrootになるように設定。

config/routes.rb

Rails.application.routes.draw do
  root "application#goodbye"
end

applicationコントローラにgoodbyeアクションの追加。

app/controllers/application.rb

class ApplicationController < ActionController::Base
:
  def goodbye
    render html: "goodbye, world!"
  end
end

以上。

参考

第1章 ゼロからデプロイまで - Railsチュートリアル

Railsチュートリアル 学習記録(1.3.2)

Railsチュートリアルを進めていく、学習の記録として残しておく。(無料の過去版テキストを使用しています。Rails 5.1対応 ※記事では、rails7で実装しています。)

※回答は、2023年8月29日現在のものです。

前提として、railsチュートリアルのここまでの内容実施済みです。(rails等の各種インストール、hello_appのアプリケーション作成。)

※参考は過去版ですが、実際にはrails7(最新版)で実装しています。

また、手順通りにしてもRuntime Error 「Your version of SQLite (3.7.17) is too old. Active Record supports SQLite >= 3.8」のエラーが発生したため、SQLiteのバージョンを3.43.0にしています。

演習問題

  1. デフォルトのRailsページに表示されているRubyのバージョンを見て、今の自分のコンピュータにインストールされているRubyのバージョンがそれと同じかどうかを調べてください。コマンドラインruby -vを実行することで簡単に確認できます。

↓画面の表示バージョン

ターミナル

$ ruby -v

結果

ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]

上記結果から、rubyのバージョンは画面の表示と同様であると確認できる。

  1. 上と同じ要領で、Railsのバージョンも調べてみましょう。調べたバージョンはリスト 1.2でインストールしたバージョンと一致していますか?

ターミナル

$ rails -v

結果

Rails 7.0.4

上記結果から、railsのバージョンも画面の表示と同様であると確認できる。

以上。

参考

第1章 ゼロからデプロイまで - Railsチュートリアル

Railsチュートリアル 学習記録(1.1.2)

Railsチュートリアルを進めていく、学習の記録として残しておく。(無料の過去版テキストを使用しています。Rails 5.1対応 ※記事では、rails7で実装しています。)

※回答は、2023年8月28日現在のものです。

演習問題

  1. Ruby on Railsで使うRuby gemはどのWebサイトにありますか?ヒント: 分からないときはとにかくググりましょう。

rubygems.org

  1. 現時点でのRailsの最新バージョンはいくつですか?

Rails 7.0.7.2 (2023.8.28現在)

  1. Ruby on Railsはこれまでに何回ダウンロードされたでしょうか?調べてみてください。

459,486,064回(2023.8.28現在)

以上。

参考

第1章 ゼロからデプロイまで - Railsチュートリアル

ページネーションのレイアウト変更

ページネーションのレイアウト変更

前回に引き続き、kaminariで導入したページネーションのレイアウトを変更していく。

前回の記事 URL

ページネーションの実装(gemの「kaminari」実装) - takifugu’s blog

前提条件

Bootstrap導入済み(yarnにて導入)

参考URL Bootstrapの実装 - takifugu’s blog

実装

gemのbootstrap5-kaminari-viewsを使用してレイアウトを変更していく。

Gemfileに下記を記述し、反映させる(bundle install)。

Gemfile

:
gem 'bootstrap5-kaminari-views'

ページネーションを表示させる部分に下記の記述を追加し、bootstrap5を反映させる。booksの部分は適宜変更。

<%= paginate books, theme: 'bootstrap-5' %>

theme: 'bootstrap-5'で記述をbootstrap5を反映させる。

view表示↓

Bootstrapを導入したことにより見やすいレイアウトになりました。

Bootstrapを導入した場合のデフォルトはこの状態ですが、

ここからさらに変更していきたい場合、現状のcssは、app/javascript配下のフォルダを参照しているため、

app/javascript配下のフォルダを編集することで、レイアウトの変更ができます。

※app/views/layouts/application.html.erbの記述が、下記のようになっていればcssがapp/javascript配下のフォルダを参照しています。

<%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>

stylesheet_pack_tagの部分が、stylesheet_link_tagとなっていれば、app/assets 配下のフォルダを参照します。

レイアウトの色を変更

試しに色を変更していきます。

app/javascript/stylesheets/application.scssに記述していく。

app/javascript/stylesheets/application.scss

:
 /* pagenation layouts */
$pagenation_color: rgb(177,31,255);

.page-item.active .page-link {
  background-color: $pagenation_color;
  border-color: $pagenation_color;
}

.a page-link {
  color: $pagenation_color;
}

.page-item a:hover {
  color: $pagenation_color;
}

.page-item a {
  color: $pagenation_color;
}

pagenation_colorを定義し、定義した色に変更しています。

下記のようなviewになります。

ページネーションの日本語化

最後に表示を日本語化していきます。

まず、config/application.rbに記述を追加します。

config/application.rb

:
module Bookers2Ver2
  class Application < Rails::Application
      :
      config.i18n.default_locale = :ja  #追加
  end
end

そして、config/localesフォルダにkaminari_ja.ymlファイルを作成し、記述していく。

config/locales/kaminari_ja.yml

ja:
  views:
      pagination:
        first: '最初'
        last: '最後'
        previous: '前'
        next: '次'
        truncate: '...'

サーバーを立ち上げ直すと、下記のような表示になります。

以上、レイアウトの変更方法は他にもあるため、他の方法でも試していければと思います。

ページネーションの実装(gemの「kaminari」実装)

ページネーションの実装

今回も以前作成したBooker2のアプリケーションに実装していく。

ページネーションとは

ページネーションとは、一つの長い文章を複数のページに分割し表示することです。

ページネーション機能の実装には、gemの「kaminari」を使い実装していく。

kaminariとは

ページネーションを簡単に実装することのできるgemのこと。

参考 kaminariのgithub URL

https://github.com/kaminari/kaminari

Kaminariのインストール

gemのkaminariをインストールしていく。

Gemfileに下記の記述を追加する。(バージョンは1.2.1を使用)

Gemfile

:
gem 'kaminari'~> '1.2.1'

bundle installを実行し、kaminariをインストールする。

$ bundle install

kaminariの設定ファイルを作成する下記コマンドを実行。

$ rails g kaminari:config

kaminariがページャで利用するテンプレートを作成する下記コマンドを実行。

$ rails g kaminari:views default

実装

Controller

UsersコントローラとBooksコントローラを変更していく。

まず、Usersコントローラのindexアクションとshowアクションの記述を変更する。

app/controllers/users_controller.rb

  def index
    # @users = User.all  #変更前
    @users = User.page(params[:page])  #変更後
    @user = current_user
    @book = Book.new
  end

  def show
    @user = User.find(params[:id])
    # @books = @user.books  #変更前
    @books = @user.books.page(params[:page])  #変更後
    @book = Book.new
  end

booksコントローラのindexアクションも記述を変更する。

app/controllers/books_controller.rb

  def index
    @book = Book.new
    # @books = Book.all  # 変更前
    @books = Book.page(params[:page])  # 変更後
  end

1ページ分の決められた数のデータだけを、新しい順に取得するように変更しています。 pageメソッドは、kaminariをインストールしたことで使用可能になったメソッドです。

View

viewでページネーションを表示したい場所に下記の記述を追加する。

app/views/users/index.html.erb

<%= paginate @users %>

app/views/books/_index.html.erb

<%= paginate books %>
表示件数の変更

config/initializersフォルダのkaminari_config.rbファイルに記述していく。 デフォルトでは25件の表示となっているので、5件に変更する。

config/initializers/kaminari_config.rb

Kaminari.configure do |config|
  config.default_per_page = 5
:
end

コメントアウトしているconfig.default_per_page = 25のコメントアウトを戻し、

25を5(表示したい件数)に変更。

表示の確認

サーバーを起動し、表示を確認する。

件数が設定した数より少ない場合、表示が出ないため注意すること。

以上でページネーションの実装が完了。

現状では、レイアウトが整っていないため、別の記事でレイアウトの調整予定。

URL ページネーションのレイアウト変更 - takifugu’s blog

ゲストログイン機能の実装

ゲストログイン機能の実装

今回は以前作成したアプリケーションにゲストログイン機能を実装していく。

使用するアプリケーション 参考URL 作成するアプリケーションについて - takifugu’s blog

前提条件

GemのDeviseを使用(導入済み)

仕様

  • トップページにゲストログインボタンを配置する。

  • ゲストログインボタンを押すと、ゲストユーザーでログイン状態となり、ユーザー詳細画面に遷移する。

  • ゲストユーザーのnameとemailは事前に作成しておく。

  • ゲストユーザーはユーザー編集機能を使用できないようにする。(ユーザー編集ボタンの削除、アクセス制限の実装)

実装手順

  1. ゲストログインメソッドの作成

  2. ルーティングの記述

  3. ゲストメソッドの定義

  4. ゲストログインボタンの作成

  5. ゲストログイン時、ユーザ編集ボタンの非表示

  6. ゲストユーザーのユーザー編集へのアクセス制限の実装

ゲストログイン機能の実装

ゲストログインメソッドの作成

Deviseではsign_inというメソッドが存在しており、これはユーザーをログイン状態にするものです。

通常のログイン機能はsessions_controller.rbに記述されており、Gemでインストールされているため、

ディレクトリツリーからは確認できません。

今回は、ゲストログイン用に独自のメソッドを作成するため、sessions_controllerの機能を継承して実装していきます。

app/controllersの配下にusersフォルダを作成し、フォルダ内にsessions_controller.rbを作成し、記述していきます。

app/controllers/users/sessions_controller.rb

class Users::SessionsController < Devise::SessionsController
  def guest_sign_in
    user = User.guest
    sign_in user
    redirect_to user_path(user), notice: "guestuserでログインしました。"
  end
end

ここでゲストユーザーのログインメソッドを記述しています。

Users::SessionsController < Devise::SessionsControllerでDeviseのsessions_controllerを継承するという記述。

guestメソッドは後で作成するため、現状では未定義。

sign_in後にユーザーの詳細画面に遷移し、noticeをフラッシュメッセージとして出します。

ルーティングの記述

作成したsessions_controllerへ処理を行うためのルーティングを設定します。

config/routes.rb

:
  devise_scope :user do
    post "users/guest_sign_in", to: "users/sessions#guest_sign_in"
  end
:

devise_scopeでdeviseに新しくルーティングを使用したいときや既存のURLをカスタマイズしたいときに使用する。

この記述を使用しないとエラーが発生する。

ルーティングを確認すると以下のようになっている。

$ rails routes

ゲストメソッドの定義

先程作成したゲストログインメソッドで使用していたguestメソッドを定義していく。

app/models/user.rb

:
  GUEST_USER_EMAIL = "guest@example.com"

  def self.guest
    find_or_create_by!(email: GUEST_USER_EMAIL) do |user|
      user.password = SecureRandom.urlsafe_base64
      user.name = "guestuser"
    end
  end
:

GUEST_USER_EMAIL = "guest@example.com"でゲストユーザーのemailを定義。

find_or_create_by?(条件)メソッドは、

条件としたデータが存在するかを確認し、存在する場合は、そのデータを返し、存在しない場合は、新規作成する。

また、find_or_create_by!の「!」を付与することで、処理がうまくいかなかった場合にエラーが発生するようになり、結果不具合を検知しやすくなります。

パスワードはSecureRandom.urlsafe_base64でランダムな文字列を生成しています。

nameは"guestuser"として固定しています。

このようにUser.rbに記述することで、Userモデルで使用できるメソッドとしてUser.guestの記述が可能になります。

ゲストログインボタンの作成

top画面にゲストログインボタンを作成します。

app/views/homes/top.html.erb

:
<div class="btn-wrapper mx-auto col-10">
  #ここから追加
  <div class="row">
    <%= link_to "ゲストログイン(閲覧用)", users_guest_sign_in_path, class: "btn btn-secondary btn-sm btn-block mb-3 sign_in", method: :post %>
  </div>
  #ここまで
  <div class="row">
    <%= link_to "Log in", new_user_session_path, class: "btn btn-sm btn-info btn-block mb-3" %>
  </div>
  <div class="row">
    <%= link_to "Sign up", new_user_registration_path, class: "btn btn-sm btn-success btn-block" %>
  </div>
</div>
:

これでゲストログインできるようになりました。

ここから、ゲストユーザーがユーザー編集できないように設定していきます。

(ユーザー編集できると、編集された後にゲストログインできなくなるため。)

ゲストログイン時、ユーザ編集ボタンの非表示

サイドバーのユーザー編集画面へのリンクボタンをゲストユーザーの時は、非表示にしていきます。

app/views/users/_info.html.erb

:
# if文を追加
<% if user.email != "guest@example.com" %>
  <div class="row">
    <%= link_to "",edit_user_path(user),class: "btn btn-outline-secondary btn-block fas fa-user-cog edit_user_#{user.id}" %>
  </div>
<% end %>

これでユーザーのemailがguest@example.comの時は表示しなくなりました。

ゲストユーザーのユーザー編集へのアクセス制限の実装

最後に、ユーザーの編集画面へのURLを直接入力された場合に、メッセージを表示してユーザー詳細画面へリダイレクトする記述をしていきます。

app/controllers/users_controller.rb

class UsersController < ApplicationController
  before_action :ensure_guest_user, only: [:edit, update]  
:
  private
: 
  def ensure_guest_user
    @user = User.find(params[:id])
    if @user.email == "guest@example.com"
      redirect_to user_path(current_user) , notice: "ゲストユーザーはプロフィール編集画面へ遷移できません。"
    end
  end  
end

これでゲストユーザーのログイン機能は実装完了です。

ここから一部コードをブラッシュアップし、汎用性を高く保つようにします。

ゲストユーザーであるかの判別をしている下記の箇所をメソッドを作成し、記述を変更します。

app/views/users/_info.html.erb

<% if user.email != "guest@example.com" %>

app/controllers/users_controller.rb

if @user.email == "guest@example.com"

user.rbにguest_user?というメソッドを作成します。

app/models/user.rb

  def guest_user?
    email == GUEST_USER_EMAIL
  end

このメソッドでは、emailが以前に定義したGUEST_USER_EMAIL("guest@example.com")かどうかを判断し、

正しければtrueを、異なっていればfalseを返します。

メソッドを使用した下記記述に変更していく。

app/views/users/_info.html.erb

<% unless user.guest_user? %>

app/controllers/users_controller.rb

if @user.guest_user?

このようにメソッドを作成することで、ゲストユーザーの判定処理を変更する場合、

このメソッドだけを修正すれば良いため、保守性に優れています。

以上。