【deviseに疲れた人のための】sorceryでユーザログイン機能を実装
deviseの代替案として、前々からsoceryいいよなんてきいてたので使ってみます。
@komagata さんにおききしたらつらみはそんなに減らないとなったのが一瞬頭によぎる
@komagata devise、つらみを感じてます...。あとはSorceryとかをよくきくのですがどうなのですかね?
— 凸(とつ) Totz Yuta (@totzyuta) December 5, 2015
@totzyuta sorceryもそれほどつらみが減ってない感じしてます。monbanが気になってますがstarが少なくて怖いです… https://t.co/trOLzHtXWh
— Masaki Komagata (@komagata) December 5, 2015
まぁ…使ってみないことには分からないのでやってみましょう。monbanも気になるところですが。
Special Thanks
- NoamB/sorcery
公式が一番。
sorcery/README.md at master · NoamB/sorcery · GitHub
- workabroad
コードめちゃくちゃ参考にさせていただきました。
- classmethod
- RailsCasts
わかりやすいのですがRailsとRubyがかなり古いので注意。
#283 Authentication with Sorcery - RailsCasts
こんな感じ
最終形態はこんな感じに、
- ユーザ登録
- ログイン
- ログアウト
できる感じになります。
初期設定
環境
今回実装している環境はこんな感じ。
$ cat .ruby-version 2.3.0 $ be rails -v Rails 5.0.0.beta3
インストール
Gemfileにsorceryを追加
# Gemfile gem "sorcery"
インストールします。ひとまずsubmoduleはremember_meだけで。できるだけミニマムに保ちたいので必要になってから追加していくようにします。
$ rails generate sorcery:install remember_me create config/initializers/sorcery.rb gsub config/initializers/sorcery.rb generate model User --skip-migration Running via Spring preloader in process 31534 invoke active_record create app/models/user.rb invoke rspec create spec/models/user_spec.rb invoke factory_girl create spec/factories/users.rb insert app/models/user.rb insert app/models/user.rb create db/migrate/20160321031406_sorcery_core.rb create db/migrate/20160321031407_sorcery_remember_me.rb
追加の場合は以下のようにすればokです。
$ rails generate sorcery:install remember_me --only-submodules
ログインさせたいモデルのクラス名がUser
でない場合はconfigファイルで変更が必要です。この部分。
config.user_class = "User"
username_attributeの変更
また、今回はemailだけでなくusername(Twitterでいうid的なイメージ)でもログインできるようにしたいと思います。
まずは以下ようにarrayにusernameも入れてあげます。
Rails.application.config.sorcery.configure do |config| config.user_config do |user| # -- core -- # specify username attributes, for example: [:username, :email]. # Default: `[:email]` # user.username_attribute_names = %i(email username) # ここ修正 end end
そしたら、最初に生成されたSorceryCore
のmigrationファイルもいじっておきます。
class SorceryCore < ActiveRecord::Migration def change create_table :users do |t| t.string :username, null: false # ここ追加 t.string :email, null: false t.string :crypted_password t.string :salt t.timestamps end add_index :users, :email, unique: true end end
Model
Userのモデルファイルも確認だけしてみます。空のクラスファイルにauthenticates_with_sorcery!
というメソッドが追加されてました。これでUserモデルで必要なクラスメソッドとインスタンスメソッドが得られるというわけですね。
ということで必要に応じてvalidationなど設定しときます。
# app/models/user.rb class User < ApplicationRecord authenticates_with_sorcery! validates :username, presence: true, uniqueness: true validates :email, presence: true, uniqueness: true validates :password, presence: true, length: { minimum: 6 }, if: -> { new_record? || changes["password"] } end
# spec/models/user.rb require 'rails_helper' RSpec.describe User, type: :model do describe "validations" do it { is_expected.to validate_presence_of(:email) } it { is_expected.to validate_uniqueness_of(:email) } it { is_expected.to validate_presence_of(:username) } it { is_expected.to validate_uniqueness_of(:username) } it { is_expected.to validate_presence_of(:password) } end end
Routing
ルーティングの設定をしておきます。こんな感じで。
# config/routes.rb Rails.application.routes.draw do root "users#new" resources :users, only: %i(new create show) resources :sessions, only: %i(new create destroy) end
Controller
次はcontroller部分を作っていきます。
$ bin/rails g controller users new
# app/controllers/users_controller.rb class UsersController < ApplicationController def new @user = User.new end def show @user = User.find(params[:id]) end def create @user = User.new(user_params) if @user.save redirect_to user_path(@user), notice: "Signed up!" else render :new, notice: "Failed to signin" end end private def user_params params.require(:user).permit(:username, :email, :password) end end
セッションを管理するcontrollerをnewアクションをつけて生成しておきます。
$ bin/rails g controller sessions new
# app/controllers/sessions_controller.rb def new end def create user = login(params[:email], params[:password], params[:remember_me]) if user redirect_back_or_to user_path(user), notice: "Logged in!" else flash.now[:alert] = "Failed to login" render :new end end def destroy logout redirect_to root_path, notice: "Logged out!" end
View
続いてViewを作っていきます。
k0kubunさんにお世話になります。hamlitを使います。
# Gemfile gem "hamlit"
Viewファイルをそれぞれ作成します。
users/new.html.haml
%h2 Registering New User = form_for @user do |f| = render 'shared/error_messages', object: f.object .field = f.label :username = f.text_field :username .field = f.label :email = f.text_field :email .field = f.label :password = f.password_field :password .actions = f.submit %p or = link_to "login", new_session_path
users/show.html.haml
%h2 User MyPage = "username: #{@user.username}" %br = "email: #{@user.email}" %br = link_to "log out", session_path, method: :DELETE
sessions/new.html.haml
%h2 User Login = render 'shared/flash_message' - if logged_in? = current_user.username = current_user.email = form_tag sessions_path do .field = label_tag :username = text_field_tag :username, params[:username] .field = label_tag :email = text_field_tag :email, params[:email] .field = label_tag :password = password_field_tag :password .field = check_box_tag :remember_me, 1, params[:remember_me] = label_tag :remember_me .actions = submit_tag "Log in" %p or = link_to "Signup", new_user_path
おわり
ということでこんな感じで完成!
sorceryを使った感じでは「ユーザログインに必要なメソッドを一式提供してくれますよ」くらいなgemの印象。
deviseは「ユーザログインに必要なこと全部やります」って感じにcontrollerのアクションのロジックまで全て用意するのに対してsorceryはビジネスロジックだけ提供してくれる、みたいな。
もう少しsorcery使ってみます。今のところ好印象。