53
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

複数認証を例にしたポリモーフィックとSTIの設計紹介 #shibuyarb

Last updated at Posted at 2015-06-17

これは 渋谷.rb[:20150617] の発表資料です

スライドバージョン


複数認証を例にしたポリモーフィックとSTIの設計紹介 #shibuyarb

sue445
2015/06/17 Shibuya.rb


自己紹介

sue445.png


【今期の嫁】キュアトゥインクル

cure_twinkle.png


【本妻】キュアピース

cure_peace.png


Agenda

  • 前置き(やりたかったこと)
  • ポリモーフィックとSTIでのそれぞれの設計
  • ポリモーフィックでよかったこと (≒ STIのデメリット)
  • ポリモーフィックでつらかったこと (≒ STIデメリット)
  • まとめ
  • おまけ(reveal.jsのこと)

やりたかったこと

とある社内アプリの開発にて下記の要件があった

  • 1ユーザで下記のいずれかの認証を利用
    • 普通の従業員であればLDAP認証
    • LDAPに紐付かないユーザ(常駐でない外部委託業者など)のでLDAP以外の認証
      • 今回はメアドとパスワード認証を採用
  • 両方の認証を同時に利用するのは無し
  • その他は特に指定なかったので Ruby2.2 + Rails4.2 で開発
    • サーバサイドエンジニアはみんなRails分かるので、Railsで書いておけば誰でもメンテできるだろという安心感

最初はdevise + omniauthでやろうとした

class User < ActiveRecord::Base
  devise :omniauthable, :rememberable,
    omniauth_providers: [:ldap, :identity],
    authentication_keys: [:username]
end

一応これでやりたいことは実現できたが。。。


できないことはないがomniauthがアレすぎて断念

  • omniauthのデフォルトのフォームにはCSRFのtokenがないのでRailsでは使えない
  • omniauthはrequestのusernamepasswordしか見ていないので form_for が使えない
    • form_for だと user[username] とかで渡ってくるので、usernameとpasswordちゃんと入力しても missing_credentials になる 参考

【参考】古き良き時代を感じさせるomniauthのデフォルトフォーム

omniauth_form.png

今風にbootstrap化しようにもrubyの中にHTMLがハードコーディングされてるのでハックするのは至難の業 参考


認証ごとにUserのModelを分けることにした

この2つがあればなんとかなるだろという最初の構想

  • LdapUser : LDAP認証のユーザ
  • LimitUser : パスワード認証のユーザ
    • パスワード認証のユーザはいろいろ機能制限されている仕様のため

LdapUser

class LdapUser < ActiveRecord::Base
  devise :ldap_authenticatable, :rememberable, authentication_keys: [:ldap_uid]

end

LimitUser

class LimitUser < ActiveRecord::Base
  devise :database_authenticatable, :recoverable, :rememberable, :validatable

end

【実現方法】ポリモーフィック vs STI

さてどうしよう?

  • 選択肢
    • ポリモーフィック
    • STI (Single Table Inheritance)
  • 先にネタバレすると今回はポリモーフィックを採用しました
    • 後述のSTIのコードやメリデメは設計段階に考えたものなので実際とは違うかもしれないです

ポリモーフィック

複数のテーブルに分割する設計

ER_polymorphism.png

usersがどっちのテーブルに紐づくかは auth_user_typeauth_user_id を見てRailsがいい感じに判断してくれる


User

class User < ActiveRecord::Base
  belongs_to :auth_user, polymorphic: true, dependent: :destroy
end

LdapUser

class LdapUser < ActiveRecord::Base
  devise :ldap_authenticatable, :rememberable, authentication_keys: [:ldap_uid]

  has_one :user, as: :auth_user
  after_create :create_user!
end

LimitUser

class LimitUser < ActiveRecord::Base
  devise :database_authenticatable, :recoverable, :rememberable, :validatable

  has_one :user, as: :auth_user
  after_create :create_user!
end

STI

1つのテーブルに全部入りする設計

ER_STI.png


User

class User < ActiveRecord::Base
end

UserがどっちのModelに紐づくかは type を見てRailsがいい感じに判断してくれる

LdapUser

class LdapUser < User
  devise :ldap_authenticatable, :rememberable, authentication_keys: [:ldap_uid]
end

LimitUser

class LimitUser < User
  devise :database_authenticatable, :recoverable, :rememberable, :validatable
end

Twitterで相談した

tweet1.png


tweet2.png


tweet3.png


tweet4.png


tweet5.png


ポリモーフィックでよかったこと(≒ STIのデメリット)


STIだと使わないカラムが必ず出てくる

  • STIだとカラムにNOT NULL制約がつけられない
  • ポリモーフィックだと使わないカラムがないのでNOT NULL制約がつけられる
  • Rubyの世界できれいに解決できないならデータベースの世界できれいになる方を選んだ(ポリモーフィックを選んだ一番大きな理由)

ポリモーフィックでつらかったこと(≒ STIのメリット)


Model層はきれいに分離できたがController層にしわ寄せが。。。

application_controller.rb

class ApplicationController < ActionController::Base
  def current_auth_user
    current_ldap_user || current_limit_user
  end

  def user_signed_in?
    ldap_user_signed_in? || limit_user_signed_in?
  end

  def signed_out_path
    case
    when current_user.ldap_user?
      destroy_ldap_user_session_path
    when current_user.limit_user?
      destroy_limit_user_session_path
    end
  end
end

ポリモーフィックなModelのcontrollerをREST的に書くと冗長になる

routes.rb

resources :users, only: [:index, :show, :edit, :update] do
  # userから見るとlimit_userは1つだけなので単数形
  resource :limit_users, only: [:edit, :update], path: :limit_user do
  end
end

# 全体から見るとlimit_userは複数あるので複数形
# limit_userとuserはセットで作る(作成時にはuser_idは存在しない)のでusersの外に出す
resources :limit_users, only: [:new, :create] do
end

上のroutesだとuserとlimit_userでフォームが分かれている


まとめ

  • 今回の場合はポリモーフィックでもSTIでもメリデメはそれぞれあったように思えるのでTPOで採用するのがいいと思う
  • Model数やレコード数と要相談
    • 今回はModel 3つでユーザ数も数100くらいなのでどっちでもよかった
  • 後からテーブル構成を変えるのは大変なので納得いくまでテーブル設計はしっかりしよう
    • 「『納得』は全てに優先するぜッ!」 by ジャイロ・ツェペリ (SBR 8巻より)

【おまけ】このスライド作成のこと

  • いつもはgoogle driveだけど reveal.js というのを使ってみた
  • index.htmlにmarkdownを書いておくだけでjsがいい感じにスライドにできる

難点&工夫点

index.htmlの中にmarkdownを書くのはエディタのシンタックスハイライトが効かないのがつらい

  • template.html.erb + README.md -> index.html 変換をするRakefileを作った
  • guard-rake からrakeタスク実行
  • README.mdを編集すれば自動でindex.htmlが作られるのでだいぶ快適になった
  • 続きはgithubで!

画像をアップロードしなおした以外はREADME.mdとQiitaに投稿した内容は全く同じです

53
52
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
52

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?