💭

AnyLoginMultipleというGemを作った。

2021/08/22に公開

初めてGemを作ってみたのでその過程で学んだことなどを整理してみようと思います。
https://github.com/kenzo-tanaka/any_login_multiple

作ったもの

左下のセレクトボックスから認証するユーザーを選択すると、認証状態が切り替わります。
このGemはAnyLoginという既存のGemに影響を受けて作っています。
Viewの動きは本家とほぼ同じなのですが、名前の通り複数のモデルに対応しています。

動機

今回参考にしているAnyLoginは認証のスイッチを手軽にするGemです。上記と同じようにユーザーをセレクトボックスで選択するだけで手軽にユーザーの認証状態の切り替えができます。

👇 本家のデモ動画
https://www.youtube.com/watch?v=978DlHvufSY

とても便利なGemで自分のプロジェクトでもよく使っているのですが、「複数のモデルに対応していない」というのがネックでした。例えばAdminモデルとStaffモデルなど2つ以上のモデルをAnyLoginで設定することはできません。

これに対しての機能要望などもIssueを見ていると上がっているようなのですが、作者としてはあまり対応する様子がなさそうです。またPRも投げてみましたが、思ったよりも変更が大きくなってしまい、取り込まれるは難しそうと思っていました。

https://github.com/igorkasyanchuk/any_login/pull/44

といった背景もあり、勉強がてらに作ってみました。

作り方

まずGemをつくるのが初めてだったので、作り方について調べました。

  • 普通にGemとして作るパターン(bundle gem hoge)
  • Railsプラグインとして作るパターン(rails plugin new hoge)
  • Railsエンジンとして作るパターン(rails plugin new hoge --mountable)

上記のようなパターンがあることが分かったのですが、今回の場合はRailsに組み込む前提のGemでViewも拡張するものなので、Railsエンジンとして作り始めました。

https://railsguides.jp/engines.html

Gemfileで相対パスで読み込んでおけば、自作中のGemの動作チェックができます。ディレクトリの外でrails newしてGemfileに下記のように書いて動作確認をしていました。Gemのコードを更新した場合は、サーバー再起動をすれば新しい変更が反映されます。

Gemfile
gem 'easy_login', path: '../easy_login'

処理の流れ

Rails本体アプリからklass_namesで設定したいモデル名を配列で受け取ります。本体のアプリ側では config/initializers/any_login_multiple.rb などを作成して設定ができます。

lib/any_login_multiple
mattr_accessor :klass_names
@@klass_names = ['User']

def self.klasses
  @@klasses = AnyLoginMultiple.klass_names.map(&:constantize)
end

配列の数だけ認証フォームを出力しています、asパラメータにモデル名を含むことでアクション側で、「どのモデルとして認証するか」がわかるようにしています。

_any_login_multiple.html.erb
<% AnyLoginMultiple.klass_names.each do |klass_name| %>
  <%= form_with url: any_login_multiple.sign_in_path(as: klass_name), class: 'any-login-multiple-form', method: :post do |form| %>
    <label>
      <%= klass_name %>
    </label>
    <%= form.select :id, klass_name.constantize.pluck(:email, :id) %>
    <%= form.submit 'Submit' %>
  <% end %>
<% end %>

認証するControllerのアクションです。Stringで受け取ったasパラメータをconstantizeでクラス名にして.. といった処理を書いています。

application_controller.rb
module AnyLoginMultiple
  class ApplicationController < ActionController::Base
    def any_login_multiple
      reset_session

      klass_string = params[:as]
      @user = klass_string.constantize.find(params[:id])

      sign_in klass_string.parameterize.underscore.to_sym, @user

      redirect_to main_app.root_path
    end
  end
end

失敗したこと

名前を使えるかどうか調べずに作ってしまった

最初このGemは EasyLogin という名前で作っていたのですが、いざ公開する段になって、すでに同名のGemがあることが分かりました。なので AnyLogin の拡張的なものとして認識しやすいよう、AnyLoginMultipleという名前にしました。

rails plugin neweasy_loginとしていたので、Class名などは全て EasyLogin になっていてこれを修正するのがとても面倒でした.. 最初からちゃんと名前が使えるかどうか調べてから作りべきでした。

良かったこと

最低限の機能に絞り込んだ

本家AnyLoginはDevise以外の認証系のGem AuthlogicClearance などにも対応しているのですが、そこまでやると実装コストがあがってしまうのと、自分のプロジェクトでは全て認証はDevise一択になっていて他のGemに対応する必要性もさほど感じなかったので、Deviseのみの対応としました。

ここを絞り込んだことによって1つのControllerだけで済み、すぐにリリースができました。

普通のRailsアプリを作っている中ではやらないような実装を経験できた

constantizeparameterizeメソッドなどはこのGemの開発を通して知ったメソッドでした。
また、クラス名を示す変数としてklassとするなどの慣習的なことも知れたのもよかったです。

最後に

良かったら使ってみてもらえると嬉しいです。

https://github.com/kenzo-tanaka/any_login_multiple

Discussion