Repository navigation
Conversation
| .where(addresses: { inactive: false, state: state }) | ||
| .distinct | ||
| # Where the registrant works on the registration (RegistrationWorkAddresses). | ||
| scope :work_state, ->(state) { |
There was a problem hiding this comment.
🤖 From Claude: These scopes pluck the current relation's ids and resolve locations in Ruby, so the work-else-home rule lives only in RegistrationLocations. Fine at one event's scale; worth knowing if they get used on a large cross-event relation.
Keeps this PR to the org-answer refactor so each reviews on its own. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
| # otherwise match a city from one and the state from the other. County accepts | ||
| # "STATE|County" (from the roster filter) to scope to a county within a specific | ||
| # state, or a bare county name for backward compatibility; a county state that | ||
| # contradicts the State filter can match nothing. |
There was a problem hiding this comment.
🤖 From Claude: City, state and county deliberately arrive in one call rather than as chainable scopes: chained, each one independently matched any of the registration's location rows, so a registrant registered under orgs in Portland OR and Seattle WA matched City=Seattle + State=OR (the old shared addresses join ruled that out). A County value whose state contradicts the State filter resolves to none for the same reason.
* Build org-answer entries in one place The org name/position/website/type/address answers were shaped into the same hash in four places, so every new org field (like country) had to be added to each one and they could drift. FormSubmission.org_entry_from now builds it, and ProfileDiff.for_entry reads it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Count org-entry identifiers as read by the submission pipelines CaptureFromSubmission now reads org answers through FormSubmission::ORG_ENTRY_FIELD_IDENTIFIERS rather than literal field_value calls, so the coverage spec's scrape missed them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Chart registrant locations by where they work Admins want the background charts to show where registrants work, not where they live. Cities, states, and countries now read the org linked on the registration, at the address picked on that registration's affiliation, else the org's oldest address. The drill-ins follow so a clicked row lists the same people; the roster's State/County filters keep matching home addresses. Also builds org_entry_from off the field-map constants so the identifier list and the hash can't drift. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Filter registrants by where they work The location filters should mean the same thing as the location charts. The roster, bulk-email picker, and attendees State/County/City filters now match the registration's work address (RegistrationWorkAddresses), so the attendees States drill-in can reuse the state param. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Move the work-location charts and filters to #2844 Keeps this PR to the org-answer refactor so each reviews on its own. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Read the org fields in one query, and keep the coverage guard honest Building the entry up front meant the form-params path looked up each org field on its own, so a form with no org section paid nine queries for answers it never had. One batched lookup covers them all. The identifier-coverage guard counted the appended constants too, which left it passing on a scan that had stopped matching most of the pipeline — it now counts only what it scanned. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Admins want the location charts and the State/County/City filters to show where registrants work, not where they live. Both now read the org linked on the registration, at the address picked on that registration's affiliation, else the org's oldest address (RegistrationWorkAddresses). Drill-ins follow so a clicked row lists the same people. Moved out of #2842 so it reviews separately from the org-answer refactor. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A registrant with no linked org (or an org with no address) dropped out of the location filters entirely; counting them where they live keeps them findable. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With the fallback to a registrant's own address, "work" no longer describes the whole rule: RegistrationWorkAddresses -> RegistrationLocations and work_* scopes -> located_in_*. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The three chained location scopes each tested any of a registration's addresses, so a registrant working in two states matched a city from one and the state from the other — the shared addresses join used to rule that out. One `located_in(state:, county:, city:)` call makes the invariant structural, and the reminder picker's City search now runs through it instead of matching rows the State and County dropdowns had already filtered separately. RegistrationLocations also takes a relation now. The attendees index built its filter options by plucking every reportable registration id and feeding the array back as IN lists across five queries; as subqueries that stays one statement no matter how large the population. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review of the work-location branch turned up three things. The attendees index resolved County in a second RegistrationLocations pass, so City and County could land on different addresses — the exact case located_in was built to prevent, and the one the feature entry promises is impossible. Its State and County dropdowns were filled by building a location row for every registration in the viewer's whole reportable population, where two grouped queries over the address tables answer the same question. And rows_by_registration had no callers. Rows now carry every field an address uses to say where it is — everything but the street and phone — so the next chart, count or filter asks for a field name rather than another method: registrant_ids_by(:country) replaces a hand-written country map, and locality, zip, school district and the LA districts come along for free. matching takes the whole filter set in one call, and filters_from folds a County option's own state into it so the roster scope and the index controller stop keeping separate copies of that rule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
27478d7 to
c1e6a7e
Compare
…dexes The attendees page searched contact info through the old catch-all `search` scope: no prefixes, and no county or country. It now uses the box Justin built for the people index, so "city: austin" means the same thing on both pages and a prefix added once shows up in both. Its main box becomes Name or email to match, and the prefixed box sits in the drawer behind the same super-admin gate the people index uses. The prefix map now lives in ContactSearchable rather than in Person, and the help text beside each box is built from it, so the two can't drift. Locality and school district join the list: an address says where someone is with every field but the street, and a search that stops short of one is a search someone retries by hand. Labelled "Their own contact info" on the attendees page on purpose — the City, State and County filters beside it read where a registrant works, and the two answering different questions is worth saying out loud rather than discovering. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An event owner reaches this page without being an admin, and the box reads a person's own address, so assert it isn't rendered for them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three things, all about saying the same thing the same way twice. Address.ranked_for_person settles which of a person's addresses speaks for them when only one can: their active work address, else the most recently edited active personal one, else a retired personal, else a retired work. Work and personal swap places below the active line because a current work address is the sharpest thing we know and a retired one the stalest — people change jobs, home outlasts the job. RegistrationLocations uses it instead of the primary flag, and stops requiring the address to be active: a registrant with nothing but a retired address belongs on the map at that address rather than nowhere. "--" has meant "either" everywhere in this app except the two newest search boxes and the roster keyword box, which each meant something else. Now every box takes it, every box carries the same hint saying so, and the prefixed boxes still take SearchCop's own `or` on top. The roster keyword box asks its whole OR block per alternative; the SearchCop boxes rewrite "--" into `or` on the way in. A `type:` prefix narrows the contact search to a work, personal or mailing address. SearchCop ANDs the prefixes onto one joined row, so `type: personal city: austin` asks for an address that is both — one prefix instead of a parallel personal_* set, and it composes with every field there is. Person.search_by_params no longer answers contact_info: the people and attendees indexes both reach ContactSearchable through the URL param directly, so the branch was a second, older meaning for the same word. Its specs now ask the scope the URL actually hits. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…here Searching someone up now reads their live addresses only. The standing chain settles which address SHOWS a person when only one can; it is the wrong rule for finding them, because the admin holding a sticky note with their old address would search it and be told nobody lives there. Expressing the chain in a search also costs a correlated subquery per person where a plain join on an indexed key does now — slower, to answer worse. Live addresses, all of them. The chain itself moved into Address::STANDING, written once in Ruby and compiled into the ORDER BY, because a hand-kept SQL twin drifts the first time the order changes. AddressDecorator sorts its loaded association by the same list instead of its own near-copy. Several words now narrow rather than widen. "amy jones" meant everyone called either, which is not what anyone types it for; it means the one person. That leaves "--" as the single way to say "either", which is what it has always meant everywhere else in this app. The organizations index Address box joins the shared search. It kept the LA district columns, which SearchCop can match as the integers they are so long as a bare word never asks them for a LIKE — they sit in SCOPING_PREFIXES with type:. Commas become spaces on the way in so a pasted "Los Angeles, CA" still reads as two words. search_contact distincts itself rather than trusting four call sites to remember. The attendees page had two City filters meaning different things side by side. The work-location one is now a prefixed box of its own, "Where they work", read by RegistrationLocations.parse_query — which is also what lets locality, zip, district and the LA districts be filtered at all. A visible dropdown still wins over the same field typed in the box, and a bare city param keeps working as a chip so an older link narrows visibly rather than silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🤖 suggested review level: 5 Inspect 🔬 changes what every registrant location chart and filter counts, plus the search grammar in every filter box
Registrant location charts and filters now use where people work (their registered org's address), falling back to where they live. Along the way, every search box in the portal ends up speaking the same language.
Where a registrant is
RegistrationLocationsis the one source, per registration: org linked on the registration → the address picked on that registration's affiliation → the org's oldest active address → the one address that stands for the registrantAddress::STANDING— active work → latest-edited active personal → active other → inactive personal → inactive workORDER BY;AddressDecoratorsorts its loaded association by the same listFilters
located_in(**fields)/matching(**fields), so they describe one addresscity: austin locality: "Outside CA"), which is also what makes locality / zip / district / LA districts filterable at allcityparam still works, now as a removable chipOne search grammar
--"..."city:etc.locality:,district:,la_council:,la_supervisorial:,la_spa:, andtype:for work/personal/mailing — SearchCop ANDs prefixes onto one joined row, sotype: personal city: austinis how you search a personal city alonePerson.search_by_params(contact_info:)deleted — every index reachesContactSearchablefrom the URL param, so it was a second, older meaning for the same wordSplit out of #2842 (org-answer refactor). Label/doc wording follow-up in #2845.
Features seed (optional)
🤖 Generated with Claude Code