From f79c12520fc023e9e24c7d53b649206b103ee631 Mon Sep 17 00:00:00 2001 From: nakamu-kazu222 Date: Wed, 15 Jan 2025 21:50:58 +0900 Subject: [PATCH] =?UTF-8?q?search=5Fhelper=E3=81=AE=E3=83=A1=E3=82=BD?= =?UTF-8?q?=E3=83=83=E3=83=89=E3=82=92=E5=85=83=E3=81=AB=E6=88=BB=E3=81=99?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/searchables_controller.rb | 2 +- app/helpers/search_helper.rb | 88 +++++++++++++++++------ app/models/concerns/searchable.rb | 8 +-- app/models/search_result.rb | 12 ++-- app/models/searcher.rb | 56 +++++++++------ app/views/searchables/index.html.slim | 1 + 6 files changed, 115 insertions(+), 52 deletions(-) diff --git a/app/controllers/searchables_controller.rb b/app/controllers/searchables_controller.rb index 3c2b1faf186..6766b34087a 100644 --- a/app/controllers/searchables_controller.rb +++ b/app/controllers/searchables_controller.rb @@ -7,7 +7,7 @@ def index @word = params[:word].to_s @document_type = params[:document_type]&.to_sym || :all - result = Searcher.search(@word, document_type: @document_type, current_user: current_user) + result = Searcher.search(@word, document_type: @document_type, current_user:) @searchables = Kaminari.paginate_array(result.uniq).page(params[:page]).per(PER_PAGE) end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index e5be9f5ca44..b4d9431c759 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -1,33 +1,79 @@ # frozen_string_literal: true module SearchHelper - extend ActiveSupport::Concern + def self.matched_document(searchable) + if searchable.instance_of?(Comment) + searchable.commentable_type.constantize.find(searchable.commentable_id) + elsif searchable.instance_of?(Answer) || searchable.instance_of?(CorrectAnswer) + searchable.question + else + searchable + end + end + + def searchable_url(searchable) + case searchable + when SearchResult + searchable.url + when Comment + "#{searchable.commentable.url}#comment_#{searchable.id}" + when CorrectAnswer, Answer + Rails.application.routes.url_helpers.question_path(searchable.question, anchor: "answer_#{searchable.id}") + else + helper_method = "#{searchable.class.name.underscore}_path" + Rails.application.routes.url_helpers.send(helper_method, searchable) + end + rescue NoMethodError + raise NoMethodError, "Route for #{searchable.class.name} is not defined. Please check your routes." + end - included do - def url - case self - when Comment - "#{commentable.url}#comment_#{id}" - when CorrectAnswer - Rails.application.routes.url_helpers.question_path(question, anchor: "answer_#{id}") - when Answer - Rails.application.routes.url_helpers.question_path(question, anchor: "answer_#{id}") + def filtered_message(searchable) + case searchable + when SearchResult + searchable.summary + when Comment + commentable = searchable.commentable_type.constantize.find(searchable.commentable_id) + if policy(commentable).show? || (commentable.is_a?(Practice) && commentable.open_product?) + searchable.body else - helper_method = "#{self.class.name.underscore}_path" - Rails.application.routes.url_helpers.send(helper_method, self) + '該当プラクティスを修了するまで他の人の提出物へのコメントは見れません。' end - rescue NoMethodError - raise NoMethodError, "Route for #{self.class.name} is not defined. Please check your routes." + when Product + searchable.body.presence || '本文がありません。' + else + searchable.try(:description) || '本文がありません。' end + end - def formatted_summary(word) - target_text = respond_to?(:body) ? body : description - return target_text if word.blank? + def comment_or_answer?(searchable) + if searchable.is_a?(SearchResult) + %w[comment answer correct_answer].include?(searchable.model_name) + else + searchable.is_a?(Comment) || searchable.is_a?(Answer) + end + end - words = word.split(/[[:blank:]]+/) - words.reduce(target_text) do |text, single_word| - Searcher.highlight_word(text, single_word) - end + def talk?(searchable) + if searchable.is_a?(SearchResult) + searchable.model_name == 'user' && searchable.talk.present? + else + searchable.instance_of?(User) && searchable.talk.present? + end + end + + def user?(searchable) + if searchable.is_a?(SearchResult) + searchable.model_name == 'user' + else + searchable.instance_of?(User) + end + end + + def created_user(searchable) + if searchable.is_a?(SearchResult) + User.find_by(id: searchable.user_id) + else + searchable.respond_to?(:user) ? searchable.user : nil end end end diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb index 58c31eb1eac..ae9746cd56b 100644 --- a/app/models/concerns/searchable.rb +++ b/app/models/concerns/searchable.rb @@ -30,7 +30,7 @@ def params_for_keyword_search(searched_values = {}) { _join_column_names => word } end end - { combinator: 'and', groupings: groupings } + { combinator: 'and', groupings: } end def word_to_groupings(word) @@ -74,11 +74,11 @@ def primary_role end def formatted_updated_at - if self.is_a?(SearchResult) - self.formatted_updated_at + if is_a?(SearchResult) + formatted_updated_at else weekdays = { 'Sunday' => '日', 'Monday' => '月', 'Tuesday' => '火', 'Wednesday' => '水', - 'Thursday' => '木', 'Friday' => '金', 'Saturday' => '土' } + 'Thursday' => '木', 'Friday' => '金', 'Saturday' => '土' } day_name = updated_at.strftime('%A') updated_at.strftime("%Y年%m月%d日(#{weekdays[day_name]}) %H:%M") end diff --git a/app/models/search_result.rb b/app/models/search_result.rb index 7f283b84a56..332a2def2e1 100644 --- a/app/models/search_result.rb +++ b/app/models/search_result.rb @@ -1,23 +1,25 @@ # frozen_string_literal: true class SearchResult + include SearchHelper + attr_accessor :url, :title, :summary, :formatted_summary, :user_id, :login_name, :formatted_updated_at, :model_name, :label, :wip, :commentable_user, :commentable_type, :primary_role def initialize(searchable, word) - @url = searchable.try(:url) + @url = searchable_url(searchable) @title = Searcher.fetch_title(searchable) - @summary = searchable.try(:summary) - @formatted_summary = searchable.formatted_summary(word) + @summary = filtered_message(searchable) + @formatted_summary = Searcher.highlight_word(@summary, word) @user_id = searchable.is_a?(User) ? searchable.id : searchable.try(:user_id) @login_name = Searcher.fetch_login_name(searchable) - @formatted_updated_at = searchable.formatted_updated_at + @formatted_updated_at = searchable.respond_to?(:formatted_updated_at) ? searchable.formatted_updated_at : searchable.updated_at.strftime('%Y年%m月%d日 %H:%M') @model_name = searchable.class.name.underscore @label = Searcher.fetch_label(searchable) @wip = searchable.try(:wip) @commentable_user = Searcher.fetch_commentable_user(searchable) @commentable_type = I18n.t("activerecord.models.#{searchable.try(:commentable)&.try(:model_name)&.name&.underscore}", default: '') - @primary_role = searchable.primary_role + @primary_role = created_user(searchable)&.primary_role end end diff --git a/app/models/searcher.rb b/app/models/searcher.rb index 51833fb0a1c..10755e4c8c6 100644 --- a/app/models/searcher.rb +++ b/app/models/searcher.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Searcher + include SearchHelper + DOCUMENT_TYPES = [ ['すべて', :all], ['お知らせ', :announcements], @@ -16,7 +18,13 @@ class Searcher AVAILABLE_TYPES = DOCUMENT_TYPES.map(&:second) - %i[all] + %i[comments answers] - def self.search(word, document_type: :all, current_user:) + def self.fetch_url(searchable) + searchable_url(searchable) + rescue NoMethodError + nil + end + + def self.search(word, current_user:, document_type: :all) words = word.split(/[[:blank:]]+/).reject(&:blank?) searchables = case document_type when :all @@ -29,17 +37,22 @@ def self.search(word, document_type: :all, current_user:) result_for(document_type, words).sort_by(&:updated_at).reverse end - if current_user.admin? - searchables = searchables.reject { |searchable| searchable.instance_of?(Talk) } - else - searchables = searchables.reject do |searchable| - searchable.instance_of?(Talk) && searchable.user_id != current_user.id - end - end + searchables = if current_user.admin? + searchables.reject { |searchable| searchable.instance_of?(Talk) } + else + searchables.reject do |searchable| + searchable.instance_of?(Talk) && searchable.user_id != current_user.id + end + end delete_comment_of_talk!(searchables, current_user) - searchables.map { |searchable| SearchResult.new(searchable, word) } + searchables.map do |searchable| + SearchResult.new( + SearchHelper.matched_document(searchable), + word + ) + end end def self.fetch_login_name(searchable) @@ -67,6 +80,8 @@ def self.fetch_title(searchable) def self.fetch_label(searchable) if searchable.is_a?(User) searchable.avatar_url + elsif searchable.is_a?(Talk) + nil else searchable.label end @@ -78,13 +93,15 @@ def self.result_for_all(words) username = user_filter.delete_prefix('user:') user = User.find_by(login_name: username) return [] unless user + AVAILABLE_TYPES .reject { |type| type == :users } .flat_map do |type| model = model(type) next [] unless model.column_names.include?('user_id') || model.column_names.include?('last_updated_user_id') + if type == :practices - model.where("last_updated_user_id = ?", user.id) + model.where('last_updated_user_id = ?', user.id) else model.where(user_id: user.id) end @@ -115,14 +132,14 @@ def self.result_for(type, words, commentable_type: nil) return [] unless user results = if type == :practices - model(type).where("user_id = ? OR last_updated_user_id = ?", user.id, user.id) + model(type).where('user_id = ? OR last_updated_user_id = ?', user.id, user.id) else model(type).where(user_id: user.id) end else results = if commentable?(type) model(type).search_by_keywords(words:, commentable_type:) + - Comment.search_by_keywords(words:, commentable_type: model_name(type)) + Comment.search_by_keywords(words:, commentable_type: model_name(type)) else model(type).search_by_keywords(words:, commentable_type:) end @@ -133,14 +150,13 @@ def self.result_for(type, words, commentable_type: nil) def self.result_matches_keyword?(result, word) return false unless result - if word.match(/^user:(\w+)$/) + if word =~ /^user:(\w+)$/ username = Regexp.last_match(1) - if result.is_a?(Practice) + return result.user&.login_name == username unless result.is_a?(Practice) + user = User.find_by(id: result.last_updated_user_id) return user&.login_name == username - else - return result.user&.login_name == username - end + end searchable_fields = [result.try(:title), result.try(:body), result.try(:description)] searchable_fields.any? { |field| field.to_s.include?(word) } @@ -170,14 +186,12 @@ def self.commentable?(document_type) def self.delete_comment_of_talk!(searchables, current_user) searchables.reject! do |searchable| - if searchable.instance_of?(Comment) && searchable.commentable.instance_of?(Talk) - searchable.commentable.user_id != current_user.id && !current_user.admin? - end + searchable.commentable.user_id != current_user.id && !current_user.admin? if searchable.instance_of?(Comment) && searchable.commentable.instance_of?(Talk) end end def self.highlight_word(text, word) - return '' unless text.present? && word.present? + return text unless text.present? && word.present? sanitized_word = Regexp.escape(word) text.gsub(/(#{sanitized_word})/i, '\1') diff --git a/app/views/searchables/index.html.slim b/app/views/searchables/index.html.slim index 9bc7d219da7..c3e0a70bb0c 100644 --- a/app/views/searchables/index.html.slim +++ b/app/views/searchables/index.html.slim @@ -14,6 +14,7 @@ hr.a-border .container.is-md .card-list.a-card - @searchables.each do |searchable| + - next if searchable.model_name == 'talk' span class="card-list-item is-#{searchable.model_name}" .card-list-item__inner - searchable_user = User.find_by(id: searchable.user_id)