【Rails】devise_invitable 使ってみる

はじめに

業務でdevise_invitablegemを使っているプロジェクトがあるが、自分で実装したことはなかったためカスタマイズなしで手順を確認しました。

環境

  • Rails 7.0.8
  • ruby 3.2.2
  • devise 4.9.2
  • devise_invitable 2.0.8

前提

  • rails newした後にdeviseをインストールし、Userを作成。認証しているUserのみアクセスできるUser一覧画面のみ作成済
  • letter_openerを入れ、メールが確認できるように設定済

そこからdevise_invitableを導入する手順です。

手順

① gemfileに追加

gem 'devise_invitable'

② インストール

$ rails g devise_invitable:install

③ deviseで作成したモデルでinvitableが使えるようにする

$ rails g devise_invitable User
$ rails db:migrate
  • マイグレーションファイルxxxxxxxxxx_devise_invitable_add_to_users.rbが作成される。invitableで使用するカラムがUsersテーブルに追加される
  • Userモデルにdevise :invitableが挿入される

この時点でrails routesで確認すると、invitationのパスが設定されている

$ rails routes
...
                  accept_user_invitation GET    /users/invitation/accept(.:format)                                                                devise/invitations#edit
                  remove_user_invitation GET    /users/invitation/remove(.:format)                                                                devise/invitations#destroy
                     new_user_invitation GET    /users/invitation/new(.:format)                                                                   devise/invitations#new
                         user_invitation PATCH  /users/invitation(.:format)                                                                       devise/invitations#update
                                         PUT    /users/invitation(.:format)                                                                       devise/invitations#update
                                         POST   /users/invitation(.:format)                                                                       devise/invitations#create

④ viewを作成

rails g devise_invitable:views

invoke  DeviseInvitable::Generators::MailerViewsGenerator
exist    app/views/devise/mailer
create    app/views/devise/mailer/invitation_instructions.html.erb
create    app/views/devise/mailer/invitation_instructions.text.erb
invoke  simple_form_for
create    app/views/devise/invitations
create    app/views/devise/invitations/edit.html.erb
create    app/views/devise/invitations/new.html.erb

⑤ ユーザーを作成してみる

Userモデルからinviteメソッドを使用して、Userを作成しつつ招待メールを送信。

$ rails c
> User.invite!(email: 'makky@example.com')

inviteメソッドから招待メールが送信される

招待リンクから遷移するとパスワード入力が入力できる

パスワードを設定して、rootに設定したページに遷移

Userが作成され、ログイン済になった!

【Rails】名前空間のあるモデルのspec

はじめに

前回名前空間のあるモデルを作成しました。

【Rails】テーブル名にプレフィックスを付与・ネームスペース付きのモデルを作成 - まっきーの学習記録

どうやってスペックを書くのか調べたので、まとめます。

方法

本記事でproject名前空間の名前です。

それぞれのアプリで置き換えてください。

(1つのアプリに複数プロジェクトを共存させるという目的があったため、projectとしています。)

Factoryの作成

spec/factories/users.rbを作るようなところ、spec/factories/project/users.rbを作成。

classに名前空間付きのクラス名を渡す。

そのほかは通常の書き方でOK。

FactoryBot.define do
  factory :project_user, class: 'Project::User' do
    ...
  end
end

System Spec 等で呼び出し

spec側での呼び出しはfactory名をそのまま呼び出すだけなので、通常通りでOK。

let(:user) { FactoryBot.create(:project_user, email: 'test@example.com') }

終わりに

Factoryを作成する時にclass名を指定してあげるだけで良いので想像以上に簡単でした。

ちなみにディレクトリ構造のイメージは以下のような感じです。 ご参考までに。

spec
  └factories
    └projectA
      └users.rb
      └admins.rb
    └projectB
      └users.rb
      └admins.rb
  └system
    └projectA
    └projectB
  └...(他のspec等)

【Rails】テーブル名にプレフィックスを付与・ネームスペース付きのモデルを作成

はじめに

ある2つのプロジェクトを1つのプロジェクトとして統合して管理したいため、Railsアプリの各ディレクトリ内を以下のようにプロジェクト毎に取り扱いということがありました。

models
  └ProjectA
    └User.rb
    └Admin.rb
    └...
  └ProjectB
    └User.rb
    └Admin.rb
    └...

そのためテーブル名にプレフィックスをつけて管理、モデルもそれに対応する必要があり、この記事を書いています。

ネームスペース付きのモデルを新規で作成してみる

新規でテーブル名にプレフィックスを付与・ネームスペース付きのモデルを作成する場合は以下のようにネームスペース名::をつけてモデルを作成します。

rails g model Project::User email:string

ネームスペース付きでrails g modeをすると以下のファイルが作成されます。

①マイグレートファイル

class CreateProjectUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :project_users do |t|
      t.string :email

      t.timestamps
    end
  end
end

self.table_name_prefixをオーバーライドしてくれるmodule

module Project
  def self.table_name_prefix
    "project_"
  end
end

③Projectのネームスペース付きUserモデル

class Project::User < ApplicationRecord
end

通常rails g modelする時との違いとして以下があります。

  • ① table名にプレフィックスが付与される(project_users)
  • ② self.table_name_prefixをオーバーライドするmoduleが作成される
    • Project::Userからfindやwhereをした際に、project_xxxxのテーブルを参照するようになる
  • ③ モデル名にProject::のようなネームスペースが付与される

既存のプロジェクトをネームスペースに分けたい時

既存のプロジェクトをネームスペースで分けたいけど、テーブル名は変えたくありませんでした。

その際は前述のapp/models/project.rbにあたるself.table_name_prefixをオーバーライドするファイルを作成せず、③のようにネームスペース(Project::)をつけてあげるだけで問題ありませんでした。

Project::User.findすると発行されるSQLで参照されるテーブル名はusersのままです。

また、controller等でUser.findみたいに呼び出している箇所はProject::User.findのように呼び出すよう修正してみてください。

【puma-dev】 使い方・躓いたこと

初めに

puma-devを使ってみたので、使い方や躓きを簡単にまとめてみました。

概要

pumaとpuma-devに関してさらっと振り返り。

puma

Ruby on RailsのWebアプリケーションに使用されるWebサーバー。

スレッドベースのアーキテクチャを使用し、複数のリクエストを同時に処理する能力を提供している。

スレッドベースのアーキテクチャとは、複数のスレッド(処理の最小単位)を使用して並行処理を実現する方法。

puma-dev

pumaで起動したサーバの一元管理を提供してくれる。

アクセスを受け付けると自動でpumaサーバを立ち上げ、一定時間アクセスがないとスリープにするなど、常にサーバが立ち上がっているような感覚で利用することが可能になる。

使い方

Homebrewでインストール

macを使用しているため、Homebrewからpuma-devをインストールします。

$ brew install puma/puma/puma-dev

DNS設定

puma-devを使うための設定を行います。

sudo puma-dev -setup

セットアップ

これでアプリ名.testのURLでサーバーにアクセスできます。

puma-dev -install -d test

シンボリックリンクを貼る

puma-devを使用したいプロジェクト配下でシンボリックリンクを貼る

cd sandbox
puma-dev link -n sandbox

~/.puma-devを確認したら、きちんとシンボリックリンクが設定されているのが確認できます。

早速、http://sandbox.test/ にアクセス。

ホストの設定をしてくれと。

ホストを設定

言われた通りにdevelopment.rbに設定

config.hosts << "sandbox.test"

躓いた

development.rbを触ったから、いつも通りrails sで再起動しようかと思ったら、、あれ?

rails s

再起動

Readmeをきちんと確認したらありました。再起動。

puma-dev で特定のアプリを再起動したい場合は、そのアプリのディレクトリでtouch tmp/restart.txt実行できます。(翻訳後)

touch tmp/restart.txt

http://sandbox.test/ にアクセス。初期画面表示されました。

最後に

他にpuma-dev と asset に関して躓いたことがあるため別でまとめたいと思います。

【Rails】deviseのメール認証を使えるようにする(Confirmable)・ローカル編

初めに

業務でdeviseのメール認証(Confirmable)を使っていますが、自分でメール認証の設定をしたことがないためアウトプットします。

今回はローカルで動かすところまでやってみます。

ローカルのメール確認にはletter_openerを使用。

手順

deviseのインストール

gem 'devise'

group :development do
  # letter_openerも入れておきます
  gem 'letter_opener'
end
bundle install

# deviseのインストール
rails g devise install

devise日本語化

config/locales/devise.ja.ymlに翻訳ファイルを配置しておきます。

curl -s https://raw.githubusercontent.com/tigrish/devise-i18n/master/rails/locales/ja.yml -o config/locales/devise.ja.yml

メール認証の設定

Userモデル作成

rails g devise user

confirmabaleカラムの有効化

Userモデルを作成時に自動生成されたマイグレーションファイルのConfirmableの箇所のコメントアウトを外す。

# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      # (省略)

      ## Confirmable
      t.string   :confirmation_token
      t.datetime :confirmed_at
      t.datetime :confirmation_sent_at
      t.string   :unconfirmed_email # Only if using reconfirmable

      # (省略)

  end
end
# マイグレートします
rails db:migrate

モデルの設定変更

:confirmableを追記

# confirmableを追記
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :confirmable
end

# メールの送信元を設定
config.mailer_sender = 'makky@example.com'

Mailerの設定

config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
config.action_mailer.delivery_method = :letter_opener

flashの設定

deviseから提供されるフラッシュメッセージを表示するようにしておきましょう。

cssにbootstrap使っています。(ここではbootstrapの導入は説明しません。)

- flash.each do |type, message|
  = content_tag(:div, message, class: "alert alert-#{type == 'notice' ? 'success' : 'danger'}")

動作確認

ユーザー一覧には最初何もなし


http://localhost:3000/users/sign_up にてサインアップ


サインアップ後、メールが送信される


画面にはフラッシュメッセージが表示


本人確認が完了していない状態(メールのリンクを踏んでいない状態)でログインしてみる


本人確認が必要とフラッシュメッセージが表示


メールのリンクから遷移すると、メールアドレスの確認が完了する


ログインが問題なくできるようになる


終わりに

今回はローカルでdeviseのメール認証を使えるようにしてみました。

次は本番環境でどう使うのかアウトプットしてみようと思います。

また、メールアドレスのみでサインアップをし、メールのリンクを踏んだらパスワード入力→そのままログインもやってみたいと思います。

may have been in progress in another thread when fork() was called・rails consoleが中断してしまう

単純にrails consoleからUser.find(...)的なことをしようとしたら以下のエラーが発生してコンソールが中断してしまうことがありました。

rails c
> User.find(...)
[__NSCFConstantString initialize] may have been in progress in another thread when fork() was called

こちら(https://github.com/rails/rails/issues/38560)のissueを確認して、解決しました。

解決法

MacOSで発生するエラーみたいですね。 環境変数の設定とspring stopすると一旦は解決しました。

export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
spring stop

OBJC_DISABLE_INITIALIZE_FORK_SAFETYmacOSで使用される環境変数みたいです。 詳しいことが分かっていないので、調べた後に加筆したいと思います。

【Rails】deviseのモジュールをサラッと確認

deviseモジュール全部みたときに、全部説明しろって言われてたらできないなぁとふと思ったので、とりあえずサラッと確認してみました。

まず導入

rails newは済んでいます。

gem 'devise'
$ bundle
$ rails generate devise:install
$ rails generate devise User
$ rails db:migrate

するとUserモデルはこんな感じになっています。

class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
end

それぞれ何ができるのか

database_authenticatable

サインイン時にパスワードをハッシュし、ユーザーの信頼性を検証するためのモジュール。

registerable

新しいリソースの登録(ユーザーのサインアップなど)に関連するモジュール

recoverable

パスワードのリセットを処理するためのモジュール

rememberable

保存されたCookieからユーザーを記憶するためのトークンの生成とクリアを管理するためのモジュール

validatable

ユーザーのメールアドレスとパスワードに必要なすべての検証を作成するためのモジュール パスワードの有無、確認、長さもテストする。

confirmable

メールに記載されているURLから本登録を完了する機能を提供するモジュール。 サインイン中にアカウントが認証済みかどうかも検証できる。

lockable

一定回数の試行後にユーザーアクセスをブロックする処理を行うためのモジュール。ロックを解除するために、解除のためのメールと時間経過の2つの方法を設定できる。

timeoutable

ユーザーセッションがすでに期限切れになっているかどうかを確認するモジュール。 設定された時間後にセッションの有効期限が切れると、ユーザーは再度クレデンシャルを要求され、サインインページにリダイレクトされる。

trackable

ユーザーのサインインに関する情報を追跡するモジュール。 以下のカラムを追跡する。

  • sign_in_count: サインインの回数
  • current_sign_in_at: サインインののタイムスタンプ
  • last_sign_in_at: 前回のサインインのタイムスタンプ
  • current_sign_in_ip: ユーザーがサインインした際に更新されるリモート ip。
  • last_sign_in_ip: 前回サインインした際のリモート ip を保持する

omniauthable

モデルにOmniAuthサポートを追加するモジュール。

所感

lockable, timeoutable, trackable は何ができるのかちゃんと読んでなかったなぁと反省。 次はdevise導入後に基本的にできることをまとめてみたいと思います。