Skip to content

Commit a7f49d2

Browse files
authored
Format UK school postcodes (#397)
**Context** UK postcode data is used in our reporting tools in order to measure how effective we are at reaching deprived areas (using the IMD / Indices of Multiple Deprivation: https://www.gov.uk/government/collections/english-indices-of-deprivation). This is a key part our programme impact monitoring work. The IMD lookup relies on a correctly formatted postcode as defined by the NSPL (National Statistics Postcode Lookup) [data](https://geoportal.statistics.gov.uk/datasets/9ac0331178b0435e839f62f41cc61c16): 2-4 char outward code, a space, then a 3-char inward code. **Problem** Postcode validation (as in "is this an actual UK postcode that is definitely in the Royal Mail Postcode Address File / PAF") is suboptimal, as doing so with a regex is a pain and potentially error prone (with the onus to 'fix' a potentially valid postcode being put on the user). We're also currently discounting using an API to look up / validate postcodes, though this may be a valid approach going forwards. **Proposed approach** Whilst we don't want to take a heavy-handed approach to postcode validation, as outlined above, we do want to make sure that any postcode that meets the criteria of being "very likely to be in the PAF and NSPL data sets" is formatted in a way which makes lookups possible (in order to prevent cases such as `CB11NT`, `CB 11NT`): * Take any >=5 char UK postcode and ensure it has one space in it before the final 3 chars / inward code. **Additional steps** Existing school data will need to have the same transform / corrections applied, something like: ``` UPDATE schools SET postal_code = CASE WHEN LENGTH(postal_code) >= 5 AND POSITION(' ' IN postal_code) = 0 THEN CONCAT(SUBSTRING(postal_code FROM 1 FOR LENGTH(postal_code) - 3), ' ', SUBSTRING(postal_code FROM LENGTH(postal_code) - 2)) ELSE postal_code END WHERE country_code = 'GB'; ```
2 parents c99fb09 + 711c6d2 commit a7f49d2

File tree

2 files changed

+62
-0
lines changed

2 files changed

+62
-0
lines changed

app/models/school.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class School < ApplicationRecord
3030

3131
before_validation :normalize_reference
3232

33+
before_save :format_uk_postal_code, if: :should_format_uk_postal_code?
34+
3335
def self.find_for_user!(user)
3436
school = Role.find_by(user_id: user.id)&.school || find_by(creator_id: user.id)
3537
raise ActiveRecord::RecordNotFound unless school
@@ -65,6 +67,10 @@ def reject
6567
update(rejected_at: Time.zone.now)
6668
end
6769

70+
def postal_code=(str)
71+
super(str.to_s.upcase)
72+
end
73+
6874
private
6975

7076
# Ensure the reference is nil, not an empty string
@@ -83,4 +89,15 @@ def rejected_at_cannot_be_changed
8389
def code_cannot_be_changed
8490
errors.add(:code, 'cannot be changed after verification') if code_was.present? && code_changed?
8591
end
92+
93+
def should_format_uk_postal_code?
94+
country_code == 'GB' && postal_code.to_s.length >= 5
95+
end
96+
97+
def format_uk_postal_code
98+
cleaned_postal_code = postal_code.delete(' ')
99+
# insert a space as the third-from-last character in the postcode, eg. SW1A1AA -> SW1A 1AA
100+
# ensures UK postcodes are always formatted correctly (as the inward code is always 3 chars long)
101+
self.postal_code = "#{cleaned_postal_code[0..-4]} #{cleaned_postal_code[-3..]}"
102+
end
86103
end

spec/models/school_spec.rb

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,51 @@
331331
end
332332
end
333333

334+
describe '#format_uk_postal_code' do
335+
it 'retains correctly formatted UK postal_code' do
336+
school.country_code = 'GB'
337+
school.postal_code = 'SW1A 1AA'
338+
school.save
339+
expect(school.postal_code).to eq('SW1A 1AA')
340+
end
341+
342+
it 'corrects incorrectly formatted UK postal_code' do
343+
school.country_code = 'GB'
344+
school.postal_code = 'SW1 A1AA'
345+
expect { school.save }.to change(school, :postal_code).to('SW1A 1AA')
346+
end
347+
348+
it 'formats UK postal_code with 4 char outcode' do
349+
school.country_code = 'GB'
350+
school.postal_code = 'SW1A1AA'
351+
expect { school.save }.to change(school, :postal_code).to('SW1A 1AA')
352+
end
353+
354+
it 'formats UK postal_code with 3 char outcode' do
355+
school.country_code = 'GB'
356+
school.postal_code = 'SW11AA'
357+
expect { school.save }.to change(school, :postal_code).to('SW1 1AA')
358+
end
359+
360+
it 'formats UK postal_code with 2 char outcode' do
361+
school.country_code = 'GB'
362+
school.postal_code = 'SW1AA'
363+
expect { school.save }.to change(school, :postal_code).to('SW 1AA')
364+
end
365+
366+
it 'does not format UK postal_code for short / invalid codes' do
367+
school.country_code = 'GB'
368+
school.postal_code = 'SW1A'
369+
expect { school.save }.not_to change(school, :postal_code)
370+
end
371+
372+
it 'does not format postal_code for non-UK countries' do
373+
school.country_code = 'FR'
374+
school.postal_code = '123456'
375+
expect { school.save }.not_to change(school, :postal_code)
376+
end
377+
end
378+
334379
describe '#reject' do
335380
it 'sets rejected_at to the current time' do
336381
school.reject

0 commit comments

Comments
 (0)