AnyLoginMultipleというGemを作った。
初めてGemを作ってみたのでその過程で学んだことなどを整理してみようと思います。
作ったもの
左下のセレクトボックスから認証するユーザーを選択すると、認証状態が切り替わります。
このGemはAnyLoginという既存のGemに影響を受けて作っています。
Viewの動きは本家とほぼ同じなのですが、名前の通り複数のモデルに対応しています。
動機
今回参考にしているAnyLoginは認証のスイッチを手軽にするGemです。上記と同じようにユーザーをセレクトボックスで選択するだけで手軽にユーザーの認証状態の切り替えができます。
👇 本家のデモ動画
とても便利なGemで自分のプロジェクトでもよく使っているのですが、「複数のモデルに対応していない」というのがネックでした。例えばAdmin
モデルとStaff
モデルなど2つ以上のモデルをAnyLoginで設定することはできません。
これに対しての機能要望などもIssueを見ていると上がっているようなのですが、作者としてはあまり対応する様子がなさそうです。またPRも投げてみましたが、思ったよりも変更が大きくなってしまい、取り込まれるは難しそうと思っていました。
といった背景もあり、勉強がてらに作ってみました。
作り方
まずGemをつくるのが初めてだったので、作り方について調べました。
- 普通にGemとして作るパターン(
bundle gem hoge
) - Railsプラグインとして作るパターン(
rails plugin new hoge
) - Railsエンジンとして作るパターン(
rails plugin new hoge --mountable
)
上記のようなパターンがあることが分かったのですが、今回の場合はRailsに組み込む前提のGemでViewも拡張するものなので、Railsエンジンとして作り始めました。
Gemfileで相対パスで読み込んでおけば、自作中のGemの動作チェックができます。ディレクトリの外でrails new
してGemfileに下記のように書いて動作確認をしていました。Gemのコードを更新した場合は、サーバー再起動をすれば新しい変更が反映されます。
gem 'easy_login', path: '../easy_login'
処理の流れ
Rails本体アプリからklass_names
で設定したいモデル名を配列で受け取ります。本体のアプリ側では config/initializers/any_login_multiple.rb
などを作成して設定ができます。
mattr_accessor :klass_names
@@klass_names = ['User']
def self.klasses
@@klasses = AnyLoginMultiple.klass_names.map(&:constantize)
end
配列の数だけ認証フォームを出力しています、as
パラメータにモデル名を含むことでアクション側で、「どのモデルとして認証するか」がわかるようにしています。
<% 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
でクラス名にして.. といった処理を書いています。
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 new
でeasy_login
としていたので、Class名などは全て EasyLogin
になっていてこれを修正するのがとても面倒でした.. 最初からちゃんと名前が使えるかどうか調べてから作りべきでした。
良かったこと
最低限の機能に絞り込んだ
本家AnyLoginはDevise以外の認証系のGem Authlogic や Clearance などにも対応しているのですが、そこまでやると実装コストがあがってしまうのと、自分のプロジェクトでは全て認証はDevise一択になっていて他のGemに対応する必要性もさほど感じなかったので、Deviseのみの対応としました。
ここを絞り込んだことによって1つのControllerだけで済み、すぐにリリースができました。
普通のRailsアプリを作っている中ではやらないような実装を経験できた
constantize
やparameterize
メソッドなどはこのGemの開発を通して知ったメソッドでした。
また、クラス名を示す変数としてklass
とするなどの慣習的なことも知れたのもよかったです。
最後に
良かったら使ってみてもらえると嬉しいです。
Discussion