+
+ <%= render "shared/filter_text", param: :phone, label: "Phone", submit: true,
+ wrapper_class: "w-full" %>
+
<%= render "shared/filter_select", param: :role, label: "Role",
options: Person::ROLE_FILTER_OPTIONS, selected: params[:role],
diff --git a/spec/models/person_spec.rb b/spec/models/person_spec.rb
index 6a5f2d83db..65c991ec18 100644
--- a/spec/models/person_spec.rb
+++ b/spec/models/person_spec.rb
@@ -700,6 +700,24 @@ def phone_numbers
expect(results).not_to include(person_alice)
end
+ it 'search_name_email matches names and emails but not phones or addresses' do
+ create(:contact_method, contactable: person_alice, value: '555-0199')
+ create(:address, addressable: person_bob, street_address: '123 Hiddenway')
+
+ expect(Person.search_name_email('Alice')).to include(person_alice)
+ expect(Person.search_name_email('alice@example')).to include(person_alice)
+ expect(Person.search_name_email('555-0199')).not_to include(person_alice)
+ expect(Person.search_name_email('Hiddenway')).not_to include(person_bob)
+ end
+
+ it 'the broad search still matches phones and addresses' do
+ create(:contact_method, contactable: person_alice, value: '555-0199')
+ create(:address, addressable: person_bob, street_address: '123 Hiddenway')
+
+ expect(Person.search('555-0199')).to include(person_alice)
+ expect(Person.search('Hiddenway')).to include(person_bob)
+ end
+
it 'filters by shared_contact_info matching a full name' do
results = Person.search_by_params(shared_contact_info: 'Alice Smith')
expect(results).to include(person_alice)
@@ -714,20 +732,30 @@ def phone_numbers
expect(Person.search_by_params(shared_contact_info: 'alice@example')).not_to include(person_alice)
end
- it 'filters by shared_contact_info matching a shared login phone' do
+ it 'no longer matches phones through shared_contact_info (phone search is admin-only)' do
person_bob.update!(profile_show_phone: true)
- person_bob.user.update!(phone: '555-0199')
+ create(:contact_method, contactable: person_bob, value: '555-0199')
- expect(Person.search_by_params(shared_contact_info: '555-0199')).to include(person_bob)
+ expect(Person.search_by_params(shared_contact_info: '555-0199')).not_to include(person_bob)
end
- it 'filters by shared_contact_info matching terms across a name and a shared phone' do
- person_alice.update!(profile_show_phone: true)
- person_alice.user.update!(phone: '555-0199')
+ it 'filters by phone matching a contact method' do
+ create(:contact_method, contactable: person_alice, value: '555-0199')
- results = Person.search_by_params(shared_contact_info: 'Alice 555-0199')
- expect(results).to include(person_alice)
- expect(results).not_to include(person_bob)
+ expect(Person.search_by_params(phone: '555-0199')).to include(person_alice)
+ expect(Person.search_by_params(phone: '555-0199')).not_to include(person_bob)
+ end
+
+ it 'filters by phone matching an inactive contact method' do
+ create(:contact_method, contactable: person_alice, value: '555-0199', inactive: true)
+
+ expect(Person.search_by_params(phone: '555-0199')).to include(person_alice)
+ end
+
+ it 'does not match a login (users) phone' do
+ person_bob.user.update!(phone: '555-0199')
+
+ expect(Person.search_by_params(phone: '555-0199')).not_to include(person_bob)
end
it 'filters by shared_contact_info treating a quoted phrase as one term' do
diff --git a/spec/requests/people_email_addresses_spec.rb b/spec/requests/people_email_addresses_spec.rb
index a0c4189a1f..003d7fbf16 100644
--- a/spec/requests/people_email_addresses_spec.rb
+++ b/spec/requests/people_email_addresses_spec.rb
@@ -35,6 +35,16 @@
expect(response.body).to include("No email addresses found.")
end
+ it "does not match a phone (same narrow search as the people index)" do
+ person = create(:person, first_name: "Carol", last_name: "Phoner",
+ email: "carol@example.com", user: nil)
+ create(:contact_method, contactable: person, value: "555-0142")
+
+ get email_addresses_people_path(contact_info: "555-0142")
+
+ expect(response.body).not_to include("carol@example.com")
+ end
+
it "is admin-only" do
sign_out admin
sign_in create(:user)
diff --git a/spec/requests/people_search_spec.rb b/spec/requests/people_search_spec.rb
index cb2903d3ae..410b705d4d 100644
--- a/spec/requests/people_search_spec.rb
+++ b/spec/requests/people_search_spec.rb
@@ -15,6 +15,11 @@
expect(response).to have_http_status(:ok)
end
+ it "shows the admin-only phone filter" do
+ get people_path
+ expect(response.body).to include('name="phone"')
+ end
+
it "breaks the person profile button out of the results frame" do
get people_path, headers: turbo_headers
expect_frame_breakout(response.body, person_path(person_alice))
@@ -162,6 +167,7 @@ def published_person(first_name)
get people_path
expect(response.body).not_to include("Admin-only filters")
expect(response.body).not_to include("staff_tag_ids")
+ expect(response.body).not_to include('name="phone"')
end
it "ignores a staff tag filter passed by URL" do
@@ -218,21 +224,17 @@ def search(term)
expect(search("Hiddenway")).not_to include("Markedone")
end
- it "matches a shared phone" do
+ it "does not match phones (phone search is admin-only)" do
marked.update!(profile_show_phone: true)
create(:contact_method, contactable: marked, value: "555-0142")
- expect(search("555-0142")).to include("Markedone")
- end
-
- it "skips a phone the person hides" do
- marked.update!(profile_show_phone: false)
- create(:contact_method, contactable: marked, value: "555-0142")
expect(search("555-0142")).not_to include("Markedone")
end
- it "skips an inactive phone" do
- create(:contact_method, contactable: marked, value: "555-0142", inactive: true)
- expect(search("555-0142")).not_to include("Markedone")
+ it "ignores a phone filter passed by URL" do
+ marked.update!(profile_show_phone: true)
+ create(:contact_method, contactable: marked, value: "555-0142")
+ get people_path, params: { phone: "555-0142" }, headers: turbo_headers
+ expect(response.body).to include("Markedone")
end
%w[first_name_last_initial first_name_only].each do |preference|
@@ -348,6 +350,11 @@ def search(term)
response.body
end
+ def search_phone(term)
+ get people_path, params: { phone: term }, headers: turbo_headers
+ response.body
+ end
+
it "matches an email the person hides" do
person.update!(email: "hidden@example.com")
expect(search("hidden@example")).to include("Hiddenfacts")
@@ -363,19 +370,19 @@ def search(term)
expect(search("Legalname")).to include("Hiddenfacts")
end
- it "matches addresses" do
+ it "does not match addresses" do
create(:address, addressable: person, street_address: "123 Hiddenway")
- expect(search("Hiddenway")).to include("Hiddenfacts")
+ expect(search("Hiddenway")).not_to include("Hiddenfacts")
end
- it "matches a phone the person hides" do
+ it "matches a phone the person hides via the phone filter" do
create(:contact_method, contactable: person, value: "555-0142")
- expect(search("555-0142")).to include("Hiddenfacts")
+ expect(search_phone("555-0142")).to include("Hiddenfacts")
end
- it "matches an inactive phone" do
+ it "matches an inactive phone via the phone filter" do
create(:contact_method, contactable: person, value: "555-0142", inactive: true)
- expect(search("555-0142")).to include("Hiddenfacts")
+ expect(search_phone("555-0142")).to include("Hiddenfacts")
end
%w[first_name_last_initial first_name_only].each do |preference|
From b66d922088338bec235322404c7018d1fd69c7d9 Mon Sep 17 00:00:00 2001
From: Justin Miller <16829344+jmilljr24@users.noreply.github.com>
Date: Sat, 24 Oct 2026 09:25:07 -0400
Subject: [PATCH 3/7] specs
---
spec/models/person_spec.rb | 28 +-------------------
spec/requests/people_email_addresses_spec.rb | 10 -------
spec/requests/people_search_spec.rb | 11 --------
3 files changed, 1 insertion(+), 48 deletions(-)
diff --git a/spec/models/person_spec.rb b/spec/models/person_spec.rb
index 65c991ec18..d1629ebfef 100644
--- a/spec/models/person_spec.rb
+++ b/spec/models/person_spec.rb
@@ -700,22 +700,9 @@ def phone_numbers
expect(results).not_to include(person_alice)
end
- it 'search_name_email matches names and emails but not phones or addresses' do
- create(:contact_method, contactable: person_alice, value: '555-0199')
- create(:address, addressable: person_bob, street_address: '123 Hiddenway')
-
+ it 'search_name_email matches names and emails' do
expect(Person.search_name_email('Alice')).to include(person_alice)
expect(Person.search_name_email('alice@example')).to include(person_alice)
- expect(Person.search_name_email('555-0199')).not_to include(person_alice)
- expect(Person.search_name_email('Hiddenway')).not_to include(person_bob)
- end
-
- it 'the broad search still matches phones and addresses' do
- create(:contact_method, contactable: person_alice, value: '555-0199')
- create(:address, addressable: person_bob, street_address: '123 Hiddenway')
-
- expect(Person.search('555-0199')).to include(person_alice)
- expect(Person.search('Hiddenway')).to include(person_bob)
end
it 'filters by shared_contact_info matching a full name' do
@@ -732,13 +719,6 @@ def phone_numbers
expect(Person.search_by_params(shared_contact_info: 'alice@example')).not_to include(person_alice)
end
- it 'no longer matches phones through shared_contact_info (phone search is admin-only)' do
- person_bob.update!(profile_show_phone: true)
- create(:contact_method, contactable: person_bob, value: '555-0199')
-
- expect(Person.search_by_params(shared_contact_info: '555-0199')).not_to include(person_bob)
- end
-
it 'filters by phone matching a contact method' do
create(:contact_method, contactable: person_alice, value: '555-0199')
@@ -752,12 +732,6 @@ def phone_numbers
expect(Person.search_by_params(phone: '555-0199')).to include(person_alice)
end
- it 'does not match a login (users) phone' do
- person_bob.user.update!(phone: '555-0199')
-
- expect(Person.search_by_params(phone: '555-0199')).not_to include(person_bob)
- end
-
it 'filters by shared_contact_info treating a quoted phrase as one term' do
person_mary = create(:person, first_name: 'Mary Ann', last_name: 'Lee')
diff --git a/spec/requests/people_email_addresses_spec.rb b/spec/requests/people_email_addresses_spec.rb
index 003d7fbf16..a0c4189a1f 100644
--- a/spec/requests/people_email_addresses_spec.rb
+++ b/spec/requests/people_email_addresses_spec.rb
@@ -35,16 +35,6 @@
expect(response.body).to include("No email addresses found.")
end
- it "does not match a phone (same narrow search as the people index)" do
- person = create(:person, first_name: "Carol", last_name: "Phoner",
- email: "carol@example.com", user: nil)
- create(:contact_method, contactable: person, value: "555-0142")
-
- get email_addresses_people_path(contact_info: "555-0142")
-
- expect(response.body).not_to include("carol@example.com")
- end
-
it "is admin-only" do
sign_out admin
sign_in create(:user)
diff --git a/spec/requests/people_search_spec.rb b/spec/requests/people_search_spec.rb
index 410b705d4d..918470e912 100644
--- a/spec/requests/people_search_spec.rb
+++ b/spec/requests/people_search_spec.rb
@@ -224,12 +224,6 @@ def search(term)
expect(search("Hiddenway")).not_to include("Markedone")
end
- it "does not match phones (phone search is admin-only)" do
- marked.update!(profile_show_phone: true)
- create(:contact_method, contactable: marked, value: "555-0142")
- expect(search("555-0142")).not_to include("Markedone")
- end
-
it "ignores a phone filter passed by URL" do
marked.update!(profile_show_phone: true)
create(:contact_method, contactable: marked, value: "555-0142")
@@ -370,11 +364,6 @@ def search_phone(term)
expect(search("Legalname")).to include("Hiddenfacts")
end
- it "does not match addresses" do
- create(:address, addressable: person, street_address: "123 Hiddenway")
- expect(search("Hiddenway")).not_to include("Hiddenfacts")
- end
-
it "matches a phone the person hides via the phone filter" do
create(:contact_method, contactable: person, value: "555-0142")
expect(search_phone("555-0142")).to include("Hiddenfacts")
From a0e39b5a1301bc5ae7915dc7a225bcda14680388 Mon Sep 17 00:00:00 2001
From: Justin Miller <16829344+jmilljr24@users.noreply.github.com>
Date: Sat, 24 Oct 2026 09:29:43 -0400
Subject: [PATCH 4/7] clean up
---
app/controllers/people_controller.rb | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/controllers/people_controller.rb b/app/controllers/people_controller.rb
index 163d4af03b..58d3a53819 100644
--- a/app/controllers/people_controller.rb
+++ b/app/controllers/people_controller.rb
@@ -30,7 +30,7 @@ def index
filters = authorized_scope(params, as: :index).to_unsafe_h
contact_info = filters.delete(:contact_info)
filtered = base_scope.search_by_params(filters)
- filtered = filtered.search_name_email(contact_info) if contact_info.present?
+ filtered = filtered.search_name_email(contact_info)
filtered = filtered.order(:first_name, :last_name)
total = filtered.count
@@ -48,7 +48,7 @@ def email_addresses
filters = params.to_unsafe_h
contact_info = filters.delete(:contact_info)
people = Person.includes(:user).search_by_params(filters)
- people = people.search_name_email(contact_info) if contact_info.present?
+ people = people.search_name_email(contact_info)
@email_addresses = people.filter_map { |person| person.preferred_email.presence }.uniq.sort
end
From 286732ce0a378740517ba95bb0616645de40cadb Mon Sep 17 00:00:00 2001
From: Justin Miller <16829344+jmilljr24@users.noreply.github.com>
Date: Sun, 25 Oct 2026 03:30:43 -0400
Subject: [PATCH 5/7] add full contact info
---
app/controllers/people_controller.rb | 10 ++++--
app/models/person.rb | 20 +++++++----
app/policies/person_policy.rb | 4 +--
app/views/people/_search_boxes.html.erb | 12 +++----
spec/models/person_spec.rb | 25 ++++++++++---
spec/policies/person_policy_spec.rb | 3 +-
spec/requests/people_email_addresses_spec.rb | 18 +++++++---
spec/requests/people_search_spec.rb | 37 +++++++++++---------
8 files changed, 87 insertions(+), 42 deletions(-)
diff --git a/app/controllers/people_controller.rb b/app/controllers/people_controller.rb
index 58d3a53819..7b8054dd93 100644
--- a/app/controllers/people_controller.rb
+++ b/app/controllers/people_controller.rb
@@ -28,9 +28,12 @@ def index
categorizable_items: { category: :category_type }
))
filters = authorized_scope(params, as: :index).to_unsafe_h
+ name_email = filters.delete(:name_email)
contact_info = filters.delete(:contact_info)
filtered = base_scope.search_by_params(filters)
- filtered = filtered.search_name_email(contact_info)
+ filtered = filtered.search_name_email(name_email)
+ filtered = filtered.search_contact(contact_info)
+ filtered = filtered.distinct if contact_info.present?
filtered = filtered.order(:first_name, :last_name)
total = filtered.count
@@ -46,9 +49,12 @@ def index
def email_addresses
authorize! Person, to: :manage?
filters = params.to_unsafe_h
+ name_email = filters.delete(:name_email)
contact_info = filters.delete(:contact_info)
people = Person.includes(:user).search_by_params(filters)
- people = people.search_name_email(contact_info)
+ people = people.search_name_email(name_email)
+ people = people.search_contact(contact_info)
+ people = people.distinct if contact_info.present?
@email_addresses = people.filter_map { |person| person.preferred_email.presence }.uniq.sort
end
diff --git a/app/models/person.rb b/app/models/person.rb
index ebdfcee055..407121cee7 100644
--- a/app/models/person.rb
+++ b/app/models/person.rb
@@ -213,6 +213,20 @@ def known_email_2_type? = email_2_type.in?(CONTACT_TYPES)
options :all, type: :text, default: true, default_operator: :or
end
+ search_scope :search_contact do
+ scope { left_joins(:contact_methods, :addresses) }
+ attributes contact_methods_phone: "contact_methods.value"
+ attributes address_street: "addresses.street_address"
+ attributes address_city: "addresses.city"
+ attributes address_state: "addresses.state"
+ attributes address_zip: "addresses.zip_code"
+ attributes address_phone: "addresses.phone"
+ attributes all: [ "contact_methods.value",
+ "addresses.street_address", "addresses.city", "addresses.state",
+ "addresses.zip_code", "addresses.phone" ]
+ options :all, type: :text, default: true, default_operator: :or
+ end
+
FIRST_NAME_HIDDEN_PREFERENCES = %w[last_name_only].freeze
LAST_NAME_HIDDEN_PREFERENCES = %w[first_name_last_initial first_name_only].freeze
@@ -295,11 +309,6 @@ def self.shared_contact_search(query)
tag_ids = Array(ids).reject(&:blank?)
return all if tag_ids.empty?
joins(:staff_taggings).where(staff_taggings: { staff_tag_id: tag_ids }).distinct }
- scope :with_phone, ->(value) {
- like = "%#{sanitize_sql_like(value.to_s.strip)}%"
- where(id: ContactMethod.where(contactable_type: polymorphic_name)
- .where("value LIKE ?", like)
- .select(:contactable_id)) }
scope :story_authors, -> { joins(:stories_as_author).distinct }
scope :blog_contributors, -> { where(blog_contributor: true) }
scope :placeholder_email, -> { where("email REGEXP ?", PlaceholderEmail.sql_pattern) }
@@ -437,7 +446,6 @@ def self.shared_contact_search(query)
def self.search_by_params(params)
results = is_a?(ActiveRecord::Relation) ? self : all
results = results.search(params[:contact_info]) if params[:contact_info].present?
- results = results.with_phone(params[:phone]) if params[:phone].present?
results = results.shared_contact_search(params[:shared_contact_info]) if params[:shared_contact_info].present?
results = results.by_role(params[:role]) if params[:role].present?
results = results.by_facilitator_status(params[:facilitator_status]) if params[:facilitator_status].present?
diff --git a/app/policies/person_policy.rb b/app/policies/person_policy.rb
index 00f144149f..aabf1ecdb3 100644
--- a/app/policies/person_policy.rb
+++ b/app/policies/person_policy.rb
@@ -20,11 +20,11 @@ class PersonPolicy < ApplicationPolicy
# behind the same admin gate the :index params_filter enforces.
ADMIN_ONLY_FILTERS = %i[
role facilitator_status membership_status
- staff_tag_ids topic_subscription_type_id phone
+ staff_tag_ids topic_subscription_type_id contact_info
].freeze
SHARED_ONLY_FILTERS = {
- contact_info: :shared_contact_info,
+ name_email: :shared_contact_info,
sector_ids: :shared_sector_ids,
age_range_ids: :shared_age_range_ids,
organization_name: :shared_organization_name,
diff --git a/app/views/people/_search_boxes.html.erb b/app/views/people/_search_boxes.html.erb
index 9e9994ab7a..c59e91557d 100644
--- a/app/views/people/_search_boxes.html.erb
+++ b/app/views/people/_search_boxes.html.erb
@@ -7,8 +7,8 @@
class: "space-y-4") do |f| %>
<%= hidden_field_tag "organization_id", params[:organization_id] if params[:organization_id].present? %>
-
- <%= render "shared/filter_text", param: :contact_info, label: "Name or email", submit: true,
+
+ <%= render "shared/filter_text", param: :name_email, label: "Name or email", submit: true,
wrapper_class: "w-full md:flex-1" %>
@@ -37,10 +37,6 @@
Admin-only filters
-
- <%= render "shared/filter_text", param: :phone, label: "Phone", submit: true,
- wrapper_class: "w-full" %>
-
<%= render "shared/filter_select", param: :role, label: "Role",
options: Person::ROLE_FILTER_OPTIONS, selected: params[:role],
@@ -74,6 +70,10 @@
selected: Array(params[:topic_subscription_type_id]).map(&:to_i), placeholder: "Any topic",
wrapper_class: "w-full",
hint: link_to("Manage topics", topic_subscription_types_path, class: eyebrow_link_class, target: "_blank", rel: "noopener") %>
+
+
+ <%= render "shared/filter_text", param: :contact_info, label: "Contact info", submit: true,
+ wrapper_class: "w-full" %>
<% end %>
diff --git a/spec/models/person_spec.rb b/spec/models/person_spec.rb
index d1629ebfef..e13a437648 100644
--- a/spec/models/person_spec.rb
+++ b/spec/models/person_spec.rb
@@ -719,17 +719,32 @@ def phone_numbers
expect(Person.search_by_params(shared_contact_info: 'alice@example')).not_to include(person_alice)
end
- it 'filters by phone matching a contact method' do
+ it 'search_contact matches a contact method' do
create(:contact_method, contactable: person_alice, value: '555-0199')
- expect(Person.search_by_params(phone: '555-0199')).to include(person_alice)
- expect(Person.search_by_params(phone: '555-0199')).not_to include(person_bob)
+ expect(Person.search_contact('555-0199')).to include(person_alice)
+ expect(Person.search_contact('555-0199')).not_to include(person_bob)
end
- it 'filters by phone matching an inactive contact method' do
+ it 'search_contact matches an inactive contact method' do
create(:contact_method, contactable: person_alice, value: '555-0199', inactive: true)
- expect(Person.search_by_params(phone: '555-0199')).to include(person_alice)
+ expect(Person.search_contact('555-0199')).to include(person_alice)
+ end
+
+ it 'search_contact matches an address' do
+ create(:address, addressable: person_bob, street_address: '123 Hiddenway', city: 'Portland')
+
+ expect(Person.search_contact('Hiddenway')).to include(person_bob)
+ end
+
+ it 'search_contact supports attribute prefixes' do
+ create(:address, addressable: person_bob, street_address: '123 Hiddenway', city: 'Portland')
+ create(:contact_method, contactable: person_alice, value: '555-0199')
+
+ expect(Person.search_contact('address_city:portland')).to include(person_bob)
+ expect(Person.search_contact('address_city:portland')).not_to include(person_alice)
+ expect(Person.search_contact('contact_methods_phone:555')).to include(person_alice)
end
it 'filters by shared_contact_info treating a quoted phrase as one term' do
diff --git a/spec/policies/person_policy_spec.rb b/spec/policies/person_policy_spec.rb
index 9a75f946df..95232c6b14 100644
--- a/spec/policies/person_policy_spec.rb
+++ b/spec/policies/person_policy_spec.rb
@@ -412,7 +412,8 @@ def in_production
describe "params_filter(:index)" do
let(:filters) do
ActionController::Parameters.new(
- contact_info: "ada", role: "story_authors", staff_tag_ids: [ "1" ],
+ name_email: "ada", contact_info: "555-0142",
+ role: "story_authors", staff_tag_ids: [ "1" ],
topic_subscription_type_id: [ "2" ], facilitator_status: "active",
membership_status: "paid", sector_ids: [ "4" ], age_range_ids: [ "5" ],
organization_name: "Acme", organization_id: "3"
diff --git a/spec/requests/people_email_addresses_spec.rb b/spec/requests/people_email_addresses_spec.rb
index a0c4189a1f..8421d88f48 100644
--- a/spec/requests/people_email_addresses_spec.rb
+++ b/spec/requests/people_email_addresses_spec.rb
@@ -12,13 +12,23 @@
create(:person, first_name: "Bob", last_name: "Builder",
email: "bob@example.com", user: nil)
- get email_addresses_people_path(contact_info: "Alice")
+ get email_addresses_people_path(name_email: "Alice")
expect(response).to have_http_status(:success)
expect(response.body).to include("alice@example.com")
expect(response.body).not_to include("bob@example.com")
end
+ it "matches contact details like the people index" do
+ person = create(:person, first_name: "Carol", last_name: "Phoner",
+ email: "carol@example.com", user: nil)
+ create(:contact_method, contactable: person, value: "555-0142")
+
+ get email_addresses_people_path(contact_info: "555-0142")
+
+ expect(response.body).to include("carol@example.com")
+ end
+
it "falls back to the person's preferred email when the email column is blank" do
create(:person, email: nil, user: create(:user, email: "dana@example.com"))
create(:person, email: nil, email_2: "rio@example.com", user: nil)
@@ -30,7 +40,7 @@
end
it "shows the empty state when no people match" do
- get email_addresses_people_path(contact_info: "nobody-matches-this")
+ get email_addresses_people_path(name_email: "nobody-matches-this")
expect(response.body).to include("No email addresses found.")
end
@@ -47,10 +57,10 @@
describe "Email addresses link on the filtered index frame" do
it "carries the active filter into the Email addresses link" do
- get people_path(contact_info: "Alice"), headers: { "Turbo-Frame" => "people_results" }
+ get people_path(name_email: "Alice"), headers: { "Turbo-Frame" => "people_results" }
expect(response).to have_http_status(:success)
- expect(response.body).to include(email_addresses_people_path(contact_info: "Alice"))
+ expect(response.body).to include(email_addresses_people_path(name_email: "Alice"))
end
end
end
diff --git a/spec/requests/people_search_spec.rb b/spec/requests/people_search_spec.rb
index 918470e912..78786ce669 100644
--- a/spec/requests/people_search_spec.rb
+++ b/spec/requests/people_search_spec.rb
@@ -15,9 +15,9 @@
expect(response).to have_http_status(:ok)
end
- it "shows the admin-only phone filter" do
+ it "shows the admin-only contact info filter" do
get people_path
- expect(response.body).to include('name="phone"')
+ expect(response.body).to include('name="contact_info"')
end
it "breaks the person profile button out of the results frame" do
@@ -32,8 +32,8 @@
expect(response.body).to include("Bob")
end
- it "filters by contact_info" do
- get people_path, params: { contact_info: "Alice" }, headers: turbo_headers
+ it "filters by name_email" do
+ get people_path, params: { name_email: "Alice" }, headers: turbo_headers
expect(response).to have_http_status(:ok)
expect(response.body).to include("Alice")
expect(response.body).not_to include("Bob")
@@ -167,7 +167,7 @@ def published_person(first_name)
get people_path
expect(response.body).not_to include("Admin-only filters")
expect(response.body).not_to include("staff_tag_ids")
- expect(response.body).not_to include('name="phone"')
+ expect(response.body).not_to include('name="contact_info"')
end
it "ignores a staff tag filter passed by URL" do
@@ -187,7 +187,7 @@ def published_person(first_name)
end
it "still applies the filters the bar does offer them" do
- get people_path, params: { contact_info: "Markedone" }, headers: turbo_headers
+ get people_path, params: { name_email: "Markedone" }, headers: turbo_headers
expect(response.body).to include("Markedone")
expect(response.body).not_to include("Plainone")
@@ -195,7 +195,7 @@ def published_person(first_name)
describe "contact info search" do
def search(term)
- get people_path, params: { contact_info: term }, headers: turbo_headers
+ get people_path, params: { name_email: term }, headers: turbo_headers
response.body
end
@@ -224,10 +224,10 @@ def search(term)
expect(search("Hiddenway")).not_to include("Markedone")
end
- it "ignores a phone filter passed by URL" do
+ it "ignores a contact details filter passed by URL" do
marked.update!(profile_show_phone: true)
create(:contact_method, contactable: marked, value: "555-0142")
- get people_path, params: { phone: "555-0142" }, headers: turbo_headers
+ get people_path, params: { contact_info: "555-0142" }, headers: turbo_headers
expect(response.body).to include("Markedone")
end
@@ -340,12 +340,12 @@ def search(term)
let!(:person) { create(:person, first_name: "Hiddenfacts", profile_show_email: false, profile_show_phone: false) }
def search(term)
- get people_path, params: { contact_info: term }, headers: turbo_headers
+ get people_path, params: { name_email: term }, headers: turbo_headers
response.body
end
- def search_phone(term)
- get people_path, params: { phone: term }, headers: turbo_headers
+ def search_contact_details(term)
+ get people_path, params: { contact_info: term }, headers: turbo_headers
response.body
end
@@ -364,14 +364,19 @@ def search_phone(term)
expect(search("Legalname")).to include("Hiddenfacts")
end
- it "matches a phone the person hides via the phone filter" do
+ it "matches a contact method via the contact info filter" do
create(:contact_method, contactable: person, value: "555-0142")
- expect(search_phone("555-0142")).to include("Hiddenfacts")
+ expect(search_contact_details("555-0142")).to include("Hiddenfacts")
end
- it "matches an inactive phone via the phone filter" do
+ it "matches an inactive contact method via the contact info filter" do
create(:contact_method, contactable: person, value: "555-0142", inactive: true)
- expect(search_phone("555-0142")).to include("Hiddenfacts")
+ expect(search_contact_details("555-0142")).to include("Hiddenfacts")
+ end
+
+ it "matches an address via the contact info filter" do
+ create(:address, addressable: person, street_address: "123 Hiddenway")
+ expect(search_contact_details("Hiddenway")).to include("Hiddenfacts")
end
%w[first_name_last_initial first_name_only].each do |preference|
From a24b2fa3b2284b5d08143a6257c5bad6ac694473 Mon Sep 17 00:00:00 2001
From: Justin Miller <16829344+jmilljr24@users.noreply.github.com>
Date: Sun, 25 Oct 2026 03:54:45 -0400
Subject: [PATCH 6/7] add tooltip
---
app/views/people/_search_boxes.html.erb | 3 ++-
app/views/shared/_filter_text.html.erb | 10 +++++++++-
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/app/views/people/_search_boxes.html.erb b/app/views/people/_search_boxes.html.erb
index c59e91557d..03a845881a 100644
--- a/app/views/people/_search_boxes.html.erb
+++ b/app/views/people/_search_boxes.html.erb
@@ -73,7 +73,8 @@
<%= render "shared/filter_text", param: :contact_info, label: "Contact info", submit: true,
- wrapper_class: "w-full" %>
+ wrapper_class: "w-full",
+ tooltip: "Searches phone, address, city, state, and ZIP." %>
<% end %>
diff --git a/app/views/shared/_filter_text.html.erb b/app/views/shared/_filter_text.html.erb
index de7964b522..f380591174 100644
--- a/app/views/shared/_filter_text.html.erb
+++ b/app/views/shared/_filter_text.html.erb
@@ -2,6 +2,7 @@
bars, so every free-text filter renders the same markup. Locals:
param – input name/id (Symbol or String)
label – optional field label (omitted when nil)
+ tooltip – optional help text shown as a title on an info icon beside the label
placeholder – optional input placeholder
value – optional input value (defaults to params[param])
submit – true adds the magnifier search button inside the field
@@ -17,7 +18,14 @@
placeholder: local_assigns[:placeholder],
**local_assigns.fetch(:input_options, {}) %>
<%= tag.div class: local_assigns.fetch(:wrapper_class, "w-full md:w-48") do %>
- <%= label_tag param, label_text, class: local_assigns.fetch(:label_class, search_label_class) if label_text %>
+ <% if label_text %>
+ <%= label_tag param, nil, class: local_assigns.fetch(:label_class, search_label_class) do %>
+ <%= label_text %>
+ <% if local_assigns[:tooltip].present? %>
+
<%= input %>
From cbd995cbbaf67d216de023c93cb32107c1952eb3 Mon Sep 17 00:00:00 2001
From: Justin Miller <16829344+jmilljr24@users.noreply.github.com>
Date: Sun, 25 Oct 2026 04:29:01 -0400
Subject: [PATCH 7/7] add country and county
---
app/models/person.rb | 15 ++++++++-------
app/views/people/_search_boxes.html.erb | 2 +-
spec/models/person_spec.rb | 6 +++---
3 files changed, 12 insertions(+), 11 deletions(-)
diff --git a/app/models/person.rb b/app/models/person.rb
index 407121cee7..466222c046 100644
--- a/app/models/person.rb
+++ b/app/models/person.rb
@@ -215,15 +215,16 @@ def known_email_2_type? = email_2_type.in?(CONTACT_TYPES)
search_scope :search_contact do
scope { left_joins(:contact_methods, :addresses) }
- attributes contact_methods_phone: "contact_methods.value"
- attributes address_street: "addresses.street_address"
- attributes address_city: "addresses.city"
- attributes address_state: "addresses.state"
- attributes address_zip: "addresses.zip_code"
- attributes address_phone: "addresses.phone"
+ attributes phone: "contact_methods.value"
+ attributes street: "addresses.street_address"
+ attributes city: "addresses.city"
+ attributes state: "addresses.state"
+ attributes zip: "addresses.zip_code"
+ attributes county: "addresses.county"
+ attributes country: "addresses.country"
attributes all: [ "contact_methods.value",
"addresses.street_address", "addresses.city", "addresses.state",
- "addresses.zip_code", "addresses.phone" ]
+ "addresses.zip_code", "addresses.county", "addresses.country" ]
options :all, type: :text, default: true, default_operator: :or
end
diff --git a/app/views/people/_search_boxes.html.erb b/app/views/people/_search_boxes.html.erb
index 03a845881a..59fca28040 100644
--- a/app/views/people/_search_boxes.html.erb
+++ b/app/views/people/_search_boxes.html.erb
@@ -74,7 +74,7 @@
<%= render "shared/filter_text", param: :contact_info, label: "Contact info", submit: true,
wrapper_class: "w-full",
- tooltip: "Searches phone, address, city, state, and ZIP." %>
+ tooltip: "Prefixes: phone:, street:, city:, state:, zip:, county:, country:" %>