-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
メモの検索をできるようにする #72
メモの検索をできるようにする #72
Conversation
backend/app/models/memo.rb
Outdated
private | ||
|
||
def resolve | ||
FILTERS.reduce(filter_collection) do |memo_scope, filter| | ||
const_get(filter).resolve(scope: memo_scope, params: filter_params) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
このprivateのresolveメソッドはどこで使用されているのでしょうか?
見た感じ、使用されていないように見えまして!間違っていたらすみません!
もし、不要であれば削除お願いします!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
削除いたしました。
backend/app/models/memo.rb
Outdated
def self.resolve(filter_collection:, filter_params:) | ||
FILTERS.reduce(filter_collection) do |memo_scope, filter| | ||
const_get(filter).resolve(scope: memo_scope, params: filter_params) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
引数の名前ですが、個人的には
resolveメソッドの引数の名前ですが個人的にはfilter_collectionよりはmemosの方がわかりやすいかなと思いました
def self.resolve(memos:, filter_params:)
FILTERS.reduce(memos) do |memo_scope, filter|
const_get(filter).resolve(scope: memo_scope, params: filter_params)
end
end
メモの集合を受け取ってreduceメソッドのブロック引数でmemo_scopeという流れの方が、引数のメモでフィルタリングするという意図が伝わりやすいかなと!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
確認ありがとうございます。修正いたしました。
ブランチ名は
ではなくドキュメントの開発フローに沿って命名していただければと思います!(次回からで大丈夫です)
とかになるかと思います(あくまで一例ですが) searchだけだと何の「検索」なのかが一目でわからないため、まずはそこを理解するためにソース読み解く必要があるためです! |
backend/spec/requests/memos_spec.rb
Outdated
describe 'SearchResolver' do | ||
let!(:test_memo_first) { create(:memo, title: 'テスト タイトル1', content: 'テスト コンテンツ1') } | ||
let!(:another_title_memo) { create(:memo, title: 'その他 タイトル', content: 'その他 コンテンツ') } | ||
let!(:test_memo_third) { create(:memo, title: 'テスト タイトル2', content: 'テスト コンテンツ2') } | ||
|
||
context 'タイトルで検索した場合' do | ||
it 'タイトルフィルターが正しく機能することを確認する' do | ||
filter_params = { title: 'テスト' } | ||
result = Memo::SearchResolver.resolve(filter_collection: Memo.all, filter_params: filter_params) | ||
expect(result).to contain_exactly(test_memo_first, test_memo_third) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
こちらに記載された検索機能のテストですが、これだとSearchResolverモジュールのresolveメソッドのユニットテスト(単体テスト)になるように思いました!
メソッド単体が、正常に動作しているかをテストするのであればとても良いのですが、今回はrequest_specで、所定のAPIエンドポイントにリクエストを送り、期待するレスポンスが返ってくるのか?という観点のテストになるのでそのような形式でテストを行うことが望ましいと思います!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
具体的には以下のようになるかと思います(例です)
describe 'GET /memos?title={title}' do
context 'タイトルで検索した場合' do
let!(:searchable_memo_1) { create(:memo, title: 'テスト タイトル1', content: 'テスト コンテンツ1') }
let!(:searchable_memo_2) { create(:memo, title: 'テスト タイトル2', content: 'テスト コンテンツ2') }
let!(:non_searchable_memo) { create(:memo, title: 'その他 タイトル', content: 'その他 コンテンツ') }
it 'タイトルフィルターが正しく機能し、期待されるメモが取得できることを確認する' do
aggregate_failures do
get '/memos', params: { title: 'テスト' }
expect(response).to have_http_status(:ok)
expect(response.parsed_body['memos'].length).to eq(2)
result_memo_ids = response.parsed_body['memos'].map { |item| item['id'] }
expected_memo_ids = [searchable_memo_1.id, searchable_memo_2.id]
expect(result_memo_ids).to eq(expected_memo_ids)
end
end
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
確認ありがとうございます。また例文も大変助かります。
こちらを参考に修正させていただきました。
backend/spec/requests/memos_spec.rb
Outdated
@@ -154,4 +154,50 @@ | |||
end | |||
end | |||
end | |||
|
|||
describe 'SearchResolver' do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
describeですが他のテストと合わせて、エンドポイントのパスを記載するようにしていただきたいです!
describe 'GET /memos?title={title}'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
このファイルはrequests specなので、
describe 'GET /memos' do
のブロック内に書きましょうか。
また別のコメントでりくやさんがおっしゃっている通り、SearchResolverのテストはmodels specに書くようにしましょうか〜
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
内容修正させていただきました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ochi-sho-private-study
おつかれさまです。
こちらご指摘いただいた内容について、modelsフォルダ内のmemo_spec.rbに記述するとうことでしょうか?
それともrequestsフォルダ内にmodels_spec.rbを作成し、そちらに記述するということでしょうか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shoutarou123
modelsディレクトリは単体テストを記載するディレクトリ
requestsディレクトリは結合テストを記載するディレクトリというイメージですね!
なのでSearchResolverの単体の動きを確認するテストは
modelsフォルダ内のmemo_spec.rbに記述する
そして僕が以下の例で記載した処理に関しては、新たにdescribe 'GET /memos?title={title}' というブロックを作るのではなくすでに存在する「describe 'GET /memos' do」のブロック内に記載するという意味かと思います!
describe 'GET /memos?title={title}' do
context 'タイトルで検索した場合' do
let!(:searchable_memo_1) { create(:memo, title: 'テスト タイトル1', content: 'テスト コンテンツ1') }
let!(:searchable_memo_2) { create(:memo, title: 'テスト タイトル2', content: 'テスト コンテンツ2') }
let!(:non_searchable_memo) { create(:memo, title: 'その他 タイトル', content: 'その他 コンテンツ') }
..etc
backend/spec/requests/memos_spec.rb
Outdated
let!(:test_memo_first) { create(:memo, title: 'テスト タイトル1', content: 'テスト コンテンツ1') } | ||
let!(:another_title_memo) { create(:memo, title: 'その他 タイトル', content: 'その他 コンテンツ') } | ||
let!(:test_memo_third) { create(:memo, title: 'テスト タイトル2', content: 'テスト コンテンツ2') } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
変数名ですが
test_というプレフィクスはなくても良いかなと思いました!(テストに使用するということは明確なため)
let!(:searchable_memo_1) { create(:memo, title: 'テスト タイトル1', content: 'テスト コンテンツ1') }
let!(:searchable_memo_2) { create(:memo, title: 'テスト タイトル2', content: 'テスト コンテンツ2') }
let!(:non_searchable_memo) { create(:memo, title: 'その他 タイトル', content: 'その他 コンテンツ') }
例えば上記のようにどれが検索対象のデータで、どれが対象外のデータなのか、わかるようにする方が、読んでいてわかりやすいかなと個人的に思いました!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
修正させていただきました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shoutarou123
コメントしました!
backend/app/models/memo.rb
Outdated
# @param filter_collection [ActiveRecord::Relation[Memo]] | ||
# @param filter_params [ActionController::Parameters] | ||
# @return [ActiveRecord::Relation[Memo]] | ||
def self.resolve(filter_collection:, filter_params:) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO/Simple
filter_paramsではなくて、シンプルにparamsでいいと思いました!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ochi-sho-private-study
ありがとうございます。内容修正させていただきました。
memos = Memo.all | ||
@memos = Memo::SearchResolver.resolve(filter_collection: memos, filter_params: params) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simple/Performant
ここで変数に格納してしまうと、
全てのレコード分のActiveRecordのインスタンスが格納されてしまうので、
メモリをかなり消費してしまいます。
(参考文献にも挙げてますが、ActiveRecordのインスタンスは意外と重いのです。)
また、今回だとmemos = Memo.all
という風に変数に格納してしまっているので、
ここでクエリが一度発行されてしまって、
Memo::SearchResolverでも一回発行してしまうので無駄なクエリが発行されてしまうと思います。
したがって、以下のように変数に格納しないように書いて、
メモリに載せないようにしましょう。
memos = Memo.all | |
@memos = Memo::SearchResolver.resolve(filter_collection: memos, filter_params: params) | |
@memos = Memo::SearchResolver.resolve(filter_collection: Memo.all, filter_params: params) |
参考文献
- ActiveRecordがかなりメモリを消費する件
- メモリ消費量を見る
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ochi-sho-private-study
ありがとうございます。
内容修正させていただきました。
backend/app/models/memo.rb
Outdated
# @param filter_params [ActionController::Parameters] | ||
# @return [ActiveRecord::Relation[Memo]] | ||
def self.resolve(filter_collection:, filter_params:) | ||
filter_params[:order] ||= 'desc' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Readable/Simple
ここでfilter_paramsの値を格納するのは、
resolveメソッドの副作用になってしまうので、わかりづらいと思います。
(ここでいう副作用とは、本来期待している動作以外の作用を指します。)
以下のように、OrderFilterに閉じた動作にするのはどうでしょうか?
こうすることで、早期リターンも必要がなくなりシンプルになると思います。
module OrderFilter
DEFAULT_ORDER = 'desc'
private_constant :DEFAULT_ORDER
def self.resolve(scope:, params:)
scope.order(id: params[:order].presence || DEFAULT_ORDER)
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ochi-sho-private-study
ありがとうございます。ご指摘のとおり修正させていただきました。
backend/app/models/memo.rb
Outdated
private | ||
|
||
def resolve | ||
filter_params[:order] ||= 'desc' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
こちらは二重に実行する必要がないと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ochi-sho-private-study
ありがとうございます。削除させていただきました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shoutarou123
コメントしました!
backend/spec/models/memo_spec.rb
Outdated
let!(:searchable_memo_one) { described_class.create(title: 'テスト タイトル1', content: 'テスト コンテンツ1') } | ||
let!(:searchable_memo_two) { described_class.create(title: 'テスト タイトル2', content: 'テスト コンテンツ2') } | ||
let!(:non_searchable_memo) { described_class.create(title: 'その他 タイトル', content: 'その他 コンテンツ') } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idiomatic/Simple
factorybotを利用してテストデータを作成するようにしましょう!
以下のように記述できるかと思います。
let!(:memos) do
{
'1' => create(:memo, title: 'テスト タイトル1', content: 'テスト コンテンツ1'),
'2' => create(:memo, title: 'テスト タイトル2', content: 'テスト コンテンツ2'),
'3' => create(:memo, title: 'その他 タイトル', content: 'その他 コンテンツ')
}
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ochi-sho-private-study
修正しました。テストコードがシンプルになりました。勉強になります。
backend/spec/models/memo_spec.rb
Outdated
@@ -81,4 +81,55 @@ | |||
end | |||
end | |||
end | |||
|
|||
describe '検索機能のテスト' do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idiomatic
テストの対象を具体的にしましょうか!
describe '検索機能のテスト' do | |
describe 'SearchResolver::resolve(memos:, params:)' do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ochi-sho-private-study
修正いたしました。
backend/app/models/memo.rb
Outdated
@@ -13,4 +13,46 @@ | |||
class Memo < ApplicationRecord | |||
validates :title, :content, presence: true | |||
has_many :comments, dependent: :destroy | |||
|
|||
module SearchResolver |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
提案
ralisのデザインパターン的にはQueryパターンになるので、
Queryモジュールにしてもらってもいいですか?
(最初に私が命名したのがミスリードでした 🙇)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ochi-sho-private-study
修正させていただきました。
7191b2c
to
1789adb
Compare
9211bf3
to
66a8c22
Compare
66a8c22
to
b5ef67b
Compare
SearchResolverの変数名変更
タイトルフィルターテスト内容修正
Searchテスト内容修正
検索に関するテストコード全て削除
b5ef67b
to
b71d0e4
Compare
docker compose run backend rake ridgepole:apply実行
併せてmemo_spec.rbも修正
@memos = Memo::SearchResolver.resolve(memos: Memo.all, params: params) | ||
render json: { memos: @memos }, status: :ok |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ここはインスタンス変数で必要はないと思いました!
Viewなどに値を渡すことがないためです!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
確認ありがとうございます。修正させていただきました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
確認ありがとうございます。修正させていただきました。
backend/spec/models/memo_spec.rb
Outdated
aggregate_failures do | ||
result = Memo::Query.resolve(memos: described_class.all, params: { content: 'コンテンツ' }) | ||
expect(result).to include(memos['1'], memos['2'], memos['3']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
aggregate_failuresは複数の期待値を一度に、検証したい時に使用するのでここでは必要ないかなと思いました!
(1つの期待値しか検証していないため(expectが1回))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
修正させていただきました。
backend/spec/models/memo_spec.rb
Outdated
aggregate_failures do | ||
result = Memo::Query.resolve(memos: described_class.all, params: { order: 'asc' }) | ||
expect(result).to eq([memos['1'], memos['2'], memos['3']]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
こちらも同様です!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
修正させていただきました。
backend/spec/models/comment_spec.rb
Outdated
# Foreign Keys | ||
# | ||
# fk_comments_memo_id (memo_id => memos.id) | ||
# | ||
RSpec.describe Comment do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
今回の変更とは関係ない差分が反映されてしまっているため、除外していただきたいです🙇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
git revertでコミット済の変更を破棄することができます!
https://qiita.com/chihiro/items/2fa827d0eac98109e7ee
「アプリ再セットアップ」というコミットが対象のコミットなのでこのコミットIDを指定していただければと思います!
コミットIDはコミットを一意に識別するIDで
git log
コマンドか
このGitHubの各コミットのページの右上あたりにも表示されています!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
修正させていただきました。
backend/spec/factories/comments.rb
Outdated
# Table name: comments | ||
# | ||
# id :bigint not null, primary key | ||
# content(内容) :text(65535) not null | ||
# content(内容) :string(1024) not null | ||
# created_at :datetime not null | ||
# updated_at :datetime not null | ||
# memo_id(メモID) :bigint not null | ||
# memo_id(メモID) :integer not null | ||
# | ||
# Indexes | ||
# | ||
# index_comments_on_memo_id (memo_id) | ||
# | ||
# Foreign Keys | ||
# | ||
# fk_comments_memo_id (memo_id => memos.id) | ||
# |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
こちらも今回の変更とは関係ない差分が反映されてしまっているため、除外していただきたいです🙇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
修正させていただきました。
backend/app/models/comment.rb
Outdated
# content(内容) :string(1024) not null | ||
# content(内容) :string(1024) not null | ||
# created_at :datetime not null | ||
# updated_at :datetime not null | ||
# memo_id(メモID) :bigint not null | ||
# memo_id(メモID) :integer not null | ||
# | ||
# Indexes | ||
# | ||
# index_comments_on_memo_id (memo_id) | ||
# | ||
# Foreign Keys | ||
# | ||
# fk_comments_memo_id (memo_id => memos.id) | ||
# |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
こちらも今回の変更とは関係ない差分が反映されてしまっているため、除外していただきたいです🙇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rikuya98
修正させていただきました。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shoutarou123
LGTM!
対応するissue
closes #20 検索機能の実装
対応内容
検索機能実装が完了しましたので、ご確認お願いします。