Introduce decoupled query_constraints for associations#51
Open
nvasilevski wants to merge 2 commits into
Open
Conversation
Comment on lines
+532
to
+533
| # If the foreign key is an array, set query constraints options | ||
| if options[:foreign_key].is_a?(Array) && !options[:query_constraints] |
There was a problem hiding this comment.
If the idea is that we're separating the two concepts, do we still need to copy the value over?
27fce20 to
af51443
Compare
af51443 to
72f9579
Compare
Reintroduces query_constraints on associations, decoupled from foreign_key. When both are specified, foreign_key handles writes and query_constraints is used for querying association targets (loading and preloading).
72f9579 to
530077e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduce decoupled
query_constraintsfor associationsReintroduces
query_constraintson associations, decoupled fromforeign_key.Motivation
Today
query_constraintson abelongs_todoubles as the foreign key. Thatconflation means a sharding/tenant column added to
query_constraintsis treatedas part of the foreign key, so it gets nulled on clear and reassigned on set
(see rails#49671 and rails#57906).
This change keeps
query_constraintsquery-only and separate fromforeign_key. With the shard column expressed as additive query scope rather thanpart of the foreign key, it's excluded from nullification by construction —
there's no per-column "should I null this one?" heuristic to get right, because the
column was never one of the things clearing the association touches.
Mental model
query_constraintsis a list of additional columns to match when querying anassociation's targets (loading and preloading). They are layered on top of the
foreign key — the foreign key always participates, because an association can't be
queried without it.
foreign_keyis given → behaves exactly as today.foreign_keyandquery_constraintsare given →foreign_keyhandleswrites, and querying matches on
foreign_key+ the extra columns.query_constraintsis allowed, not rejected — itis de-duplicated.
Nullify / assign behavior: clearing an association nulls only the
foreign_key;query_constraintscolumns are never written or nulled, because theyscope the owner, not point at the target.
The common case: the foreign key stays conventional
Because
query_constraintsno longer redefines the foreign key, the foreign key stayscompletely traditional and can just be derived. In the common case you don't spell it
out at all:
query_constraintsbecomes a small additive layer on top of a conventional foreignkey, rather than something that replaces it.
Two sides of a constraint (advanced)
A column can have a different name on each side.
query_constraintsaccepts symbols(same name on both sides) and hashes (
self_column => target_column):Resulting join keys:
BlogPost) columns:["blog_id", "id", "featured_comment_id"]Comment) columns:["blog_id", "blog_post_id", "id"]A Hash mapping requires an explicit
foreign_key(an FK can't be derived from arenamed pair) — otherwise an
ArgumentErroris raised.Implementation
query_constraints_foreign_key,normalized_query_constraints_mapping, andjoin_query_constraints_{primary,foreign}_key/join_query_constraints_id_for.Join (
join_scope,AssociationScope) and preload paths resolve keys throughthese, falling back to the existing
join_*behavior when noquery_constraintsare present.
active_record_primary_key(and autosave's write key) ignorequery_constraintswhen an explicit
foreign_keyis present, so writes and.joins()keep using thescalar PK/FK.
ThroughReflection/PolymorphicReflection/RuntimeReflectiondelegate the newmethods in lockstep with their
join_*counterparts.Backward compatibility
Old-style
query_constraints(a plain list of FK columns, no explicitforeign_key)keeps its previous meaning: the columns become the foreign key and
normalized_query_constraints_mappingreturnsnil.