<<前 [TOP] 次>>
認証されたユーザがアプリケーションの全ての管理機能を使えるようにします。
まず管理用のユーザ名とパスワードを格納するモデルとデータベーステーブルの作成から始めます。
コマンドプロンプトで「bin」フォルダに移動して「rails generate scaffold User name:string password:digest
」と入力します。
途中「scaffolds.scss」を上書きするか聞いてくるので「n」と入力して上書きしないようにします。
続いてマイグレーションを実行します。
コマンドプロンプトで「rails db:migrate
」と入力してください。
ユーザモデルに具体的な内容を記述していきます。
「C:\Rails6\work\shop\app\models\user.rb」ファイルを以下のように編集します。
【C:\Rails6\work\shop\app\models\user.rb】
class User < ApplicationRecord validates :name, presence: true, uniqueness: true has_secure_password end
名前が存在してユニークであること、つまり同じ名前のユーザがデータベースに2人いないかを調べます。
「has_secure_password」はパスワードとその確認フィールドをフォームに表示する機能や指定された名前とパスワードでユーザ認証する機能を実現します。
パスワードをハッシュ化させるために「bcrypt」というgemをインストールします。
まず「Gemfile」に「gem 'bcrypt', '~> 3.1.7'」と記述を追加します。
【C:\Rails6\work\shop\Gemfile】
source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.6.6' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 6.0.2', '>= 6.0.2.2' # Use mysql as the database for Active Record gem 'mysql2', '>= 0.4.4' # Use Puma as the app server gem 'puma', '~> 4.1' # Use SCSS for stylesheets gem 'sass-rails', '>= 6' # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker gem 'webpacker', '~> 4.0' # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks gem 'turbolinks', '~> 5' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.7' # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 4.0' # Use Active Model has_secure_password # gem 'bcrypt', '~> 3.1.7' # Use Active Storage variant # gem 'image_processing', '~> 1.2' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.4.2', require: false group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] end group :development do # Access an interactive console on exception pages or by calling 'console' anywhere in the code. gem 'web-console', '>= 3.3.0' end group :test do # Adds support for Capybara system testing and selenium driver gem 'capybara', '>= 2.15' gem 'selenium-webdriver' # Easy installation and use of web drivers to run system tests with browsers gem 'webdrivers' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'rails-i18n' gem 'bcrypt', '~> 3.1.7'
コマンドプロンプトで「bundle install
」を行いgemを追加します。
「users_controller.rb」ファイルの編集を行います。
作成(create)の操作を行ったあとにユーザの表示(show)にリダイレクトするのを避けるようにします。
その代わりユーザのindexにリダイレクトしてユーザ名をフラッシュの表示に追加します。
「create()」「update()」「index()」の3つのメソッドの記述を変更しています。
【C:\Rails6\work\shop\app\controllers\users_controller.rb】
class UsersController < ApplicationController before_action :set_user, only: [:show, :edit, :update, :destroy] # GET /users # GET /users.json def index @users = User.order(:name) end # GET /users/1 # GET /users/1.json def show end # GET /users/new def new @user = User.new end # GET /users/1/edit def edit end # POST /users # POST /users.json def create @user = User.new(user_params) respond_to do |format| if @user.save format.html { redirect_to users_url, notice: "ユーザ #{@user.name}を作成しました。" } format.json { render :show, status: :created, location: @user } else format.html { render :new } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # PATCH/PUT /users/1 # PATCH/PUT /users/1.json def update respond_to do |format| if @user.update(user_params) format.html { redirect_to users_url, notice: "ユーザ #{@user.name}を更新しました。" } format.json { render :show, status: :ok, location: @user } else format.html { render :edit } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # DELETE /users/1 # DELETE /users/1.json def destroy @user.destroy respond_to do |format| format.html { redirect_to users_url, notice: 'ユーザを削除しました。' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_user @user = User.find(params[:id]) end # Only allow a list of trusted parameters through. def user_params params.require(:user).permit(:name, :password, :password_confirmation) end end
次は「_form.html.erb」ビューの編集を行います。
【C:\Rails6\work\shop\app\views\users\_form.html.erb】
<div class="depot_form"> <%= form_with(model: user, local: true) do |form| %> <% if user.errors.any? %> <div id="error_explanation"> <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2> <ul> <% user.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> <h2>Enter User Details</h2> <div class="field"> <%= form.label :name, 'Name:' %> <%= form.text_field :name, size: 40 %> </div> <div class="field"> <%= form.label :password, 'Password:' %> <%= form.password_field :password, size: 40 %> </div> <div class="field"> <%= form.label :password_confirmation, 'Confirm:' %> <%= form.password_field :password_confirmation, id: :user_password_confirmation, size: 40 %> </div> <div class="actions"> <%= form.submit %> </div> <% end %> </div>
「new.html.erb」ビューも変更しておきます。
【C:\Rails6\work\shop\app\views\users\new.html.erb】
<h1>新規ユーザの作成</h1> <%= render 'form', user: @user %> <%= link_to 'Back', users_path %>
ブラウザで「http://localhost:3000/users/new」にアクセスして確認してみます。
フォームに入力してユーザを作成してみます。
MySQLに接続してデータベース内を調べてみると、ユーザの詳細が格納されたことが確認できます。
作成したユーザを使ってユーザーの認証できるように実装していきます。
作成するのはログインとログアウトのためのセッションコントローラと管理者を迎えるためのコントローラです。
コマンドプロンプトで「bin」フォルダに移動して「rails generate controller Sessions new create destroy
」と入力してセッションコントローラを作成します。
もう一つ「rails generate controller Admin index
」と入力して管理者を迎えるためのコントローラ「Admin」を作成します。
Sessionsコントローラのcreateアクションは管理者がログイン済みであることを示す情報をsessionに記録する必要があります。
「:user_id」をキーとしてUserオブジェクトのidを格納します。
また、ログアウトをしたとき用に「destroy()」メソッドも実装しておきます。
「sessions_controller.rb」ファイルを以下のように編集します。
【C:\Rails6\work\shop\app\controllers\sessions_controller.rb】
class SessionsController < ApplicationController def new end def create user = User.find_by(name: params[:name]) if user.try(:authenticate, params[:password]) session[:user_id] = user.id redirect_to admin_url else redirect_to login_url, alert: "無効なユーザー/パスワードの組み合わせです。" end end def destroy session[:user_id] = nil redirect_to market_index_url, notice: "ログアウトしました。" end end
セッションの「new」アクション用のビューを編集します。
【C:\Rails6\work\shop\app\views\sessions\new.html.erb】
<section class="shop_form"> <% if flash[:alert] %> <aside class="notice"><%= flash[:alert] %></aside> <% end %> <%= form_tag do %> <h2>ログインしてください。</h2> <div class="field"> <%= label_tag :name, '名前:' %> <%= text_field_tag :name, params[:name] %> </div> <div class="field"> <%= label_tag :password, 'パスワード:' %> <%= password_field_tag :password, params[:password] %> </div> <div class="actions"> <%= submit_tag "ログイン" %> </div> <% end %> </section>
indexページを追加しておきます。
管理者がログイン後に最初に目にする画面です。
【C:\Rails6\work\shop\app\views\admin\index.html.erb】
<h1>ようこそ</h1> <p> 現在時刻は<%= Time.now %>です。 現在<%= pluralize(@total_orders, "件") %>の注文があります。 </p>
このコードを実際に使えるようにするにはもう一つ作業が残っています。
これまではモデルとルートの生成をscaffoldに任せていましたが、今回のコントローラにはデータベースに基づいたモデルがないのでコントローラだけを作成しました。
このコントローラに関しては従うべきscaffoldの規約がないためGETリクエストに応答すべきアクションもわからなければPOSTリクエストに応答すべきアクションもわからないといったことになります。
このような情報を指定するには「config\routes.rb」ファイルを編集します。
【C:\Rails6\work\shop\config\routes.rb】
Rails.application.routes.draw do get 'admin' => 'admin#index' controller :sessions do get 'login' => :new post 'login' => :create delete 'logout' => :destroy end get 'sessions/new' get 'sessions/create' get 'sessions/destroy' resources :users resources :orders resources :line_items resources :carts #get 'market/index' root 'market#index', as: 'market_index' resources :goods # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html end
ブラウザで「http://localhost:3000/admin」にアクセスすると、管理者用のインターフェイスが開けるようになりました。
管理者としてログインしたユーザ以外はサイトの管理ページにアクセスできないようアクセス制限をかけます。
これはRailsのフィルタ機能を使うと簡単に実装できます。
Railsではフィルタによってアクションメソッドの呼び出しを補足し、その呼出しが実行される直前や呼び出しから復帰した直後に独自の処理を加えることができます。
まずは「application_controller.rb」ファイルの実装です。
【C:\Rails6\work\shop\app\controllers\application_controller.rb】
class ApplicationController < ActionController::Base before_action :authorize protected def authorize unless User.find_by(id: session[:user_id]) redirect_to login_url, notice: "ログインしてください。" end end end
「before_action :authorize」によってこのアプリケーションのすべてのアクションの前に「authorize()」メソッドが呼び出されるようになります。
しかしこれではストア自体へのアクセスを管理者だけに制限してしまいます。
認証を必要としないメソッドやコントローラに「skip_before_action()」メソッドの呼び出しを追加します。
「market_controller.rb」「sessions_controller.rb」「carts_controller.rb」「line_items_controller.rb」「orders_controller.rb」の5つです。
【C:\Rails6\work\shop\app\controllers\market_controller.rb】
class MarketController < ApplicationController skip_before_action :authorize include CurrentCart before_action :set_cart def index @goods = Good.order(:title) end end
【C:\Rails6\work\shop\app\controllers\sessions_controller.rb】
class SessionsController < ApplicationController skip_before_action :authorize def new end def create user = User.find_by(name: params[:name]) if user.try(:authenticate, params[:password]) session[:user_id] = user.id redirect_to admin_url else redirect_to login_url, alert: "無効なユーザー/パスワードの組み合わせです。" end end def destroy session[:user_id] = nil redirect_to market_index_url, notice: "ログアウトしました。" end end
【C:\Rails6\work\shop\app\controllers\carts_controller.rb】
class CartsController < ApplicationController skip_before_action :authorize, only: [:create, :update, :destroy] before_action :set_cart, only: [:show, :edit, :update, :destroy] rescue_from ActiveRecord::RecordNotFound, with: :invalid_cart # GET /carts # GET /carts.json def index @carts = Cart.all end # GET /carts/1 # GET /carts/1.json def show end # GET /carts/new def new @cart = Cart.new end # GET /carts/1/edit def edit end # POST /carts # POST /carts.json def create @cart = Cart.new(cart_params) respond_to do |format| if @cart.save format.html { redirect_to @cart, notice: 'カートを作成しました。' } format.json { render :show, status: :created, location: @cart } else format.html { render :new } format.json { render json: @cart.errors, status: :unprocessable_entity } end end end # PATCH/PUT /carts/1 # PATCH/PUT /carts/1.json def update respond_to do |format| if @cart.update(cart_params) format.html { redirect_to @cart, notice: 'カートが更新されました。' } format.json { render :show, status: :ok, location: @cart } else format.html { render :edit } format.json { render json: @cart.errors, status: :unprocessable_entity } end end end # DELETE /carts/1 # DELETE /carts/1.json def destroy @cart.destroy if @cart.id == session[:cart_id] session[:cart_id] = nil respond_to do |format| format.html { redirect_to market_index_url, notice: 'カートが空になりました。' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_cart @cart = Cart.find(params[:id]) end # Only allow a list of trusted parameters through. def cart_params params.fetch(:cart, {}) end def invalid_cart logger.error "無効なカート(#{params[:id]})にアクセスしようとしました。" redirect_to market_index_url, notice: '無効なカートです。' end end
【C:\Rails6\work\shop\app\controllers\line_items_controller.rb】
class LineItemsController < ApplicationController skip_before_action :authorize, only: :create include CurrentCart before_action :set_cart, only: [:create] before_action :set_line_item, only: [:show, :edit, :update, :destroy] # GET /line_items # GET /line_items.json def index @line_items = LineItem.all end # GET /line_items/1 # GET /line_items/1.json def show end # GET /line_items/new def new @line_item = LineItem.new end # GET /line_items/1/edit def edit end # POST /line_items # POST /line_items.json def create good = Good.find(params[:good_id]) @line_item = @cart.add_good(good) respond_to do |format| if @line_item.save format.html { redirect_to market_index_url } format.js { @current_item = @line_item } format.json { render :show, status: :created, location: @line_item } else format.html { render :new } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end # PATCH/PUT /line_items/1 # PATCH/PUT /line_items/1.json def update respond_to do |format| if @line_item.update(line_item_params) format.html { redirect_to @line_item, notice: '品目が更新されました。' } format.json { render :show, status: :ok, location: @line_item } else format.html { render :edit } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end # DELETE /line_items/1 # DELETE /line_items/1.json def destroy @line_item.destroy respond_to do |format| format.html { redirect_to line_items_url, notice: '品目を破棄しました。' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_line_item @line_item = LineItem.find(params[:id]) end # Only allow a list of trusted parameters through. def line_item_params params.require(:line_item).permit(:good_id, :cart_id) end end
【C:\Rails6\work\shop\app\controllers\orders_controller.rb】
class OrdersController < ApplicationController skip_before_action :authorize, only: [:new, :create] include CurrentCart before_action :set_cart, only: [:new, :create] before_action :ensure_cart_isnt_empty, only: :new before_action :set_order, only: [:show, :edit, :update, :destroy] # GET /orders # GET /orders.json def index @orders = Order.all end # GET /orders/1 # GET /orders/1.json def show end # GET /orders/new def new @order = Order.new end # GET /orders/1/edit def edit end # POST /orders # POST /orders.json def create @order = Order.new(order_params) @order.add_line_items_from_cart(@cart) respond_to do |format| if @order.save Cart.destroy(session[:cart_id]) session[:cart_id] = nil OrderMailer.received(@order).deliver_later format.html { redirect_to market_index_url, notice: 'ご注文ありがとうございました。' } format.json { render :show, status: :created, location: @order } else format.html { render :new } format.json { render json: @order.errors, status: :unprocessable_entity } end end end # PATCH/PUT /orders/1 # PATCH/PUT /orders/1.json def update respond_to do |format| if @order.update(order_params) format.html { redirect_to @order, notice: 'Order was successfully updated.' } format.json { render :show, status: :ok, location: @order } else format.html { render :edit } format.json { render json: @order.errors, status: :unprocessable_entity } end end end # DELETE /orders/1 # DELETE /orders/1.json def destroy @order.destroy respond_to do |format| format.html { redirect_to orders_url, notice: 'Order was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_order @order = Order.find(params[:id]) end # Only allow a list of trusted parameters through. def order_params params.require(:order).permit(:name, :address, :email, :pay_type) end def ensure_cart_isnt_empty if @cart.line_items.empty? redirect_to market_index_url, notice: 'カートは空です。' end end def pay_type_params if order_params[:pay_type] == "クレジットカード" params.require(:order).permit(:credit_card_number, :expiration_date) elsif order_params[:pay_type] == "現金" params.require(:order).permit(:routing_number, :account_number) elsif order_params[:pay_type] == "着払い" params.require(:order).permit(:po_number) else {} end end end
これでストア内のアクセスは制限されず、管理ページのみアクセス制限がかかるようになりました。
「http://localhost:3000/carts/」にアクセスすると「http://localhost:3000/login」にリダイレクトされます。
ログインすれば「http://localhost:3000/orders/」にアクセスできるようになります。
↓↓クリックして頂けると励みになります。