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
While debugging a sentry error for "ActiveRecord::InvalidForeignKey ActiveStorage::Representations::RedirectController", it was noticed that we enqueue a Avatar::AvatarFromUrlJob for each setUser call, which is unnecessary. Hence making this call only if the contact doesn't have an existing avatar.
If one needs to have this avatar updated, they can go to the contacts tab and delete the current avatar, Chatwoot will pick up the new avatar in subsequent API call.
fixes: #3853
- Introduced DISABLE_GRAVATAR Global Config, which will stop chatwoot from making API requests to gravatar
- Cleaned up avatar-related logic and centralized it into the avatarable concern
- Added specs for the missing cases
- Added migration for existing installations to move the avatar to attachment, rather than making the API that results in 404.
When an agent updates a contact email in the dashboard and the contact also update his email via the email collect box,
Merge action receives the same base and mergee contacts which could cause data corruption.