diff --git a/app/models/contact.rb b/app/models/contact.rb index 83920d9fc..4d7b37c44 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -12,6 +12,7 @@ # custom_attributes :jsonb # email :string # identifier :string +# is_verified :boolean default(FALSE), not null # last_activity_at :datetime # last_name :string default("") # location :string default("") @@ -25,8 +26,10 @@ # Indexes # # index_contacts_on_account_id (account_id) +# index_contacts_on_account_id_and_is_verified (account_id,is_verified) # index_contacts_on_account_id_and_last_activity_at (account_id,last_activity_at DESC NULLS LAST) # index_contacts_on_blocked (blocked) +# index_contacts_on_is_verified (is_verified) # index_contacts_on_lower_email_account_id (lower((email)::text), account_id) # index_contacts_on_name_email_phone_number_identifier (name,email,phone_number,identifier) USING gin # index_contacts_on_nonempty_fields (account_id,email,phone_number,identifier) WHERE (((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text)) @@ -63,7 +66,7 @@ class Contact < ApplicationRecord after_create_commit :dispatch_create_event, :ip_lookup after_update_commit :dispatch_update_event after_destroy_commit :dispatch_destroy_event - before_save :sync_contact_attributes + before_save :sync_contact_attributes, :update_is_verified enum contact_type: { visitor: 0, lead: 1, customer: 2 } @@ -176,7 +179,7 @@ class Contact < ApplicationRecord end def self.resolved_contacts - where("contacts.email <> '' OR contacts.phone_number <> '' OR contacts.identifier <> ''") + where(is_verified: true) end def discard_invalid_attrs @@ -238,4 +241,11 @@ class Contact < ApplicationRecord def dispatch_destroy_event Rails.configuration.dispatcher.dispatch(CONTACT_DELETED, Time.zone.now, contact: self) end + + def update_is_verified + self.is_verified = email.present? || + phone_number.present? || + identifier.present? || + (additional_attributes.present? && additional_attributes['company_name'].present?) + end end diff --git a/db/migrate/20250722093235_add_is_verified_to_contacts.rb b/db/migrate/20250722093235_add_is_verified_to_contacts.rb new file mode 100644 index 000000000..7c6203813 --- /dev/null +++ b/db/migrate/20250722093235_add_is_verified_to_contacts.rb @@ -0,0 +1,7 @@ +class AddIsVerifiedToContacts < ActiveRecord::Migration[7.1] + def change + add_column :contacts, :is_verified, :boolean, default: false, null: false + add_index :contacts, :is_verified + add_index :contacts, [:account_id, :is_verified], name: 'index_contacts_on_account_id_and_is_verified' + end +end diff --git a/db/schema.rb b/db/schema.rb index 34637315b..b1fa5c7ae 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_07_14_104358) do +ActiveRecord::Schema[7.1].define(version: 2025_07_22_093235) do # These extensions should be enabled to support this database enable_extension "pg_stat_statements" enable_extension "pg_trgm" @@ -538,14 +538,17 @@ ActiveRecord::Schema[7.1].define(version: 2025_07_14_104358) do t.string "location", default: "" t.string "country_code", default: "" t.boolean "blocked", default: false, null: false + t.boolean "is_verified", default: false, null: false t.index "lower((email)::text), account_id", name: "index_contacts_on_lower_email_account_id" t.index ["account_id", "email", "phone_number", "identifier"], name: "index_contacts_on_nonempty_fields", where: "(((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text))" + t.index ["account_id", "is_verified"], name: "index_contacts_on_account_id_and_is_verified" t.index ["account_id", "last_activity_at"], name: "index_contacts_on_account_id_and_last_activity_at", order: { last_activity_at: "DESC NULLS LAST" } t.index ["account_id"], name: "index_contacts_on_account_id" t.index ["account_id"], name: "index_resolved_contact_account_id", where: "(((email)::text <> ''::text) OR ((phone_number)::text <> ''::text) OR ((identifier)::text <> ''::text))" t.index ["blocked"], name: "index_contacts_on_blocked" t.index ["email", "account_id"], name: "uniq_email_per_account_contact", unique: true t.index ["identifier", "account_id"], name: "uniq_identifier_per_account_contact", unique: true + t.index ["is_verified"], name: "index_contacts_on_is_verified" t.index ["name", "email", "phone_number", "identifier"], name: "index_contacts_on_name_email_phone_number_identifier", opclass: :gin_trgm_ops, using: :gin t.index ["phone_number", "account_id"], name: "index_contacts_on_phone_number_and_account_id" end diff --git a/lib/tasks/populate_contact_verification.rake b/lib/tasks/populate_contact_verification.rake new file mode 100644 index 000000000..ead410c98 --- /dev/null +++ b/lib/tasks/populate_contact_verification.rake @@ -0,0 +1,27 @@ +namespace :contacts do + desc 'Populate is_verified column for existing contacts based on verification criteria' + task populate_verification: :environment do + total_contacts = Contact.count + puts "Total contacts to process: #{total_contacts}" + + batch_size = 1000 + updated_count = 0 + + Contact.find_each(batch_size: batch_size) do |contact| + # Check if contact should be verified using the same logic as model + should_be_verified = contact.email.present? || + contact.phone_number.present? || + contact.identifier.present? || + (contact.additional_attributes.present? && contact.additional_attributes['company_name'].present?) + + # Update only if current value is different (avoid unnecessary updates) + if contact.is_verified != should_be_verified + # Using update_columns to avoid callbacks and validations for bulk operation + contact.update_columns(is_verified: should_be_verified) # rubocop:disable Rails/SkipsModelValidations + updated_count += 1 + end + # Progress indicator + puts "Updated #{updated_count} contacts..." if (updated_count % 100).zero? + end + end +end