デモ
管理者(Admin)が職員(Worker)を作成する
Workerは店舗(Shop)に所属している
環境
$ rails -v
Rails 6.1.7
$ ruby -v
ruby 3.0.2
既にRuby、Railsの環境構築とrails newはしてある前提です。
全てのコードはこちらのリポジトリから
ER図
Workerにis_adminのようなカラムを付け足して管理者機能を実装することも考えられますが、今回はAdminとWorkerのできることが大きく違うため、別モデルで実装します。
管理者と非管理者の違いがそこまでないような場合はカラムを追加するだけの実装が簡単かもしれません。
以下のような記事参考にしてください。
https://qiita.com/kubochiro/items/340f32c366740a962cb8
deviseの導入
公式はこちら
gem 'devise'
$ bundle install
$ rails generate devise:install
<body>
の下に追加
<!DOCTYPE html>
<html>
<head>
<title>AdminWorkerShop</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body>
+ <p class="notice"><%= notice %></p>
+ <p class="alert"><%= alert %></p>
<%= yield %>
</body>
</html>
ここまでの差分
Adminの作成
$ rails generate devise Admin
$ rails db:migrate
初期Adminをseedで作成
Admin.create!(email: "admin@example.com", password: "p@ssword")
$ rails db:seed
ここまでの差分
Shopの作成
店舗のCRUDを作成
$ rails g scaffold Shop name:string
$ rails db:migrate
rootパスを追加
Rails.application.routes.draw do
resources :shops
devise_for :admins
+ root 'shops#index'
end
管理者しか店舗を作成できないようにする
class ShopsController < ApplicationController
+ before_action :authenticate_admin!
before_action :set_shop, only: %i[ show edit update destroy ]
# 以下省略
管理者のログアウトボタンを設置
<%= link_to "ログアウト", destroy_admin_session_path, method: :delete %>
ここで、rails s
をして、ログイン機能と店舗の作成等がうまく実装されているか確認してみてください!
ここまでの差分
Workerの作成
DeviseでWorkerモデルを作成
$ rails generate devise Worker
nameカラムとshop_idカラムを追加
class DeviseCreateWorkers < ActiveRecord::Migration[6.1]
def change
create_table :workers do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
+ t.string :name
+ t.integer :shop_id
# 省略
$ rails db:migrate
Workersのログインのビューを作成する
$ rails generate devise:views workers
# 省略
# ==> Scopes configuration
# Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you
# are using only default views.
- # config.scoped_views = false
+ config.scoped_views = true
# Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes (usually :user).
# config.default_scope = :user
# 省略
Shopの選択と名前の入力をできるようにする
<h2>Sign up</h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render "workers/shared/error_messages", resource: resource %>
+ <div class="field">
+ <%= f.label :name %><br />
+ <%= f.text_field :name %>
+ </div>
+ <div class="field">
+ <%= f.label :shop %><br />
+ <%= f.collection_select :shop_id, Shop.all, :id, :name, include_blank: "選択して下さい" %>
+ </div>
# 省略
ストロングパラメータを設定
class ApplicationController < ActionController::Base
+ before_action :configure_permitted_parameters, if: :devise_controller?
+
+ protected
+ def configure_permitted_parameters
+ devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :shop_id])
+ devise_parameter_sanitizer.permit(:account_update, keys: [:name, :shop_id])
+ end
end
ここまででrails s
してhttp://localhost:3000/workers/sign_up
でWorkerのログインができるか確認する
差分はこちら
Workersを作成
コントローラーの作成
$ rails g controller workers index
class WorkersController < ApplicationController
+ before_action :authenticate_admin!
def index
+ @workers = Worker.joins(:shop).all
end
end
routesを設定
Rails.application.routes.draw do
+ get 'workers/index'
devise_for :workers
resources :shops
devise_for :admins
+ resources :workers
root 'shops#index'
end
アソシエーションを設定
class Shop < ApplicationRecord
+ has_many :workers
end
class Worker < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
+ belongs_to :shop
end
ビューを作成
<h1>Workers#index</h1>
<% @workers.each do |worker| %>
<div>
名前:<%= worker.name %>
所属:<%= worker.shop.name %>
</div>
<% end %>
ここで、rails s
してみて、http://localhost:3000/workers
にアクセスして(Adminのログインが必要な場合もあり)Worker一覧が表示されれば、問題ありません。
ここまでの差分
Deviseの設定を調整
AdminとWorkerを新規作成をさせないようにする
class Admin < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
- devise :database_authenticatable, :registerable,
+ devise :database_authenticatable,
:recoverable, :rememberable, :validatable
end
class Worker < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
- devise :database_authenticatable, :registerable,
+ devise :database_authenticatable,
:recoverable, :rememberable, :validatable
belongs_to :shop
end
それぞれのログイン後のリダイレクト先を変更
class ApplicationController < ActionController::Base
before_action :configure_permitted_parameters, if: :devise_controller?
+ def after_sign_in_path_for(resource)
+ case resource
+ when Admin
+ shops_path
+ when Worker
+ worker_path(current_worker)
+ end
+ end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :shop_id])
devise_parameter_sanitizer.permit(:account_update, keys: [:name, :shop_id])
end
end
workers#showを作成し、Workerの新規登録をAdmin以外ができなくする
class WorkersController < ApplicationController
def index
authenticate_admin!
@workers = Worker.joins(:shop).all
end
def new
authenticate_admin!
@worker = Worker.new
end
def create
authenticate_admin!
Worker.create!(worker_params)
redirect_to workers_path
end
def show
authenticate_worker!
@worker = current_worker
end
private
def worker_params
params.require(:worker).permit(:name, :password, :email, :shop_id)
end
end
ここまでの変更
ビューの調整
それぞれのページ(の好きなところ)に追加
<%= link_to "Worker一覧", workers_path %>
<h4>Worker一覧</h4>
<% @shop.workers.each do |worker| %>
<%= worker.name %><br>
<% end %>
<%= link_to "新規職員", new_worker_path %>
<%= link_to 'ログアウト', destroy_admin_session_path, method: :delete %>
<%= link_to "店舗一覧", shops_path %>
<%= form_for @worker do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :shop %><br />
<%= f.collection_select :shop_id, Shop.all, :id, :name, include_blank: "選択して下さい" %>
</div>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
</div>
<div class="field">
<%= f.label :password %>
<%= f.password_field :password, autocomplete: "new-password" %>
</div>
<div class="actions">
<%= f.submit "新規登録" %>
</div>
<% end %>
<h1>Workers#Show</h1>
<%= @worker.name %>
<%= @worker.shop %>
<% if current_admin %>
<%= link_to "職員一覧", wokers_path %>
<% end %>
ここまでの変更
以上で完成になります!
あとがき
Deviseはパッと作るのには向いてるけど、カスタマイズしようとすると、仕様をちゃんと理解していないと大変。
この記事の内容で、エラーや誤植等あれば、遠慮なくご指摘ください!