検索機能
備忘録というかコードを見ながら思ったことで、非常にぼやぼやしています🤤
検索=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.distinct
、Article.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を使った方がいいのかなぁという印象 らしい
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