fix: Skip redundant contact saves in ContactIdentifyAction (#13778)
When the SDK sends identify calls with identical payloads (common on every page load), `save!` fires even though no attributes changed. While Rails skips the actual UPDATE SQL, it still opens a transaction, runs all callbacks (including validation queries like `Contact Exists?`), and triggers `after_commit` hooks — all for a no-op. This adds a `changed?` guard before `save!` to skip it entirely when no attributes have actually changed. **How to test** - Trigger an identify call via the SDK with a contact's existing attributes (same name, email, custom_attributes, etc.) - The contact should not fire a save (no transaction, no callbacks) - Trigger an identify call with a changed attribute — save should work normally **What changed** - `ContactIdentifyAction#update_contact`: guard `save!` with `changed?` check - Added specs to verify `save!` is skipped for unchanged params and avatar job still enqueues independently
This commit is contained in:
@@ -104,7 +104,7 @@ class ContactIdentifyAction
|
||||
# blank identifier or email will throw unique index error
|
||||
# TODO: replace reject { |_k, v| v.blank? } with compact_blank when rails is upgraded
|
||||
@contact.discard_invalid_attrs if discard_invalid_attrs
|
||||
@contact.save!
|
||||
@contact.save! if @contact.changed?
|
||||
enqueue_avatar_job
|
||||
end
|
||||
|
||||
|
||||
@@ -145,5 +145,24 @@ describe ContactIdentifyAction do
|
||||
expect(contact.phone_number).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when params have not changed' do
|
||||
it 'skips save and does not issue an UPDATE query' do
|
||||
contact.update!(name: 'test', identifier: 'test_id', custom_attributes: { test: 'test', test1: 'test1' })
|
||||
params = { name: 'test', identifier: 'test_id', custom_attributes: { test: 'test', test1: 'test1' } }
|
||||
|
||||
# any_instance is needed because merge lookup can reassign @contact to a different Ruby object
|
||||
expect_any_instance_of(Contact).not_to receive(:save!) # rubocop:disable RSpec/AnyInstance
|
||||
described_class.new(contact: contact, params: params).perform
|
||||
end
|
||||
|
||||
it 'still enqueues avatar job even when attributes have not changed' do
|
||||
contact.update!(name: 'test')
|
||||
params = { name: 'test', avatar_url: 'https://chatwoot-assets.local/sample.png' }
|
||||
|
||||
expect(Avatar::AvatarFromUrlJob).to receive(:perform_later).with(contact, params[:avatar_url]).once
|
||||
described_class.new(contact: contact, params: params).perform
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user