diff --git a/app/controllers/prompts/cards_controller.rb b/app/controllers/prompts/cards_controller.rb index bb8c0dfc5..25e9a8dde 100644 --- a/app/controllers/prompts/cards_controller.rb +++ b/app/controllers/prompts/cards_controller.rb @@ -20,7 +20,7 @@ class Prompts::CardsController < ApplicationController def search_cards published_cards - .mentioning(params[:filter], board_ids: Current.user.board_ids) + .mentioning(params[:filter], user: Current.user) .reverse_chronologically .limit(MAX_RESULTS) end diff --git a/app/models/card/searchable.rb b/app/models/card/searchable.rb index 8254f5983..713e92a3a 100644 --- a/app/models/card/searchable.rb +++ b/app/models/card/searchable.rb @@ -4,13 +4,14 @@ module Card::Searchable included do include ::Searchable - scope :mentioning, ->(query, board_ids:) do + scope :mentioning, ->(query, user:) do query = Search::Query.wrap(query) if query.valid? - joins("INNER JOIN search_index ON search_index.card_id = cards.id AND search_index.board_id = cards.board_id") - .where("search_index.board_id IN (?)", board_ids) - .where("MATCH(search_index.content, search_index.title) AGAINST(? IN BOOLEAN MODE)", query.to_s) + table_name = Searchable.search_index_table_name(user.account_id) + joins("INNER JOIN #{table_name} ON #{table_name}.card_id = cards.id AND #{table_name}.board_id = cards.board_id") + .where("#{table_name}.board_id IN (?)", user.board_ids) + .where("MATCH(#{table_name}.content, #{table_name}.title) AGAINST(? IN BOOLEAN MODE)", query.to_s) .distinct else none diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb index 5620d41c1..c22101b52 100644 --- a/app/models/concerns/searchable.rb +++ b/app/models/concerns/searchable.rb @@ -1,6 +1,10 @@ module Searchable extend ActiveSupport::Concern + def self.search_index_table_name(account_id) + "search_index_#{account_id % 16}" + end + included do after_create_commit :create_in_search_index after_update_commit :update_in_search_index @@ -13,8 +17,10 @@ module Searchable private def create_in_search_index + table_name = Searchable.search_index_table_name(account_id) + self.class.connection.execute self.class.sanitize_sql([ - "INSERT INTO search_index (searchable_type, searchable_id, card_id, board_id, title, content, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)", + "INSERT INTO #{table_name} (searchable_type, searchable_id, card_id, board_id, title, content, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)", self.class.name, id, search_card_id, @@ -26,8 +32,10 @@ module Searchable end def update_in_search_index + table_name = Searchable.search_index_table_name(account_id) + result = self.class.connection.execute(self.class.sanitize_sql([ - "UPDATE search_index SET card_id = ?, board_id = ?, title = ?, content = ?, created_at = ? WHERE searchable_type = ? AND searchable_id = ?", + "UPDATE #{table_name} SET card_id = ?, board_id = ?, title = ?, content = ?, created_at = ? WHERE searchable_type = ? AND searchable_id = ?", search_card_id, search_board_id, search_title, @@ -41,8 +49,10 @@ module Searchable end def remove_from_search_index + table_name = Searchable.search_index_table_name(account_id) + self.class.connection.execute self.class.sanitize_sql([ - "DELETE FROM search_index WHERE searchable_type = ? AND searchable_id = ?", + "DELETE FROM #{table_name} WHERE searchable_type = ? AND searchable_id = ?", self.class.name, id ]) diff --git a/app/models/filter.rb b/app/models/filter.rb index c9711c10c..894a7ccca 100644 --- a/app/models/filter.rb +++ b/app/models/filter.rb @@ -33,7 +33,7 @@ class Filter < ApplicationRecord result = result.closed_at_window(closure_window) if closure_window result = result.closed_by(closers) if closers.present? result = terms.reduce(result) do |result, term| - result.mentioning(term, board_ids: creator.board_ids) + result.mentioning(term, user: creator) end result diff --git a/app/models/search.rb b/app/models/search.rb index d534016fc..edb12c8dd 100644 --- a/app/models/search.rb +++ b/app/models/search.rb @@ -27,24 +27,25 @@ class Search query_string = query.to_s sanitized_query = Search::Result.connection.quote(query_string) sanitized_raw_query = Search::Result.connection.quote(query.terms) + table_name = Searchable.search_index_table_name(user.account_id) - Search::Result.from("search_index") - .joins("INNER JOIN cards ON search_index.card_id = cards.id") + Search::Result.from(table_name) + .joins("INNER JOIN cards ON #{table_name}.card_id = cards.id") .joins("INNER JOIN boards ON cards.board_id = boards.id") - .where("search_index.board_id IN (?)", board_ids) - .where("MATCH(search_index.content, search_index.title) AGAINST(? IN BOOLEAN MODE)", query_string) + .where("#{table_name}.board_id IN (?)", board_ids) + .where("MATCH(#{table_name}.content, #{table_name}.title) AGAINST(? IN BOOLEAN MODE)", query_string) .select([ - "search_index.card_id as card_id", - "CASE WHEN search_index.searchable_type = 'Comment' THEN search_index.searchable_id ELSE NULL END as comment_id", - "COALESCE(search_index.title, cards.title) AS card_title_in_database", - "CASE WHEN search_index.searchable_type = 'Card' THEN search_index.content ELSE NULL END AS card_description_in_database", - "CASE WHEN search_index.searchable_type = 'Comment' THEN search_index.content ELSE NULL END AS comment_body_in_database", + "#{table_name}.card_id as card_id", + "CASE WHEN #{table_name}.searchable_type = 'Comment' THEN #{table_name}.searchable_id ELSE NULL END as comment_id", + "COALESCE(#{table_name}.title, cards.title) AS card_title_in_database", + "CASE WHEN #{table_name}.searchable_type = 'Card' THEN #{table_name}.content ELSE NULL END AS card_description_in_database", + "CASE WHEN #{table_name}.searchable_type = 'Comment' THEN #{table_name}.content ELSE NULL END AS comment_body_in_database", "boards.name as board_name", "cards.creator_id", - "search_index.created_at as created_at", - "MATCH(search_index.content, search_index.title) AGAINST(#{sanitized_query} IN BOOLEAN MODE) AS score", + "#{table_name}.created_at as created_at", + "MATCH(#{table_name}.content, #{table_name}.title) AGAINST(#{sanitized_query} IN BOOLEAN MODE) AS score", "#{sanitized_raw_query} AS query" ].join(",")) - .order("search_index.created_at DESC") + .order("#{table_name}.created_at DESC") end end diff --git a/config/brakeman.ignore b/config/brakeman.ignore index b11670f0d..5c5145e3c 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -46,6 +46,52 @@ ], "note": "" }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "08f15e8e259f1da31762507f718191732f86a4f4f6d2fe2719e70fd22c9bc738", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/search.rb", + "line": 33, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "Search::Result.from(Searchable.search_index_table_name(user.account_id)).joins(\"INNER JOIN cards ON #{Searchable.search_index_table_name(user.account_id)}.card_id = cards.id\")", + "render_path": null, + "location": { + "type": "method", + "class": "Search", + "method": "perform_search" + }, + "user_input": "Searchable.search_index_table_name(user.account_id)", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "" + }, + { + "warning_type": "SQL Injection", + "warning_code": 0, + "fingerprint": "e2069dcaa15f04898b9ad356ee3277e4a0b718e88c991c86d896dbded1b47f97", + "check_name": "SQL", + "message": "Possible SQL injection", + "file": "app/models/card/searchable.rb", + "line": 12, + "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/", + "code": "joins(\"INNER JOIN #{Searchable.search_index_table_name(account_id)} ON #{Searchable.search_index_table_name(account_id)}.card_id = cards.id AND #{Searchable.search_index_table_name(account_id)}.board_id = cards.board_id\")", + "render_path": null, + "location": { + "type": "method", + "class": "Card::Searchable", + "method": null + }, + "user_input": "Searchable.search_index_table_name(account_id)", + "confidence": "Medium", + "cwe_id": [ + 89 + ], + "note": "" + }, { "warning_type": "Mass Assignment", "warning_code": 70, diff --git a/db/migrate/20251112093037_create_search_index.rb b/db/migrate/20251112093037_create_search_index.rb deleted file mode 100644 index 4b524b76e..000000000 --- a/db/migrate/20251112093037_create_search_index.rb +++ /dev/null @@ -1,20 +0,0 @@ -class CreateSearchIndex < ActiveRecord::Migration[8.2] - def up - create_table :search_index do |t| - t.string :searchable_type, null: false - t.bigint :searchable_id, null: false - t.bigint :card_id, null: false - t.bigint :board_id, null: false - t.string :title - t.text :content - t.datetime :created_at, null: false - - t.index [:searchable_type, :searchable_id], unique: true - t.index [:content, :title], type: :fulltext, name: "index_search_index_on_content_and_title" - end - end - - def down - drop_table :search_index - end -end diff --git a/db/migrate/20251112093037_create_search_indices.rb b/db/migrate/20251112093037_create_search_indices.rb new file mode 100644 index 000000000..0e8d5b612 --- /dev/null +++ b/db/migrate/20251112093037_create_search_indices.rb @@ -0,0 +1,24 @@ +class CreateSearchIndex < ActiveRecord::Migration[8.2] + def up + 16.times do |i| + create_table "search_index_#{i}".to_sym do |t| + t.string :searchable_type, null: false + t.bigint :searchable_id, null: false + t.bigint :card_id, null: false + t.bigint :board_id, null: false + t.string :title + t.text :content + t.datetime :created_at, null: false + + t.index [:searchable_type, :searchable_id], unique: true, name: "idx_si#{i}_type_id" + t.index [:content, :title], type: :fulltext, name: "idx_si#{i}_fulltext" + end + end + end + + def down + 16.times do |i| + drop_table "search_index_#{i}".to_sym + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 3d25b0b65..7b59e877b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -375,7 +375,7 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_12_093037) do t.index ["reacter_id"], name: "index_reactions_on_reacter_id" end - create_table "search_index", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + create_table "search_index_0", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| t.bigint "board_id", null: false t.bigint "card_id", null: false t.text "content" @@ -383,8 +383,188 @@ ActiveRecord::Schema[8.2].define(version: 2025_11_12_093037) do t.bigint "searchable_id", null: false t.string "searchable_type", null: false t.string "title" - t.index ["content", "title"], name: "index_search_index_on_content_and_title", type: :fulltext - t.index ["searchable_type", "searchable_id"], name: "index_search_index_on_searchable_type_and_searchable_id", unique: true + t.index ["content", "title"], name: "idx_si0_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si0_type_id", unique: true + end + + create_table "search_index_1", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si1_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si1_type_id", unique: true + end + + create_table "search_index_10", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si10_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si10_type_id", unique: true + end + + create_table "search_index_11", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si11_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si11_type_id", unique: true + end + + create_table "search_index_12", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si12_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si12_type_id", unique: true + end + + create_table "search_index_13", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si13_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si13_type_id", unique: true + end + + create_table "search_index_14", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si14_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si14_type_id", unique: true + end + + create_table "search_index_15", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si15_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si15_type_id", unique: true + end + + create_table "search_index_2", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si2_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si2_type_id", unique: true + end + + create_table "search_index_3", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si3_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si3_type_id", unique: true + end + + create_table "search_index_4", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si4_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si4_type_id", unique: true + end + + create_table "search_index_5", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si5_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si5_type_id", unique: true + end + + create_table "search_index_6", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si6_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si6_type_id", unique: true + end + + create_table "search_index_7", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si7_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si7_type_id", unique: true + end + + create_table "search_index_8", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si8_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si8_type_id", unique: true + end + + create_table "search_index_9", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| + t.bigint "board_id", null: false + t.bigint "card_id", null: false + t.text "content" + t.datetime "created_at", null: false + t.bigint "searchable_id", null: false + t.string "searchable_type", null: false + t.string "title" + t.index ["content", "title"], name: "idx_si9_fulltext", type: :fulltext + t.index ["searchable_type", "searchable_id"], name: "idx_si9_type_id", unique: true end create_table "search_queries", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t| diff --git a/lib/tasks/search.rake b/lib/tasks/search.rake index c414f6dc2..94d4051ab 100644 --- a/lib/tasks/search.rake +++ b/lib/tasks/search.rake @@ -1,8 +1,10 @@ namespace :search do desc "Reindex all cards and comments in the search index" task reindex: :environment do - puts "Clearing search index..." - ActiveRecord::Base.connection.execute("DELETE FROM search_index") + puts "Clearing search index shards..." + 16.times do |i| + ActiveRecord::Base.connection.execute("DELETE FROM search_index_#{i}") + end puts "Reindexing cards..." Card.find_each do |card| diff --git a/test/models/card/searchable_test.rb b/test/models/card/searchable_test.rb index 546418023..99b67a975 100644 --- a/test/models/card/searchable_test.rb +++ b/test/models/card/searchable_test.rb @@ -4,44 +4,45 @@ class Card::SearchableTest < ActiveSupport::TestCase self.use_transactional_tests = false setup do - ActiveRecord::Base.connection.execute "DELETE FROM search_index" + 16.times { |i| ActiveRecord::Base.connection.execute "DELETE FROM search_index_#{i}" } Account.find_by(name: "Search Test")&.destroy @account = Account.create!(name: "Search Test") @user = User.create!(name: "Test User", account: @account) @board = Board.create!(name: "Test Board", account: @account, creator: @user) + Current.account = @account end teardown do - ActiveRecord::Base.connection.execute "DELETE FROM search_index" + 16.times { |i| ActiveRecord::Base.connection.execute "DELETE FROM search_index_#{i}" } Account.find_by(name: "Search Test")&.destroy end test "card search" do # Searching by title card = @board.cards.create!(title: "layout is broken", creator: @user) - results = Card.mentioning("layout", board_ids: [@board.id]) + results = Card.mentioning("layout", user: @user) assert_includes results, card # Searching by comment card_with_comment = @board.cards.create!(title: "Some card", creator: @user) card_with_comment.comments.create!(body: "overflowing text", creator: @user) - results = Card.mentioning("overflowing", board_ids: [@board.id]) + results = Card.mentioning("overflowing", user: @user ) assert_includes results, card_with_comment # Sanitizing search query card_broken = @board.cards.create!(title: "broken layout", creator: @user) - results = Card.mentioning("broken \"", board_ids: [@board.id]) + results = Card.mentioning("broken \"", user: @user ) assert_includes results, card_broken # Empty query returns no results - assert_empty Card.mentioning("\"", board_ids: [@board.id]) + assert_empty Card.mentioning("\"", user: @user ) # Filtering by board_ids other_board = Board.create!(name: "Other Board", account: @account, creator: @user) card_in_board = @board.cards.create!(title: "searchable content", creator: @user) card_in_other_board = other_board.cards.create!(title: "searchable content", creator: @user) - results = Card.mentioning("searchable", board_ids: [@board.id]) + results = Card.mentioning("searchable", user: @user) assert_includes results, card_in_board assert_not_includes results, card_in_other_board end diff --git a/test/models/comment/searchable_test.rb b/test/models/comment/searchable_test.rb index c8fe24c08..48b6d709a 100644 --- a/test/models/comment/searchable_test.rb +++ b/test/models/comment/searchable_test.rb @@ -4,32 +4,35 @@ class Comment::SearchableTest < ActiveSupport::TestCase self.use_transactional_tests = false setup do - ActiveRecord::Base.connection.execute "DELETE FROM search_index" + 16.times { |i| ActiveRecord::Base.connection.execute "DELETE FROM search_index_#{i}" } Account.find_by(name: "Search Test")&.destroy @account = Account.create!(name: "Search Test") @user = User.create!(name: "Test User", account: @account) @board = Board.create!(name: "Test Board", account: @account, creator: @user) @card = @board.cards.create!(title: "Test Card", creator: @user) + Current.account = @account end teardown do - ActiveRecord::Base.connection.execute "DELETE FROM search_index" + 16.times { |i| ActiveRecord::Base.connection.execute "DELETE FROM search_index_#{i}" } Account.find_by(name: "Search Test")&.destroy end test "comment search" do + table_name = Searchable.search_index_table_name(@account.id) + # Comment is indexed on create comment = @card.comments.create!(body: "searchable comment text", creator: @user) result = ActiveRecord::Base.connection.execute( - "SELECT COUNT(*) FROM search_index WHERE searchable_type = 'Comment' AND searchable_id = #{comment.id}" + "SELECT COUNT(*) FROM #{table_name} WHERE searchable_type = 'Comment' AND searchable_id = #{comment.id}" ).first[0] assert_equal 1, result # Comment is updated in index comment.update!(body: "updated text") content = ActiveRecord::Base.connection.execute( - "SELECT content FROM search_index WHERE searchable_type = 'Comment' AND searchable_id = #{comment.id}" + "SELECT content FROM #{table_name} WHERE searchable_type = 'Comment' AND searchable_id = #{comment.id}" ).first[0] assert_match /updat/, content @@ -37,7 +40,7 @@ class Comment::SearchableTest < ActiveSupport::TestCase comment_id = comment.id comment.destroy result = ActiveRecord::Base.connection.execute( - "SELECT COUNT(*) FROM search_index WHERE searchable_type = 'Comment' AND searchable_id = #{comment_id}" + "SELECT COUNT(*) FROM #{table_name} WHERE searchable_type = 'Comment' AND searchable_id = #{comment_id}" ).first[0] assert_equal 0, result @@ -45,14 +48,14 @@ class Comment::SearchableTest < ActiveSupport::TestCase card_with_comment = @board.cards.create!(title: "Card One", creator: @user) card_with_comment.comments.create!(body: "unique searchable phrase", creator: @user) card_without_comment = @board.cards.create!(title: "Card Two", creator: @user) - results = Card.mentioning("searchable", board_ids: [@board.id]) + results = Card.mentioning("searchable", user: @user) assert_includes results, card_with_comment assert_not_includes results, card_without_comment # Comment stores parent card_id and board_id new_comment = @card.comments.create!(body: "test comment", creator: @user) row = ActiveRecord::Base.connection.execute( - "SELECT card_id, board_id FROM search_index WHERE searchable_type = 'Comment' AND searchable_id = #{new_comment.id}" + "SELECT card_id, board_id FROM #{table_name} WHERE searchable_type = 'Comment' AND searchable_id = #{new_comment.id}" ).first assert_equal @card.id, row[0] assert_equal @board.id, row[1] diff --git a/test/models/search_test.rb b/test/models/search_test.rb index c9d52c4c5..d17c721e7 100644 --- a/test/models/search_test.rb +++ b/test/models/search_test.rb @@ -4,16 +4,17 @@ class SearchTest < ActiveSupport::TestCase self.use_transactional_tests = false setup do - ActiveRecord::Base.connection.execute "DELETE FROM search_index" + 16.times { |i| ActiveRecord::Base.connection.execute "DELETE FROM search_index_#{i}" } Account.find_by(name: "Search Test")&.destroy @account = Account.create!(name: "Search Test") @user = User.create!(name: "Test User", account: @account) @board = Board.create!(name: "Test Board", account: @account, creator: @user) + Current.account = @account end teardown do - ActiveRecord::Base.connection.execute "DELETE FROM search_index" + 16.times { |i| ActiveRecord::Base.connection.execute "DELETE FROM search_index_#{i}" } Account.find_by(name: "Search Test")&.destroy end