citusdata / citusdata/activerecord-multi-tenant
Bug: Arel.sql in update_all becomes a literal value inside MultiTenant.with
- Dominant language
- Ruby
- Stars
- 759
- Forks
- 113
- PR merge metrics
- No merged PRs in 30d
Description
## Affected versions / refs
* Gem: [activerecord-multi-tenant](https://github.com/citusdata/activerecord-multi-tenant)
* Commit tested for Rails 7.2 support: [0ac43aa](https://github.com/citusdata/activerecord-multi-tenant/commit/0ac43aa98334a29ed3e5b33e0bcc5ee281f97254)
* Rails 8 work is also affected (see Support rails 8.0.0)
* Note: latest release (2.4.0) still declares support only up to Rails 7.0, so people may be pinning to these commits.
## Summary
When calling relation.update_all with an assignment that uses Arel.sql(column_name) to reference another column, it works correctly outside a tenant block.
Inside MultiTenant.with(...), the Arel.sql value is coerced to a literal (e.g. 1 for booleans), so every row gets the same constant instead of the column value.
## Expected behavior
Arel.sql("active") should be treated as a SQL expression and compiled as:
```
UPDATE "users" SET "user_valid" = "users"."active", "active" = ? [["active", 0]]
WHERE "users"."practice_id" = 1;
```
## Actual behavior
Within MultiTenant.with, the assignment is type-cast/quoted as a literal:
```
UPDATE "users"
SET "user_valid" = 1, "active" = 0
WHERE "users"."id" IN (
SELECT "users"."id" FROM "users" WHERE "users"."practice_id" = 1
) AND "users"."practice_id" = 1;
```
## Reproduction
Schema
```
# users: id, practice_id:bigint, user_valid:boolean, active:boolean, timestamps
class User < ApplicationRecord
multi_tenant :practice
end
```
Repo script:
```
users = User.all
# Baseline: OUTSIDE tenant
users.update_all(user_valid: Arel.sql("active"), active: false)
# => UPDATE "users" SET "user_valid" = (active), "active" = 0
practice = Practice.first
MultiTenant.with(practice) do
users = User.all
users.update_all(user_valid: Arel.sql("active"), active: false)
end
# => UPDATE "users" SET "user_valid" = 1, "active" = 0 ...
```
## Why this likely happens
Active Record normally treats Arel::Nodes::SqlLiteral as “don’t quote/type-cast this.”
Inside the gem’s tenant scoping path, update_all seems to flow through a code path that type-casts the update hash, converting the Arel.sql(...) node into a bound literal.
## Environment
* Rails: 7.2.x (also visible with Rails 8 prerelease)
* DB: PostgreSQL (likely adapter-agnostic)
* activerecord-multi-tenant: GitHub ref 0ac43aa
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.