SQL文を読み解こう②(Pundit, Simple_form,FormObject,scope)
SQL文を読み解こう①の続きです。
- simple_formって何
- いやpluckって何
- controllerの挙動どうなってるん?
- Punditて何 ⇦ここから!
- FormObjectて何
- ほんでscopeて何なんw
4. Punditて何
Punditとは
管理権限に基づいて、ユーザーにできることを制限するためのgem
Policyと言うクラスが自動で生成され、例えばCRUD処理の末尾に?をつけることで、ユーザーのidによってviewに編集機能や削除機能を出し入れすることができます。
今回はArticlePolicyと言うクラスが作られています。ArticlePolicyはApplicationPolicyのメソッドを継承しているため、def edit?にtrueを記述するだけで、編集権限を付与することができます。
# Policy (punditを使って権限を管理するクラス)
class ArticlePolicy < ApplicationPolicy
def edit?
true
end
end
class ApplicationPolicy
//セッター
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
false
end
def show?
false
end
def create?
false
end
def new?
create?
end
def update?
false
end
def edit?
update?
end
def destroy?
false
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
scope
end
end
end
例えば、userが管理者(admin)であれば編集画面を表示させ、そうでない場合は、表示させたくないと言う処理を記述したいときは以下の通りにするだけで大丈夫です。
tbody
- @articles.each do |article|
tr
td = article.id
td
div = article.title
div
- if policy(article).edit?
=> link_to edit_admin_article_path(article.uuid), class: %w[btn btn-default btn-xs btn-flat]
i.fa.fa-edit
'
| 編集
policy(article).edit?は、ArticlePolicyのedit?メソッドに該当する? a.k.a あなた編集権限ある? a.k.a あんたのuuidにadminという、enumで設定した管理者権限が付与されてはります?
と言う意味になります。
なんで= f.input :tag_idsとすると、article_tagsとtag_idがjoinされるの?
実は前もって定義されているんですね。
まず、formにバリデーションを効かせるために、FormObjectという概念が使われているんです。
5. FormObjectて何
FormObjectとは
Ruby on Railsのデザインパターン「設計手法」の1つです。
元々modelにバリデーション等様々な処理が書かれているものを、form層に切り出すことで多くのmodelで同一の処理を使えるなど、拡張性の高いコードを実現させることができるもの。
今回は下記のように、FormObjectが使われている。(tag_idの部分だけを抜粋)
class SearchArticlesForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :tag_id, :integer
def search
relation = relation.by_tag(tag_id) if tag_id.present?
relation
end
end
今回のように複数の値に対してバリデーションを効かせたい場合、あちこちにバリデーションを効かせるロジックを書いてしまうと、どのコードが実際に動いているのかわからなくなってしまいますよね。
僕は実際に開発現場に入ったことはないですが、ロジックを一つの場所に集約させておくことで、後任の人が「なんかこのコード臭うぞ?」と思うことを幾らか防げるみたいなんです。
技術負債を防ぐってやつですね。
フォームに複数の値を保存させたいときはActiveModelを継承させる下記コードを追加します。
class SearchArticlesForm
include ActiveModel::Model
include ActiveModel::Attributes
これで、ActiveRecord特有のメソッドである、validateや〇〇.all,〇〇.find_by等が使えるようになりました。
6. ほんでscopeて何なんw
by_tag(tag_id)
ここで謎のコードby_tagが追加されています。引数にtag_idをとっていますね。これが先に述べた、= f.input :tag_idsとすると、article_tagsとtag_idがjoinされる理由になります。
これ、article.rbでscopeを使って定義されているんですね。
scopeとは?
スコープでは、where、joins、includesなど、これまでに登場したすべてのメソッドを使用できます。どのスコープメソッドも、常にActiveRecord::Relationオブジェクトを返します。
(https://railsguides.jp/active_record_querying.html)
事前にarticle.rbで下記のように定義しておくことで、他のモデルやコントローラで呼び出した時にコードをスッキリさせることができます。
article.rb
**scope :by_tag, ->(tag_id) { joins(:article_tags).where(article_tags: { tag_id: tag_id}) }**
さて、上記のようなプロセスを踏むことで、冒頭に記載したSQL文が発行されるに至るんですね。
生成されるSQL文
SELECT "taxonomies"."id"
FROM "taxonomies"
INNER JOIN "article_tags"
ON "taxonomies"."id" = "article_tags"."tag_id"
WHERE "taxonomies"."type" IN ('Tag')
AND "article_tags"."article_id" = ? [["article_id", 21]]
たった数行のSQL文を理解するために下記のようなことを学ぶことができました。
・INNER JOIN句
・IN句
・Pluckメソッド
・Pundit gem
・Simple_form gem
・FormObject
・scope
今後も学習を積み重ねていきたいです。
Active Record クエリインターフェイス - Railsガイド
SQLでIN句を使おう!基本からサブクエリ活用方法まで一覧紹介 | 侍エンジニアブログ