With the introduction of composite primary keys, a common usecase is querying for records with tuples representing the composite key. This change introduces new syntax to the where clause that allows specifying an array of columns mapped to a list of corresponding tuples. It converts this to an OR-joined list of separate queries, similar to previous implementations that rely on grouping queries.
Given a model with a composite primary key, for example:
`TravelRoute.primary_key = [:from, :to]`
and two objects of the given model, objects `a` and `b`, should be
equal to each other and have the same `hash` value if they have the same
values for the composite primary key, like:
```ruby
a = TravelRoute.new(from: "NYC", to: "LAX")
b = TravelRoute.new(from: "NYC", to: "LAX")
a == b # => true
a.hash == b.hash # => true
```
At the same time, two objects of the given model should not be equal to
each other and should have different `hash` values if they have
different primary key values or no primary key being set, like:
```ruby
a = TravelRoute.new(from: "NYC", to: "LAX")
b = TravelRoute.new(from: "NYC", to: "SFO")
a == b # => false
a.hash == b.hash # => false
TravelRoute.new == TravelRoute.new # => false
TravelRoute.new.hash == TravelRoute.new.hash # => false
```
Given a `has_many :through` association with `query_constraints`:
```ruby
BlogPost.has_many :posts_tags
BlogPost.has_many :tags,
through: :posts_tags,
query_constraints: [:blog_id, :post_id]
``
It is possible to nullify the association like `blog_post.tags = []`
which results in deletion of records from the `posts_tags` joining table.
In https://github.com/rails/rails/pull/46690 the `db_warnings_action` and `db_warnings_ignore` configs where added. The
`db_warnings_ignore` can take a list of warning messages to match against.
At GitHub we have a subscriber that that does something like this but also filters out error codes. There might also be
other applications that filter via error codes and this could be something they can use instead of just the explicit
messages.
This also refactors the adapter tests in order for mysql2 and postgresql adapters to share the same helper when setting
the db_warnings_action and db_warnings_ignore configs
Given an association defined with composite query constraints like:
```ruby
BlogPost.has_many :comments, query_constraints: [:blog_id, :blog_post_id]
```
it is possible to query blog posts by whole `comments` objects like:
```ruby
comments = Comment.first(2)
BlogPost.where(comments: comments).to_a
```
Initial implementation falls back to `primary_key.is_a?(Array)` so
it can be immediately used in place of direct `is_a?` checks.
Though implementation may be changed to rely on a pre-initialized
`@composite_primary_key` ivar in the future.
`ActiveRecord::FinderMethods#find` now supports passing sets of
composite primary key values like:
```ruby
Cpk::Book.find([1, 1])
Cpk::Book.find([[1, 1]])
Cpk::Book.find([1, 1], [1, 2])
Cpk::Book.find([[1, 1], [1, 2]])
```
and treats values as values of the composite primary key columns but
only for models with the `primary_key` being an `Array`.
The codepaths related to destroying associations asynchronously now
consider when query constraints are present. In most cases, this means
interpreting the primary key as an array of columns, and identifying
associated records by a tuple of these columns, where previously this
would've been a single ID. In each of the callsites, we use branching.
This is done to maintain backwards compatibility and ensure the
signature of the destroy job remains stable: it has consumers outside of
Rails.
Returning `self` and `db_config` from `resolve_config_for_connection` is
hold over from legacy behavior when we returned `self.name` or
`Base.name` rather than just `self`. We can simplify this by passing
`self` directly` to the handlers `establish_connection`.
By default, exclude constraints in PostgreSQL are checked after each statement.
This works for most use cases, but becomes a major limitation when replacing
records with overlapping ranges by using multiple statements.
```ruby
exclusion_constraint :users, "daterange(valid_from, valid_to) WITH &&", deferrable: :immediate
```
Passing `deferrable: :immediate` checks constraint after each statement,
but allows manually deferring the check using `SET CONSTRAINTS ALL DEFERRED`
within a transaction. This will cause the excludes to be checked after the transaction.
It's also possible to change the default behavior from an immediate check
(after the statement), to a deferred check (after the transaction):
```ruby
exclusion_constraint :users, "daterange(valid_from, valid_to) WITH &&", deferrable: :deferred
```
*Hiroyuki Ishii*
Given a model with a composite primary key, the `query_constraints_list`
should equal the `primary_key` value to enable capabilities managed by
`query_constraints_list` such as `destroy`, `update`, `delete` and others.