11# frozen_string_literal: true
22
33class School < ApplicationRecord
4+ class DuplicateSchoolError < StandardError ; end
5+
46 has_many :classes , class_name : :SchoolClass , inverse_of : :school , dependent : :destroy
57 has_many :lessons , dependent : :nullify
68 has_many :projects , dependent : :nullify
@@ -15,6 +17,8 @@ class School < ApplicationRecord
1517 validates :website , presence : true , format : { with : VALID_URL_REGEX , message : I18n . t ( 'validations.school.website' ) }
1618 validates :address_line_1 , presence : true
1719 validates :municipality , presence : true
20+ validates :administrative_area , presence : true
21+ validates :postal_code , presence : true
1822 validates :country_code , presence : true , inclusion : { in : ISO3166 ::Country . codes }
1923 validates :reference , uniqueness : { case_sensitive : false , allow_nil : true } , presence : false
2024 validates :district_nces_id , uniqueness : { case_sensitive : false , allow_nil : true } , presence : false
@@ -30,8 +34,6 @@ class School < ApplicationRecord
3034 validates :verified_at , absence : { if : proc { |school | school . rejected? } }
3135 validates :code ,
3236 uniqueness : { allow_nil : true } ,
33- presence : { if : proc { |school | school . verified? } } ,
34- absence : { unless : proc { |school | school . verified? } } ,
3537 format : { with : /\d \d -\d \d -\d \d / , allow_nil : true }
3638 validate :verified_at_cannot_be_changed
3739 validate :code_cannot_be_changed
@@ -40,8 +42,11 @@ class School < ApplicationRecord
4042 before_validation :normalize_district_fields
4143 before_validation :normalize_school_roll_number
4244
45+ before_save :prevent_duplicate_school
4346 before_save :format_uk_postal_code , if : :should_format_uk_postal_code?
4447
48+ after_commit :generate_code! , on : :create , if : -> { FeatureFlags . immediate_school_onboarding? }
49+
4550 def self . find_for_user! ( user )
4651 school = Role . find_by ( user_id : user . id ) &.school || find_by ( creator_id : user . id )
4752 raise ActiveRecord ::RecordNotFound unless school
@@ -62,9 +67,18 @@ def rejected?
6267 end
6368
6469 def verify!
70+ generate_code! if ENV [ 'ENABLE_IMMEDIATE_SCHOOL_ONBOARDING' ] . blank?
71+
72+ update! ( verified_at : Time . zone . now )
73+ end
74+
75+ def generate_code!
76+ return code if code . present?
77+
6578 attempts = 0
6679 begin
67- update! ( verified_at : Time . zone . now , code : ForEducationCodeGenerator . generate )
80+ new_code = ForEducationCodeGenerator . generate
81+ update! ( code : new_code )
6882 rescue ActiveRecord ::RecordInvalid => e
6983 raise unless e . record . errors [ :code ] . include? ( 'has already been taken' ) && attempts <= 5
7084
@@ -78,6 +92,8 @@ def reject
7892 end
7993
8094 def reopen
95+ return false unless rejected?
96+
8197 update ( rejected_at : nil )
8298 end
8399
@@ -122,7 +138,7 @@ def rejected_at_cannot_be_changed
122138 end
123139
124140 def code_cannot_be_changed
125- errors . add ( :code , 'cannot be changed after verification ' ) if code_was . present? && code_changed?
141+ errors . add ( :code , 'cannot be changed after onboarding ' ) if code_was . present? && code_changed?
126142 end
127143
128144 def should_format_uk_postal_code?
@@ -139,4 +155,29 @@ def format_uk_postal_code
139155 # ensures UK postcodes are always formatted correctly (as the inward code is always 3 chars long)
140156 self . postal_code = "#{ cleaned_postal_code [ 0 ..-4 ] } #{ cleaned_postal_code [ -3 ..] } "
141157 end
158+
159+ def prevent_duplicate_school
160+ name_threshold = 0.6
161+ municipality_threshold = 0.7
162+ postal_code_threshold = 0.8
163+ max_edit_distance = 3
164+
165+ normalized_postal = postal_code . to_s . gsub ( /\s +/ , '' )
166+
167+ duplicate_school = School
168+ . where . not ( id :)
169+ . where ( country_code :)
170+ . where (
171+ 'similarity(lower(name), ?) >= ? AND levenshtein(lower(name), ?) <= ?' ,
172+ name . to_s , name_threshold , name . to_s , max_edit_distance
173+ )
174+ . where ( 'similarity(lower(municipality), ?) >= ?' , municipality . to_s , municipality_threshold )
175+ . where (
176+ "similarity(lower(replace(postal_code, ' ', '')), ?) >= ?" ,
177+ normalized_postal , postal_code_threshold
178+ )
179+ . first
180+
181+ raise DuplicateSchoolError , I18n . t ( 'validations.school.duplicate_school' ) if duplicate_school
182+ end
142183end
0 commit comments