Skip to content

Commit

Permalink
Match search on normalised postcodes
Browse files Browse the repository at this point in the history
- Check if query is a postcode and search directly on a normalised index of this and fallback to address search if no records found
- This enables querying for postcodes stored as SE21 7DN to be matched with query "se217dn"
  • Loading branch information
benbaumann95 committed Oct 22, 2024
1 parent d1bb72c commit 0b7d404
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 15 deletions.
26 changes: 20 additions & 6 deletions app/services/planning_application_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def local_authority
end

def records_matching_query
records_matching_reference.presence || records_matching_address.presence || records_matching_description
records_matching_reference.presence || records_matching_address_search.presence || records_matching_description
end

def records_matching_reference
Expand All @@ -118,15 +118,29 @@ def records_matching_reference
)
end

def records_matching_postcode
current_planning_applications.where(
"LOWER(replace(postcode, ' ', '')) = ?",
query.gsub(/\s+/, "").downcase
)
end

def records_matching_description
current_planning_applications
.select(sanitized_select_sql)
.where(where_sql, query_terms)
.order(rank: :desc)
end

def records_matching_address_search
return records_matching_address unless postcode_query?

postcode_results = records_matching_postcode
postcode_results.presence || records_matching_address
end

def records_matching_address
current_planning_applications.where("address_search @@ to_tsquery('simple', ?)", address_query_terms)
current_planning_applications.where("address_search @@ to_tsquery('simple', ?)", query.split.join(" & "))
end

def sanitized_select_sql
Expand All @@ -149,10 +163,6 @@ def query_terms
@query_terms ||= query.split.join(" | ")
end

def address_query_terms
@address_query_terms ||= query.split.map { |term| term }.join(" & ")
end

def query_submitted?
submit.present?
end
Expand All @@ -172,4 +182,8 @@ def sorted_scope(scope = current_planning_applications)
def selected_application_type_ids
ApplicationType.where(name: application_type).ids
end

def postcode_query?
query.match?(/^(GIR\s?0AA|[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2})$/i)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# frozen_string_literal: true

class AddNormalisedPostcodeIndexToPlanningApplications < ActiveRecord::Migration[7.1]
disable_ddl_transaction!

def change
add_index :planning_applications, "LOWER(replace(postcode, ' ', ''))", algorithm: :concurrently
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2024_10_16_135118) do
ActiveRecord::Schema[7.1].define(version: 2024_10_18_100002) do
# These are extensions that must be enabled in order to support this database
enable_extension "btree_gin"
enable_extension "plpgsql"
Expand Down Expand Up @@ -790,6 +790,7 @@
t.boolean "site_history_checked", default: false, null: false
t.virtual "address_search", type: :tsvector, as: "to_tsvector('simple'::regconfig, (((((((((COALESCE(address_1, ''::character varying))::text || ' '::text) || (COALESCE(address_2, ''::character varying))::text) || ' '::text) || (COALESCE(town, ''::character varying))::text) || ' '::text) || (COALESCE(county, ''::character varying))::text) || ' '::text) || (COALESCE(postcode, ''::character varying))::text))", stored: true
t.index "lower((reference)::text)", name: "ix_planning_applications_on_lower_reference"
t.index "lower(replace((postcode)::text, ' '::text, ''::text))", name: "ix_planning_applications_on_LOWER_replace_postcode"
t.index "to_tsvector('english'::regconfig, description)", name: "index_planning_applications_on_description", using: :gin
t.index ["address_search"], name: "ix_planning_applications_on_address_search", using: :gin
t.index ["api_user_id"], name: "ix_planning_applications_on_api_user_id"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,22 @@ def search_description
.order(rank: :desc)
end

def search_postcode
scope.where(
"LOWER(replace(postcode, ' ', '')) = ?",
query.gsub(/\s+/, "").downcase
)
end

def search_address
scope.where("address_search @@ to_tsquery('simple', ?)", address_query_terms)
return search_address_results unless postcode_query?

postcode_results = search_postcode
postcode_results.presence || search_address_results
end

def search_address_results
scope.where("address_search @@ to_tsquery('simple', ?)", query.split.join(" & "))
end

def sanitized_select_sql
Expand All @@ -60,8 +74,8 @@ def query_terms
@query_terms ||= query.split.join(" | ")
end

def address_query_terms
@query_terms ||= query.split.map { |term| term }.join(" & ")
def postcode_query?
query.match?(/^(GIR\s?0AA|[A-Z]{1,2}\d[A-Z\d]?\s?\d[A-Z]{2})$/i)
end
end
end
Expand Down
21 changes: 16 additions & 5 deletions engines/bops_api/spec/services/application/search_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
context "when performing a search" do
let!(:matching_reference) { create(:planning_application) }
let!(:matching_description) { create(:planning_application, description: "This is a unique description") }
let!(:matching_address) { create(:planning_application, address_1: "123 Unique Road", county: "Greater London", town: "Unique Town", postcode: "123 XYZ") }
let!(:matching_address) { create(:planning_application, address_1: "123 Unique Road", county: "Greater London", town: "Unique Town", postcode: "SE21 7DN") }

context "when searching by reference" do
let(:params) { {q: matching_reference.reference} }
Expand Down Expand Up @@ -72,11 +72,22 @@
end

context "with postcode" do
let(:params) { {q: "123 xyz"} }
context "with exact postcode query" do
let(:params) { {q: "SE21 7DN"} }

it "returns applications matching the address" do
expect(results).to include(matching_address)
expect(results).not_to include(matching_reference, matching_description)
it "returns applications matching the postcode" do
expect(results).to include(matching_address)
expect(results).not_to include(matching_reference, matching_description)
end
end

context "with postcode query" do
let(:params) { {q: "se217Dn"} }

it "returns applications matching the postcode" do
expect(results).to include(matching_address)
expect(results).not_to include(matching_reference, matching_description)
end
end
end
end
Expand Down
7 changes: 7 additions & 0 deletions spec/system/searching_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,13 @@
expect(page).not_to have_content(planning_application1.reference)
expect(page).to have_content(planning_application2.reference)
expect(page).not_to have_content(planning_application3.reference)

fill_in("Find an application", with: "sE228uR")
click_button("Search")

expect(page).not_to have_content(planning_application1.reference)
expect(page).to have_content(planning_application2.reference)
expect(page).not_to have_content(planning_application3.reference)
end
end

Expand Down

0 comments on commit 0b7d404

Please sign in to comment.