Rails Diary

プログラミングの学習記録です。

検索機能

備忘録というかコードを見ながら思ったことで、非常にぼやぼやしています🤤
検索=ransackみたいな頭の回路になっていた。。

検索フォーム

@search_articles_form = SearchArticlesForm.new(search_params)

コントローラに↑という記載があったが、SearchArticlesFormというモデルがあるわけでもない。VScodeの検索でそういうクラスを検索してみると、app/forms配下に検索フォーム用のクラスが作られていた。

これが例のActiveRecordを継承しないでActiveRecordのような書き方を使いたい例か..!

正直ActiveModelの理解度は3割行くか行かないかのぼんやり具合なので、あんまりピンと来てはいない。ただ事前に記述されているコードからおそらくここにこれを記載するんだろうな、ということだけは推測できた。

↓ActiveModel::Attributesに関して(すごく分かりやすい) ushinji.hatenablog.com

distinctのみで使うパターンとは・・

【Rails】 distinctメソッドでユニークなデータを取得する方法 | Pikawaka

def search
  relation = Article.distinct

distinctは取得した値の重複を取り除いて返してくれるメソッド

  • 現状記事のタイトルに重複を防ぐバリデーションが記載されている
  • distinctは通常selectで指定したカラムに重複があった場合に、重複を取り除いた配列を返してくれる
  • selectを使用せずに上記のようにdistinctをかけた場合、全てのカラムで値が重複する場合のみ機能する
では、この場合にdistinctを使用する意味とは(Article.allではダメなのか)
  • 結論: Article.distinctArticle.allのいずれであっても結果は変わらない

  • ただsearchメソッドの部分だけで重複を許容していないことが分かるように、あえてArticle.distinctが使われているとのこと
    →また、仕様変更でバリデーションが変更になったとしても、distinctがあるので重複を排除して検索が行われることが担保されるため

scopeとは? modelに記載している判定ロジックと何が違うのか

Rails の scope をあまり使わない方がいい理由 - ハトネコエ Web がくしゅうちょう

relation = relation.by_category(category_id) if category_id.present?
  • このby_categoryがどこから来たのか
  • 最初、ファットコントローラ対策でモデルに記載するロジック的なヤツだと思い、articleモデルを見に行くとscopeという括りで定義されていた。モデルに記載されたメソッドとscopeの違いはなんなのか
結論
  • scopeに書こうがメソッドとして定義しようが基本的に同じような動きをしているので、書き手の好みだそう
  • ただ、scopeは一行で書いているイメージがあり、無理くり一行でまとめるとかなり読みづらい
  • 複数行に分けた方が見やすいのであればdef~endを使った方がいいのかなぁという印象 らしい

qiita.com

whereメソッド

whereメソッドとは、テーブル内の条件に一致したレコードを配列の形で取得することができるメソッドです。 【Rails】 whereメソッドを使って欲しいデータの取得をしよう! | Pikawaka

find_byとwhereの違い

find_by
  • findメソッドで検索できるのがidのみであるのに対し、find_byではid以外の要素でも検索ができる
  • 複数のデータが検索結果に該当する場合は、検索に引っかかった最初のデータを一つだけ取得する
  • 検索に該当するものがなかった場合はnilを返す
where
  • id以外でも検索可能
  • 該当するデータが複数あった場合はその全てを配列の形で取得することができる
  • 検索に該当するものがなかった場合は空の配列を返す
# 文字列指定
User.where("name = '太郎'")

# シンボル指定 ★SQLインジェクション対策としてこちらを使う
User.where(name: '太郎')
SQLインジェクションとは

SQLインジェクションとは、アプリケーションの脆弱性により本来の意図ではない不当な「SQL」文が作成されてしまい、「注入(injection)」されることによって、データベースのデータを不正に操作される攻撃 のことです。 SQLインジェクションとは?仕組み・被害事例・対策をわかりやすく解説 | クラウド型WAF 攻撃遮断くん

例えば下記のようなユーザーを取得するコードがある

id = params[id]
User.where("id = #{id} ")
# ユーザーidが1の場合、下記のSQLが発行される
#=> SELECT `users`. * FROM `users` WHERE (id = 1)
このコードにおける脆弱性

もしこのidに1 OR 1 = 1という値が入ってしまったら

id = '1 OR 1 + 1'
User.where("id = #{id}")
#=> SELECT `users`. * FROM `users` WHERE (id = 1 OR 1 = 1)
  • SQLではWHEREにtrueが渡ると全てのレコードを取得する
  • このコードでは1 = 1 がtrueと判断され、全てのレコードが取得できてしまう

必要な値を下記のようにハッシュ形式で記述しておけば、発行されるSQLは以下のように変わり、予期せぬ値が渡ることを防ぐことができる。

User.where(id: id)
#=> SELECT `users`. * FROM `users` WHERE `users`.`id` = 1

scope内の指定方法について

※ 途中で分からなくなったため、一時保留

scope :body_contain, ->(body) { joins(:sentences).merge(where('sentences.body LIKE ?', "%#{body}%")) }
Article Load (0.2ms)  
SELECT  DISTINCT "articles".* 
FROM "articles" 
INNER JOIN "article_blocks" 
ON "article_blocks"."article_id" = "articles"."id" 
AND "article_blocks"."blockable_type" = ? 
INNER JOIN "sentences" 
ON "sentences"."id" = "article_blocks"."blockable_id" 
WHERE (sentences.body LIKE '%カレーライス%') 
ORDER BY "articles"."id" DESC 
LIMIT ? 
OFFSET ? 
 [["blockable_type", "Sentence"], ["LIMIT", 25], ["OFFSET", 0]]
  • articlesテーブルとarticle_blocksテーブルを内部結合させて、blockable_typeをsentenceと判断
  • sentencesテーブルを内部結合させて最終的にはsentencesテーブルのbodyカラムで曖昧検索している
    joinsメソッド

    関連するテーブル同士を内部結合(両テーブルで結合条件がマッチするレコードのみ取得する結合)してくれるメソッド。関連するテーブルと内部結合したデータを取得する際に便利。

下記のようなアソシエーションがあるとする

# owner.rb
class Author < ActiveRecord::Base
  has_many :books
end

# book.rb
class Book < ActiveRecord::Base
  belongs_to :author
end
# モデル名.joins(:関連名)
Author.joins(:books)
SELECT `authors`. * FROM `authors` INNER JOIN `books` ON `books`.`author_id` = `authors`.`id`

参考にしたサイト

【Rails】findとfind_byとwhereの使い分け - Qiita

【Rails】 whereメソッドを使って欲しいデータの取得をしよう! | Pikawaka

SQL like 前方一致、あいまい検索、エスケープ | ITSakura