This change allows for applications to optionally run a `default_scope`
on `update` and `delete` queries. Default scopes already ran on select
and insert queries.
Applications can now run a set default scope on all queries for a model
by setting a `all_queries` option:
```ruby
class Article < ApplicationRecord
default_scope -> { where(blog_id: 1) }, all_queries: true
end
```
Using the default scope in this way is useful for applications that need
to query by more than the primary key by default. An example of this
would be in an application using a sharding strategy like Vitess like.
For Rails sharding, we route connections first and then query the
database. However, Vitess and other solutions use a parameter in the
query to figure out how to route the queries. By extending
`default_scope` to apply to all queries we can allow applications to
optionally apply additional constraints to all queries. Note that this
only works with `where` queries as it does not make sense to select a
record by primary key with an order. With this change we're allowing
apps to select with a primary key and an additional key.
To make this change dynamic for routing queries in a tenant sharding
strategy applications can use the `Current` API or parameters in a
request to route queries:
```ruby
class Article < ApplicationRecord
default_scope -> { where(blog_id: Current.blog.id) }, all_queries: true
end
```
In order to achieve this I created a new object when default scopes are
created. This allows us to store both the scope itself and whether we
should run this on all queries. I chose not to implement an `on:` option
that takes an array of actions because there is no simple or clear way
to turn off the default scope for create/select. It also doesn't really
make sense to only have a default scope for delete queries. The decision
to use `all_queries` here allows for the implementation to be more
flexible than it was without creating a mess in an application.
Merged
eileencodes added a commit to eileencodes/rails that referenced this pull request
Feb 11, 2021Similar to rails#40720 and rails#40805 this change allows for the `scoping` method to apply to all queries in the block. Previously this would only apply to queries on the class and not the instance. Ie `Post.create`, Post.all`, but not `post.update`, or `post.delete`. The change here will create a global scope that is applied to all queries for a relation for the duration of the block. Benefits: This change allows applications to add a scope to any query for the duration of a block. This is useful for applications using sharding to be able to control the query without requiring a `default_scope`. This is useful if you want to have more control over when a `scoping` is used on a relation. This also brings `scoping` in parity with the behavior of `default_scope` so there are less surprises between the behavior of these two methods. There are a caveats to this behavior: 1) The `scoping` only applies to objects of the same type. IE you cannot scope `Post.where(blog_id: 1).scoping` and then expect `post.comments` will apply `blog_id = 1` to the `Comment` query. This is not possible because the scope is `posts.blog_id = 1` and we can't apply the `posts` scope to a `comments` query. To solve this, scopes must be nested. 2) If a block is scoped to `all_queries` it cannot be unscoped without exiting the block. I couldn't find a way around this but ActiveRecord scoping is a bit complex and turning off `all_queries` when it's already on in nested scoping blocks had interesting behavior that I decided was best left out.
Open