Passion make things more better

Ruby on Rails / React.js / Swift / AWS / Docker

sorceryを使ってEmail + Passwordログインを実装

Sorceyを用いたログイン機能実装を解説します。

Railsのログイン機能でよく知らているdeviseよりも機能が少なく、簡単に扱えるのでこちらを採用しております。

実装の詳細に関してはSorceryのGitHubリポジトリwikiに書かれています。 今回の実装はwikiSimple Password Authenticationを参考にしていきます。

Gemの導入

以下をGemfileに追加し、bundle installを実行してください。

gem 'sorcery'

必要ファイルの生成

sorceryにはgenerateコマンドが備わっています。以下のコマンドを実行すると、必要ファイルが生成されます。

bundle exec rails g sorcery:install
      create  config/initializers/sorcery.rb
    generate  model User --skip-migration
      invoke  active_record
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert  app/models/user.rb
      insert  app/models/user.rb

migrationファイルの作成

先程のコマンドだと、migrationファイルが生成されていません。以下のコマンドを実行し、migrationファイルを作成しましょう。

※何か問われた場合は、すべて「Y」と入力し進めてください。

$ bundle exec rails g model User name:string email:string

生成されたmigrationファイルを以下のように編集してください。編集が終わったら、bundle exec rake db:migrateを実行し適用させましょう。

class CreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      t.string :name, null: false
      t.string :email, null: false
      t.string :crypted_password
      t.string :salt

      t.timestamps null: false
    end
  end
end

User modelの編集

User modelに対して、sorceryを使用するための設定およびValidationを追加します。app/models/user.rbを以下のように編集してください。

class User < ApplicationRecord
  authenticates_with_sorcery!

  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }

  validates :email, uniqueness: true
end

Routingの設定

ログイン周りのsession管理を扱うControllerをSessionsController、ユーザーの登録等を行うControllerをUsersControllerとし、以下のようにRouting(config/routes.rb)を編集します。

Rails.application.routes.draw do
  root to: 'pages#home'

  get 'login', to: 'sessions#new', as: :login
  post 'login', to: 'sessions#create'
  post 'logout', to: 'sessions#destroy', as: :logout

  resources :users, only: [:new, :create]
end

Controllerの作成

Sorceryの実装に関わる、以下の4つのControllerを実装します。

  • ApplicationController
    • ベースの設定に関する処理
  • SessionsController
    • セッションの操作に関する処理
  • UsersController
    • ユーザーの操作に関する処理
  • PagesController
    • リダイレクト先として仮で用意(こちらは適宜Routingとともに自分で変更して問題ありません)

ApplicationController

ApplicationControllerにSorceryを使用するための設定を記述します。以下のようにapp/controllers/application_controller.rbを編集してください。

class ApplicationController < ActionController::Base
  # 全ページでログインを必須とする
  before_action :require_login

  private
  # ログインをしていない場合の処理
  def not_authenticated
    redirect_to login_path, alert: 'ログインしてください。'
  end
end

SessionsController

ログイン・ログアウト周りの処理を行う、SessionsControllerを作成します。以下の内容にてapp/controllers/sessions_controller.rbを作成してください。

※以下のようにskip_before_action :require_loginを指定することによって、ログインを必須ではなくすることができます。

class SessionsController < ApplicationController
  skip_before_action :require_login, only: [:new, :create]

  def new
  end

  def create
    user = login(params[:email], params[:password])
    if user.present?
      redirect_to root_path, notice: 'ログインしました。'
    else
      redirect_to login_path, alert: 'ログインに失敗しました。'
    end
  end

  def destroy
    logout
    redirect_to login_path, notice: 'ログアウトしました。'
  end
end

UsersController

ユーザー登録を行うためのUsersControllerを作成します。以下の内容にてapp/controllers/users_controller.rbを作成してください。

class UsersController < ApplicationController
  skip_before_action :require_login

  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      login(params[:user][:email], params[:user][:password])
      redirect_to root_path, notice: 'ユーザー登録しました。'
    else
      render :new
    end
  end

  private
  def user_params
    params.require(:user).permit(:name, :email, :password, :password_confirmation)
  end
end

PagesController

リダイレクト先の仮ページとしてPagesControllerを用意します。以下の内容にてapp/controllers/pages_controller.rbを作成してください。

class PagesController < ApplicationController
  skip_before_action :require_login

  def home
  end
end

Viewの作成

ここまでで、Routing、Model、Controllerの作成が終わりました。最後に以下のViewファイルを作成します。

※Viewにbootstrap3.xを使用しているため、htmlのclassにはその要素が適用されております。

├── layouts
│   └── application.html.erb
├── pages
│   └── home.html.erb
├── sessions
│   └── new.html.erb
└── users
    └── new.html.erb

pages#home

ログイン・ログアウト及びユーザー登録ページへの導線を配置します。

<div class="container">
  <h1>ホーム</h1>
  <ul>
    <li><%= link_to 'ログイン', login_path %></li>
    <li><%= link_to 'ユーザー新規登録', new_user_path %></li>
    <% if current_user %>
    <li><%= link_to 'ログアウト', logout_path, method: :post %></li>
    <% end %>
  </ul>
</div>

sessions#new

ログインページのViewとなります。

<div class="container">  
  <div class="row">
    <div class="col-md-4"></div>
    <div class="col-md-4">
      <%= form_tag login_path, method: :post do %>
      <div class="form-group">
        <label>メールアドレス</label>
        <%= text_field_tag 'email', nil, class: 'form-control' %>
      </div>
      <div class="form-group">
        <label>パスワード</label>
        <%= password_field_tag 'password', nil, class: 'form-control' %>
      </div>
      <div class="form-group text-center">
        <%= submit_tag 'ログインする', class: 'btn btn-primary' %>
      </div>
      <% end %>
    </div>
  </div>
</div>

users#new

ユーザー登録ページのViewとなります。

<div class="container">
  <div class="row">
    <div class="col-md-4"></div>
    <div class="col-md-4">
      <% if @user.errors.any? %>
      <div id="error_explanation">
        <h2><%= @user.errors.count %>件のエラーがあります。</h2>

        <ul>
          <% @user.errors.full_messages.each do |msg| %>
          <li><%= msg %></li>
          <% end %>
        </ul>
      </div>
      <% end %>

      <%= form_for @user, url: { action: 'create' } do |f| %>
        <div class="form-group">
          <%= f.label :name %>
          <%= f.text_field :name, class: 'form-control', required: true %>
        </div>
        <div class="form-group">
          <%= f.label :email %>
          <%= f.email_field :email, class: 'form-control', required: true %>
        </div>
        <div class="form-group">
          <%= f.label :password %>
          <%= f.password_field :password, class: 'form-control', required: true %>
        </div>
        <div class="form-group">
          <%= f.label :password_confirmation %>
          <%= f.password_field :password_confirmation, class: 'form-control', required: true %>
        </div>
        <div class="form-group text-center">
          <%= f.submit '登録する', class: 'btn btn-primary' %>
        </div>
      <% end %>
    </div>
  </div>
</div>

ここまで実装を行うと、ユーザー登録 -> ログイン -> ログアウトの一連の流れを確認することができます。

sorceryでFacebookログインを実装する

個人的に最近deviceではなくsorceryを使うことが多くなりました。deviceと比較してsorceryの良い点として、必要な機能だけを使えるという所です。

今回はsorceryを使ってFacebookログインを実装する方法を紹介します。

初期設定

# Gemfile
gem 'sorcery'

# bundle install
~ bundle install --path vendor/bundle

# sorceryの初期ファイルのインストール
~ bundle exec rails g sorcery:install

# Facebookログインを実装するためのファイル等インストール
~ bundle exec rails g sorcery:install external --only-submodules

# Authentication Model作成
~ bundle exec rails g model Authentication --migration=false

設定ファイル

# config/routes.rb
post 'logout', to: 'users/sessions#destroy', as: 'logout'
get 'oauth/callback', to: 'oauth#callback'
get 'oauth/:provider', to: 'oauth#oauth', as: 'auth_at_provider'
# config/initializers/sorcery.rbの該当部分に以下を追記

Rails.application.config.sorcery.configure do |config|

  config.external_providers = [:facebook]

  config.facebook.key = Settings.facebook[:key]
  config.facebook.secret = Settings.facebook[:secret]
  config.facebook.callback_url = Settings.facebook[:callback_url]
  config.facebook.display = "page"
  # 以下3つは取得したい項目と合わせて変更する
  config.facebook.user_info_mapping = { email: "email", name: "name", gender: "gender" }
  config.facebook.user_info_path = "me?fields=email,name,gender"
  config.facebook.access_permissions = ["public_profile", "email"]
  
  
  config.user_config do |user|
    user.authentications_class = Authentication
  end

end

Migration

# db/migrate/xxxxxxxxxxx_sorcery_core.rb
class SorceryCore < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :email, null: false
      
      t.timestamps
    end

    add_index :users, :email, unique: true
  end
end
# db/migrate/xxxxxxxxxxx_sorcery_external.rb
class SorceryExternal < ActiveRecord::Migration
  def change
    create_table :authentications do |t|
      t.integer :user_id, null: false
      t.string :provider, null: false
      t.string :uid, null: false

      t.timestamps
    end

    add_index :authentications, [:provider, :uid], unique: true, name: 'ui_authentications_01'
  end
end

Model

# app/model/user.rb
class User < ApplicationRecord
  authenticates_with_sorcery! do |config|
    config.authentications_class = Authentication
  end

  has_many :authentications, dependent: :destroy
  accepts_nested_attributes_for :authentications

  validates :email, presence: true, uniqueness: true
end
# app/model/authentication.rb
class Authentication < ApplicationRecord
  belongs_to :user

  validates :user_id, presence: true
  validates :provider, uniqueness: { scope: :uid }
end

Controller

# app/controllers/app_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  before_action :require_login
  # もしログインを必要としない場合は以下を該当のControllerに記述する
  # skip_before_action :require_login

  private
  def not_authenticated
    redirect_to login_path, alert: "ログインが必要です"
  end
end
# app/controllers/users/sessions_controller.rb
class Users::SessionsController < ApplicationController
  def destroy
    logout
    redirect_to root_path, notice: 'ログアウトしました'
  end
end
# app/controllers/oauth_controller.rb
class OauthController < ApplicationController
  skip_before_action :require_login

  def oauth
    login_at(params[:provider])
  end

  def callback
    provider = params[:provider]
    if @user = login_from(provider)
      redirect_to root_path, notice: 'ログインに成功しました'
    else
      begin
        @user = create_from(provider)
        reset_session
        auto_login(@user)

        redirect_to root_path, notice: 'ログインに成功しました'
      rescue
        redirect_to root_path, alert: 'ログインに失敗しました'
      end
    end
  end

  private
  def auth_params
    params.permit(:code, :provider)
  end
end

View

# ログイン配置したいところに以下のコードを記述
<%= link_to 'Login with Facebook', auth_at_provider_path(provider: :facebook) %>

参考

Sidekiqの導入

前提

  • redisが必要です

初期設定

やることは以下の2つ。

  • Gemfileの編集
  • 設定ファイルの作成

Gemfile

Gemfileにsidekiqを記述 & bundle install。

# Gemfile
gem 'sidekiq'
gem 'redis-namespace'

~ bundle install --path vendor/bundle

設定ファイルの作成

config/initializers/sidekiq.rbを以下の内容で作成。 host, portに関してはconfigで設定し、そこから呼んでいます。

Sidekiq.configure_server do |config|
  config.redis = {
    url: "redis://#{Settings.redis.host}:#{Settings.redis.port}/#{Settings.redis.db}",
    namespace: '_xxxxxx_sidekiq'
  }
end

Sidekiq.configure_client do |config|
  config.redis = {
    url: "redis://#{Settings.redis.host}:#{Settings.redis.port}/#{Settings.redis.db}",
    namespace: '_xxxxxx_sidekiq'
  }
end

config/sidekiq.yml

:concurrency: 1
:queues:
  - default

Workerの作成

app/workers以下にworker用のファイルを作成します。 以下は例でメールを送信するworkerとして、email_worker.rb作成しています。

class EmailWorker
  include Sidekiq::Worker

  def perform(user_id)
    user = User.find_by(id: user_id)
    # ActionMailerを呼び出す処理なりを書く
  end
end

呼び出し方

上で作ったEmailWorkerを呼び出すには以下のようにします。 これで実行のキューに入ります。

EmailWorker.perform_async(1)

エンジニアがSketchを使い始める時にやったアレコレ

僕がSketchを使うにあたり初めにやったことをまとめました。

基礎知識のインプット

使い方を知らないと元も子もないので、まずは基礎知識のインプットを行いました。 とりあえず以下のリンクを読んで、ざっくりと把握しました。

UIデザインの作成ツール!誰でも分かるSketchの使い方【初心者向け】

これで抑えた点は、ざっくりと以下のような項目になります。

  • とりあえず何か置きたいときは、左上の「Insert」を押せばいいらしい
    • 押すといろいろ出てくるので、見ればわかるかと
  • artboardを使えば、デバイスに応じた画面サイズの枠を作れるらしい
  • 図形の操作(色塗ったりとか)は右側でいろいろやるらしい
    • 項目がそこまで多くないので、結構直感的に使える
    • スライスの設定も右側の下の方でできる
  • マスクは図形を置いて右クリックして「Mask」選択でできる
  • 「File」-> 「New From Template」でよくある系のベーステンプレが現れる
    • コピーして使いたいところに貼り付ける感じで使うのかな
  • ちゃんとショートカットがある
    • これは使い慣れてきたら覚えればいいかくらいに捉えました

プラグイン

「こういうことできないのかなぁ..」と考えていて、あれこれ調べていたら、どうやらプラグインなるものがあるらしい...(そりゃそうだろ)。

プラグインの管理

プラグインの管理どうやるのが良いのだろうか、と探していたらSketch Toolboxを使うのがよくあるパターンらしい。

コマンドでやったほうがいいよ、みたいに書いている人もいましたが、僕はSketch Toolboxを使いました。

使い方は開けばわかると思います笑。installの時はsearchのところに対象のプラグインをいれて、表示された物のinstallボタンを押すだけです。

とりあえず入れたプラグイン4つ

エンジニア視点でとりあえずこんなのあったらなぁ、というのを入れました。

Content Generator for Sketch

仮画像を当てるのに使えるプラグイン

Sketch Measure

フォントサイズとか余白をかき出してくれるプラグイン

Sketch Palettes

配色をパレット化して保存できるプラグイン

Sketch Use FontAwesome

エンジニアなら使ってる人多いですよね!FontAwesomeのアイコンをそのまま使えるプラグインです。 そんなアイコン一個一個作れないよ、という人が多いと思うので。

最後に

あくまでエンジニアの人がSketchを使い始めるときの初めの一歩くらいの感覚で読んでいただけると。 僕の場合、Sketchはワイヤーを作る時に使うことが多いので、これで事足りてしまいます。誰かのお役に立てると幸いです。

Railsでseedデータを分割して実行できるようにする

個人的にRailsのseed運用は以外と悩みがちです。seed-fu使ったりと色々やりましたが、これから説明する方法に落ち着きました。

  • ファイルを指定してseedを実行できるようにrake taskを作成する
  • db/seeds以下のディレクトリを作成し、以下にxxxx.rbといった実行したい処理を書いたファイルを用意
  • bundle exec rake db:seed:xxxx(作成したファイル名)で実行

rake taskの作成

コマンドでファイルを指定して実行できるようにrake taskを作成します。 ※参考に記載したサイトのコードをお借りしております。

# lib/tasks/seed.rakeとして以下を作成する

Dir.glob(File.join(Rails.root, 'db', 'seeds', '*.rb')).each do |file|
  desc "Load the seed data from db/seeds/#{File.basename(file)}."
  task "db:seed:#{File.basename(file).gsub(/\..+$/, '')}" => :environment do
    load(file)
  end
end

seedファイルの用意

db/seedsというディレクトリを作成し、以下に行いたい処理を記載したファイルを作成します。 今回は例でCategory(nameカラムを持つ)モデルに対してseedデータを挿入するファイルを作成します。

# db/seeds/category.rb

Category.create(name: 'サンプル')

seedファイルの実行

db/seeds/category.rbを以下のコマンドにて実行します。

~ bundle exec rake  db:seed:category

これでseedデータが挿入できたかと思います。 個人的にはこのやり方が一番しっくりきているので、しばらくはこちらで続けたいと思います。

参考