前提・実現したいこと
ユーザー検索機能を実装しています。ユーザー名や性別などに加え、「ユーザーが話せる言語」でも絞り込みをかけています。
ユーザーは自分が話せる言語をプロフィール編集画面(users/edit.html.erb
)で選択出来ます(複数可)。こちらの機能は既にユーザー・言語間を多対多の関係で実装済みです。
以下がつまずいている点なのですが、例えばユーザーがそれぞれ以下の言語を話せるとします。
- ユーザーA: 日本語
- ユーザーB: 日本語、英語
- ユーザーC: 日本語、英語、中国語
「日本語」のみを選択し検索した場合、日本語を話せるA, B, C全員をヒットさせたかったのですが、現状ユーザーAだけがヒットします。「日本語のみを話せるのはユーザーAのみです」と認識されてしまっているようです。
同様に「英語、中国語」を選択すると、該当のユーザーがいない場合の処理が動いてしまいます。どちらも話せるユーザーCをヒットさせたかったのですが、「英語、中国語のみを話せるユーザーはいません」と認識されているようです。
該当のソースコード
users/index.html.erb
<%= form_with url: users_path, method: :get, local: true do |f| %> <p>User name</p> <%= f.search_field :name, :value => params[:name] %> <p>Keyword</p> <%= f.search_field :introduction, :value => params[:introduction] %> <p>Gender</p> <%= f.radio_button :gender, "Male", :checked => params[:gender] == ("Male") %>Male <%= f.radio_button :gender, "Female", :checked => params[:gender] == ("Female") %>Female <p>Languages</p> <%= f.check_box :language_ids, {multiple: true, :checked => params[:language_ids]&.include?("1")}, "1", false %>Japanese <%= f.check_box :language_ids, {multiple: true, :checked => params[:language_ids]&.include?("2")}, "2", false %>English <%= f.check_box :language_ids, {multiple: true, :checked => params[:language_ids]&.include?("3")}, "3", false %>Chinese <%= f.submit :search, :class => "btn-square-little-rich" %> <% end %>
users_controller.rb
def index if params[:name].present? || params[:introduction].present? || params[:gender].present? || params[:language_ids].present? @users = User.where(['name LIKE ? AND introduction LIKE ? AND gender LIKE ? AND language LIKE ?', "%#{params[:name]}%", "%#{params[:introduction]}%", "#{params[:gender]}%", "%#{params[:language_ids]}%"]) .page(params[:page]).per(20) else # ユーザーが何も入力しなかった場合。 @users = User.all.order(created_at: :desc).page(params[:page]).per(20) end if @users == [] # 該当のユーザーが存在しなかった場合。 flash[:notice] = "User was not found" @users = User.all.order(created_at: :desc).page(params[:page]).per(20) end end
試したこと
"%#{params[:language_ids]}%"
のように% %
で挟む事で部分一致にしたつもりだったのですが、なぜか上手くいきません。
英語(id: 2
)、中国語(id: 3
)で検索した場合、ログは以下の様に出力されます。
Parameters: {"utf8"=>"✓", "name"=>"", "introduction"=>"", "language_ids"=>["2", "3"], "commit"=>"search"}
各ユーザーの言語情報は以下の様に保存されておりましたので、なぜこれでユーザーCがヒットにならないのかが分かりません…。
[27] pry(main)> User.find(1).language_ids User Load (4.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 (4.9ms) SELECT `languages`.`id` FROM `languages` INNER JOIN `user_languages` ON `languages`.`id` = `user_languages`.`language_id` WHERE `user_languages`.`user_id` = 1 => [1] [28] pry(main)> User.find(2).language_ids User Load (0.7ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1 (0.8ms) SELECT `languages`.`id` FROM `languages` INNER JOIN `user_languages` ON `languages`.`id` = `user_languages`.`language_id` WHERE `user_languages`.`user_id` = 2 => [1, 2] [29] pry(main)> User.find(3).language_ids User Load (2.4ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 3 LIMIT 1 (2.3ms) SELECT `languages`.`id` FROM `languages` INNER JOIN `user_languages` ON `languages`.`id` = `user_languages`.`language_id` WHERE `user_languages`.`user_id` = 3 => [1, 2, 3]
どなたかご助言を頂けますと有難いです。
補足情報(FW/ツールのバージョンなど)
ruby 2.6.4p104
RubyGems 3.0.3
Rails 5.2.3
【追記】
モデル関連
user.rb
class User < ApplicationRecord has_many :user_languages, dependent: :destroy has_many :languages, through: :user_languages end
language.rb
class Language < ApplicationRecord has_many :user_language has_many :users, through: :user_language end
user_language.rb
class UserLanguage < ApplicationRecord belongs_to :user belongs_to :language end
テーブル情報
schema.rb
ruby
1ActiveRecord::Schema.define(version: 2021_03_11_063016) do 2 create_table "languages", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| 3 t.string "description" 4 t.boolean "done" 5 t.bigint "user_id" 6 t.datetime "created_at", null: false 7 t.datetime "updated_at", null: false 8 t.index ["user_id"], name: "index_languages_on_user_id" 9 end 10 11 create_table "user_languages", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| 12 t.bigint "user_id" 13 t.bigint "language_id" 14 t.datetime "created_at", null: false 15 t.datetime "updated_at", null: false 16 t.index ["language_id", "user_id"], name: "index_user_languages_on_language_id_and_user_id", unique: true 17 t.index ["language_id"], name: "index_user_languages_on_language_id" 18 t.index ["user_id"], name: "index_user_languages_on_user_id" 19 end 20 21 create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| 22 t.string "name" 23 t.datetime "created_at", null: false 24 t.datetime "updated_at", null: false 25 t.string "gender" # "sex" から "gender" に変更しました。 26 t.string "language" 27 t.text "introduction" 28 end 29 30 add_foreign_key "languages", "users" 31 add_foreign_key "user_languages", "languages" 32 add_foreign_key "user_languages", "users" 33end
ログ関連
以下の言語を話せるユーザーに関する language
情報を確認したログを追記致しました。
- ユーザーA(
id: 1
): 日本語 - ユーザーB(
id: 2
): 日本語、英語 - ユーザーC(
id: 3
): 日本語、英語、中国語
[36] pry(main)> User.find(1).language User Load (33.1ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 => "[\"1\"]" [37] pry(main)> User.find(2).language User Load (1.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1 => "[\"1\", \"2\"]" [38] pry(main)> User.find(3).language User Load (20.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 3 LIMIT 1 => "[\"1\", \"2\", \"3\"]"
/
(コマンド上では ¥
) が表示されていました。ここに問題があったのでしょうか…。
winterboumさまにご提示頂いたコードで検索した際のログ
以下のユーザーしか存在しない中で「英語」のみで検索した所、誰もヒットしませんでした。
- ユーザーA(
id: 1
): 日本語 - ユーザーB(
id: 2
): 日本語、英語 - ユーザーC(
id: 3
): 日本語、英語、中国語
Parameters: {"utf8"=>"✓", "name"=>"", "language_ids"=>["2], "introduction"=>"", "commit"=>"search"} User Load (0.6ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1 ↳ app/controllers/application_controller.rb:9 User Load (0.8ms) SELECT `users`.* FROM `users` WHERE (name LIKE '%%' AND introduction LIKE '%%' AND gender LIKE '%') AND `users`.`language` = '2' LIMIT 20 OFFSET 0 ↳ app/controllers/users_controller.rb:34 Rendering users/index.html.erb within layouts/application User Load (2.4ms) SELECT `users`.* FROM `users` ORDER BY `users`.`created_at` DESC LIMIT 20 OFFSET 0 ↳ app/views/users/index.html.erb:40 Rendered users/index.html.erb within layouts/application (38.4ms) CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1 [["id", 2], ["LIMIT", 1]] ↳ app/views/layouts/application.html.erb:48 (0.7ms) SELECT COUNT(*) FROM `notifications` WHERE `notifications`.`visited_id` = 2 AND `notifications`.`checked` = FALSE AND `notifications`.`action` = 'message' ↳ app/helpers/notifications_helper.rb:54 (0.8ms) SELECT COUNT(*) FROM `notifications` WHERE `notifications`.`visited_id` = 2 AND `notifications`.`action` != 'message' AND `notifications`.`checked` = FALSE ↳ app/helpers/notifications_helper.rb:48 Completed 200 OK in 342ms (Views: 304.0ms | ActiveRecord: 5.3ms)
追記2
【検索の仕様に関して】
仕様と致しましては、検索された言語(複数可)を「全てを含んだ」ユーザーをヒットさせたいです。
- ユーザーA: 日本語
- ユーザーB: 日本語、英語
- ユーザーC: 日本語、英語、中国語
例えば上記のユーザーがいた場合、
①「日本語」で検索 → A, B, C全員をヒットさせたい。
②「日本語、英語」で検索 → B, Cをヒットさせたい。
③「英語、中国語」で検索 → Cのみをヒットさせたい。
※完全一致ではない。
「日本語」でユーザー検索 → 完全一致ですと「日本語のみ」を話せるユーザーAだけがヒットすると思いますが、実装したいのはこちらの機能ではありません。
※部分一致でもない。
「日本語、英語」でユーザー検索 → 部分一致ですと「日本語または英語のどちらか」を話せるユーザーA, B, Cの全てがヒットすると思いますが、実装したいのはこちらの機能でもありません。
名前や性別を含んだ検索でも同様に、検索された文言を「全て含んだ」ユーザーをヒットさせたいです。
- 名前:(指定なし)
- 性別:Male
- 言語:日本語、中国語
- キーワード:hoge
例えば上記の条件で検索した場合:
男性で、かつ日本語・中国語をともに話せ(それ以上話せても可)、かつ自己紹介欄に「hoge」の文言を含むユーザーをヒットさせたいです(名前は問わない。[]と言う名前と言う訳ではない)。
最終的に解決したコード
users_controller.rb
ruby
1@users = User.all.order(created_at: :desc).page(params[:page]).per(20) 2 3if params[:language_ids].present? 4 @users = @users.select("users.*, COUNT(*) AS languages_count") 5 .joins(:user_languages) 6 .where(user_languages: { language_id: params[:language_ids] }) 7 .group("user_languages.user_id") 8 .having("languages_count = #{params[:language_ids]&.length}") 9end 10 11if params[:name].present? || params[:gender].present? || params[:introduction].present? 12 @users = @users.where(['name LIKE ? AND gender LIKE ? AND introduction LIKE ?', 13 "%#{params[:name]}%", "#{params[:gender]}%", "%#{params[:introduction]}%"]) 14end 15 16if @users == [] 17 # 該当のユーザーが存在しなかった場合。 18 flash[:notice] = "User was not found" 19 @users 20end
回答2件
あなたの回答
tips
プレビュー