44# Database name: primary
55#
66# id :bigint not null, primary key
7+ # alertable_type :string
78# dismissed_at :datetime
89# kind :integer
910# message :text
1011# resolved_at :datetime
1112# created_at :datetime not null
1213# updated_at :datetime not null
14+ # alertable_id :bigint
1315# bike_id :bigint
1416# organization_id :bigint
1517# theft_alert_id :bigint
1820#
1921# Indexes
2022#
23+ # index_user_alerts_on_alertable (alertable_type,alertable_id)
2124# index_user_alerts_on_bike_id (bike_id)
2225# index_user_alerts_on_theft_alert_id (theft_alert_id)
2326# index_user_alerts_on_user_id (user_id)
@@ -31,17 +34,24 @@ class UserAlert < ApplicationRecord
3134 unassigned_bike_org : 3
3235 } . freeze
3336
37+ UNIQ_KINDS = %w[ phone_waiting_confirmation ] . freeze
38+
39+ # Deprecated columns, still read until Backfills::UserAlertAlertableJob has run
40+ LEGACY_ALERTABLE_COLUMNS = { "TheftAlert" => :theft_alert_id , "UserPhone" => :user_phone_id } . freeze
41+
3442 enum :kind , KIND_ENUM
3543
3644 belongs_to :user
3745 belongs_to :bike
46+ belongs_to :organization
47+ belongs_to :alertable , polymorphic : true
3848 belongs_to :user_phone
3949 belongs_to :theft_alert
40- belongs_to :organization
4150
4251 has_one :notification , as : :notifiable
4352
44- validates :user_phone_id , uniqueness : { scope : %i[ kind user_id ] } , allow_blank : true
53+ validates :alertable_id , uniqueness : { scope : %i[ alertable_type kind user_id ] } ,
54+ allow_blank : true , if : :uniq_kind?
4555
4656 before_validation :set_calculated_attributes
4757
@@ -55,6 +65,16 @@ class UserAlert < ApplicationRecord
5565 scope :account , -> { where ( kind : account_kinds ) }
5666 scope :dismissable , -> { where ( kind : dismissable_kinds ) }
5767 scope :with_notification , -> { joins ( :notification ) . where . not ( notifications : { id : nil } ) }
68+ # Also matches rows Backfills::UserAlertAlertableJob hasn't reached yet
69+ scope :for_alertable , -> ( alertable ) {
70+ next all if alertable . blank?
71+
72+ type = alertable . class . polymorphic_name
73+ matched = where ( alertable_type : type , alertable_id : alertable . id )
74+ legacy_column = LEGACY_ALERTABLE_COLUMNS [ type ]
75+
76+ legacy_column ? matched . or ( where ( legacy_column => alertable . id ) ) : matched
77+ }
5878 scope :create_notification , -> {
5979 where ( kind : notification_kinds , updated_at : notify_period )
6080 . left_joins ( :notification ) . where ( notifications : { id : nil } )
@@ -103,13 +123,13 @@ def self.placement(kind)
103123 end
104124
105125 def self . find_or_build_by ( attrs )
106- where ( attrs ) . first || new ( attrs )
126+ where ( attrs . except ( :alertable ) ) . for_alertable ( attrs [ :alertable ] ) . first || new ( attrs )
107127 end
108128
109129 def self . update_theft_alert_without_photo ( user :, theft_alert :)
110130 # scope to just active, to alert if the theft alert once again has no image
111131 user_alert = UserAlert . active . find_or_build_by ( kind : "theft_alert_without_photo" ,
112- user_id : user . id , theft_alert_id : theft_alert . id )
132+ user_id : user . id , alertable : theft_alert )
113133 if theft_alert . missing_photo?
114134 user_alert . bike_id = theft_alert . bike &.id
115135 user_alert . save
@@ -141,7 +161,7 @@ def self.update_unassigned_bike_org(user:, organization:, bike:)
141161
142162 def self . update_phone_waiting_confirmation ( user :, user_phone :)
143163 user_alert = UserAlert . find_or_build_by ( kind : "phone_waiting_confirmation" ,
144- user_id : user . id , user_phone_id : user_phone . id )
164+ user_id : user . id , alertable : user_phone )
145165 if user_phone . confirmed?
146166 # Don't create if phone is already confirmed
147167 user_alert . id . blank? || user_alert . resolve!
@@ -150,6 +170,11 @@ def self.update_phone_waiting_confirmation(user:, user_phone:)
150170 end
151171 end
152172
173+ # Falls back to the legacy columns for rows Backfills::UserAlertAlertableJob hasn't reached
174+ def alertable
175+ super || theft_alert || user_phone
176+ end
177+
153178 def kind_humanized
154179 self . class . kind_humanized ( kind )
155180 end
@@ -222,6 +247,12 @@ def email_subject
222247 end
223248 end
224249
250+ private
251+
252+ def uniq_kind?
253+ UNIQ_KINDS . include? ( kind )
254+ end
255+
225256 def set_calculated_attributes
226257 self . message = nil if message . blank?
227258 end
0 commit comments