前提・実現したいこと
Ruby On Rails 5で社内のみで使う業務管理システムを作成中。
プロジェクトごとにタグが複数ついていて、タグと案件番号と顧客会社名によるプロジェクト検索機能を作りたい。
######テーブルは下記(質問に関係ない情報は省略)
1←多(矢印の向きがbelongs_to)
projects → clients
projects ← project_tags → tags(Active Hash)
[projects]
id(PK) integer
code string (「案件番号」と呼ぶ、「302-5」みたいな数字とハイフンの文字列)
client_id(FK) integer
[clients]
id(PK) integer
company_name integer (株式会社○○など)
project_tags
id(PK) integer
project_id(FK) integer
tag_id(FK) integer
[tags](Active Hash)11個ある
id(PK)
name
######検索機能の詳細
案件番号は前方一致、顧客会社名は部分一致、タグは1,2,3を選択した場合タグIDとして1,2,3のいずれかを含む案件がヒット
発生している問題の例
赤は検索フォームで選択されたtag_id
Project:1(青)とProject: 2(緑)のみヒット
eager_loadでJOINする
↓
projects.id | project_tags.tag_id |
---|---|
1 | 1 |
1 | 2 |
1 | 3 |
1 | 4 |
1 | 5 |
2 | 2 |
2 | 3 |
2 | 4 |
3 | 4 |
3 | 5 |
3 | 6 |
↓
tag_id: [1,2,3]で絞り込み
↓
projects.id | project_tags.tag_id |
---|---|
1 | 1 |
1 | 2 |
1 | 3 |
2 | 2 |
2 | 3 |
→ Project:1は4番・5番、Project: 2は4番のタグが消えているためビューで全てタグが表示されない
該当のソースコード
※注意:なぜindexではなくallなのか
メモリの問題もあるので文字通りallを取り出すわけではない。
しかし resourcesのindexを使うと
検索前のURLが/projects
検索後のURLは/projects/search....(←クエリ文字列)
検索フォームの自動補完機能をjQueryで実装するときautocompleteアクションへの相対パスを書くわけだが、検索前と検索後でURLの階層が異なるとそれが使えなくなるため、
検索前のURLを/projects/allとして階層をそろえている。
######views/projects/all.html.erb (検索フォーム部分)
Ruby
1<%= form_with url: search_projects_path, method: :get, min: 1, local: true do |f| %> 2 <%= f.label :code, '案件番号' %> 3 <%= f.search_field :code, value: @code, id: 'code_autocomplete' %> 4 5 <%= f.label :client, '顧客' %> 6 <%= f.search_field :client, value: @company_name, id: 'client_autocomplete' %> 7 8 <%= f.collection_check_boxes(:tag_ids, Tag.all, :id, :name, include_hidden: false) do |tag| %> 9 <%= tag.label do %> 10 <%= tag.check_box %> 11 <%= tag.text %> 12 <% end %> 13 <% end %> 14 <%= f.submit '検索' %> 15<% end %>
models/project.rb
Ruby
1 def self.search(code, client_company_name, tag_ids) 2 if tag_ids 3 Project.eager_load(:project_tags, :client) 4 .where('projects.code LIKE ? AND clients.company_name LIKE ?',"#{code}%", "%#{client_company_name}%") 5 .where(project_tags: {tag_id: tag_ids}) 6 else # タグ選択がない場合は検索条件に入れない 7 Project.eager_load(:project_tags, :client) 8 .where('projects.code LIKE ? AND clients.company_name LIKE ?', "#{code}%", "%#{client_company_name}%") 9 end 10 end
######controllers/projects_controller.rb
Ruby
1class ProjectsController < ApplicationController 2 def all 3 @projects = Project.limit(100).preload(:client, :project_tags) 4 end 5 6 def search 7 @code = params[:code] 8 @company_name = params[:company_name] 9 @projects = Project.limit(100).eager_load(:client, :project_tags).search(@code, @company_name, params[:tag_ids]) 10 render action: :all 11 end 12end
######views/projects/all.html.erb(検索結果表示部分)
erb
1<% @projects&.each do |pj| %> 2 <% pj.project_tags&.each do |pt| %> 3 <%= pt.tag.name %> 4 <% end %> 5<% end %>
試したこと
######・案件を絞ったあとでタグは別々にロードする(N+1問題が残る)
一応ヒットした案件のタグはすべて表示される
erb
1<% @projects&.each do |pj| %> 2 <% ProjectTag.where(project_id: pj.id)&.each do |pt| %> 3 <%= pt.tag.name %> 4 <% end %> 5<% end %>
補足情報(FW/ツールのバージョンなど)
FW: AWSのcloud9
rails: 5.2.3
DB: sqlite3
OS: Windows10
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/07/23 09:51