From 7b953378447015440fc4bba24a8444440e702c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CWesley?= Date: Fri, 27 Sep 2024 13:17:59 -0400 Subject: [PATCH 1/2] Fixes #292 Nearly identical to the patch provided by @dwillett, I've updated to apply to latest version of pg_search and added a spec + some documentation. --- README.md | 13 +++++++++++++ lib/pg_search/model.rb | 4 ++-- lib/pg_search/scope_options.rb | 12 +++++++----- spec/integration/pg_search_spec.rb | 12 ++++++++++++ 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4ccf1562..c9b31145 100644 --- a/README.md +++ b/README.md @@ -1203,6 +1203,19 @@ shirt_brands = ShirtBrand.search_by_name("Penguin") .group("shirt_brands.id, #{PgSearch::Configuration.alias('shirt_brands')}.rank") ``` +#### Scoping search rank subqueries + +It may be necessary to limit the scope of a rank subquery for performance reasons. +The subquery scope relation object is accessed by passing a block to the search +method. + +```ruby +shirt_manufacturer = ShirtManufacturer.find_by(name: "Hypercolor Sportswear Co") +shirt_brands = ShirtBrand.search_by_name("Penguin") do |subquery_relation| + subquery_relation.where(shirt_manufacturer: shirt_manufacturer) +end.with_pg_search_rank +``` + ## ATTRIBUTIONS PgSearch would not have been possible without inspiration from texticle (now renamed diff --git a/lib/pg_search/model.rb b/lib/pg_search/model.rb index ceb7a0f1..855f062f 100644 --- a/lib/pg_search/model.rb +++ b/lib/pg_search/model.rb @@ -14,10 +14,10 @@ def pg_search_scope(name, options) raise ArgumentError, "pg_search_scope expects a Hash or Proc" end - define_singleton_method(name) do |*args| + define_singleton_method(name) do |*args, &block| config = Configuration.new(options_proc.call(*args), self) scope_options = ScopeOptions.new(config) - scope_options.apply(self) + scope_options.apply(self, &block) end end diff --git a/lib/pg_search/scope_options.rb b/lib/pg_search/scope_options.rb index 6fe515dd..55d84548 100644 --- a/lib/pg_search/scope_options.rb +++ b/lib/pg_search/scope_options.rb @@ -12,12 +12,12 @@ def initialize(config) @feature_options = config.feature_options end - def apply(scope) + def apply(scope, &block) scope = include_table_aliasing_for_rank(scope) rank_table_alias = scope.pg_search_rank_table_alias(include_counter: true) scope - .joins(rank_join(rank_table_alias)) + .joins(rank_join(rank_table_alias, &block)) .order(Arel.sql("#{rank_table_alias}.rank DESC, #{order_within_rank}")) .extend(WithPgSearchRank) .extend(WithPgSearchHighlight[feature_for(:tsearch)]) @@ -79,7 +79,7 @@ def increment_counter delegate :connection, :quoted_table_name, to: :model def subquery - model + relation = model .unscoped .select("#{primary_key} AS pg_search_id") .select("#{rank} AS rank") @@ -87,6 +87,8 @@ def subquery .where(conditions) .limit(nil) .offset(nil) + + block_given? ? yield(relation) : relation end def conditions @@ -162,8 +164,8 @@ def rank end end - def rank_join(rank_table_alias) - "INNER JOIN (#{subquery.to_sql}) AS #{rank_table_alias} ON #{primary_key} = #{rank_table_alias}.pg_search_id" + def rank_join(rank_table_alias, &block) + "INNER JOIN (#{subquery(&block).to_sql}) AS #{rank_table_alias} ON #{primary_key} = #{rank_table_alias}.pg_search_id" end def include_table_aliasing_for_rank(scope) diff --git a/spec/integration/pg_search_spec.rb b/spec/integration/pg_search_spec.rb index 8c73e692..a0c9ae90 100644 --- a/spec/integration/pg_search_spec.rb +++ b/spec/integration/pg_search_spec.rb @@ -435,6 +435,18 @@ expect(results).to eq([winner, loser]) end + it "is filterable in sub-select when sorted by rank" do + ModelWithPgSearch.create!(content: "foo foo foo", parent_model_id: 2) + loser = ModelWithPgSearch.create!(content: "foo", parent_model_id: 1) + winner = ModelWithPgSearch.create!(content: "foo foo", parent_model_id: 1) + + results = ModelWithPgSearch.search_content("foo") do |subquery_relation| + subquery_relation.where(parent_model_id: 1) + end.with_pg_search_rank + expect(results[0].pg_search_rank).to be > results[1].pg_search_rank + expect(results).to eq([winner, loser]) + end + it "preserves column selection when with_pg_search_rank is chained after a select()" do ModelWithPgSearch.create!(title: "foo", content: "bar") From 8617373d39fe9356c07c90b037887e6d62505f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CWesley?= Date: Fri, 27 Sep 2024 13:22:34 -0400 Subject: [PATCH 2/2] Tweak the spec for better clarity --- spec/integration/pg_search_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/integration/pg_search_spec.rb b/spec/integration/pg_search_spec.rb index a0c9ae90..2c280af1 100644 --- a/spec/integration/pg_search_spec.rb +++ b/spec/integration/pg_search_spec.rb @@ -436,14 +436,16 @@ end it "is filterable in sub-select when sorted by rank" do - ModelWithPgSearch.create!(content: "foo foo foo", parent_model_id: 2) loser = ModelWithPgSearch.create!(content: "foo", parent_model_id: 1) winner = ModelWithPgSearch.create!(content: "foo foo", parent_model_id: 1) + filtered = ModelWithPgSearch.create!(content: "foo foo foo", parent_model_id: 2) + results = ModelWithPgSearch.search_content("foo") do |subquery_relation| subquery_relation.where(parent_model_id: 1) end.with_pg_search_rank - expect(results[0].pg_search_rank).to be > results[1].pg_search_rank + + expect(results).to_not include(filtered) expect(results).to eq([winner, loser]) end