citusdata / citusdata/activerecord-multi-tenant

Bug: Arel.sql in update_all becomes a literal value inside MultiTenant.with

Open
#278 0 comments 0 reactions 0 assignees View on GitHub
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.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.