Associations Deep-Dive: hasMany, belongsTo, Nested Properties, and Eager Loading #3274
bpamiri
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Read: https://blog.wheels.dev/posts/associations-deep-dive-wheels-4
You build the blog, and then the requests start coming. The author page needs every post that author wrote. The post page needs every comment, but only the ones belonging to that post. Deleting an author shouldn't orphan a hundred posts pointing at a dead foreign key. The admin form edits a post and adds three comments in one submit. The index renders 50 posts with their author's name — and you don't want 51 queries to do it.
Every one of those is a join, a foreign key, and a lifecycle rule. Associations are the part of the ORM that turns all of it into declarations you write once in
config(). This post is the comprehensive 4.0 walkthrough.What's in it
The three types.
belongsTo(this model holds the FK),hasMany(FK on the other table, many rows), andhasOne(FK on the other table, exactly one) — plus how Wheels deducesmodelName,foreignKey, andjoinKeyfrom a singlenameargument, and the named-argument syntax for overriding them.The exact dynamic methods each generates. This is the part people guess at. A
belongsTogives you two methods; ahasOne, seven; ahasMany, eleven. The post has a table per type —author.posts(),author.postCount(),author.hasPosts(),author.addPost(),author.removeAllPosts(),author.deleteAllPosts()— with the naming rule that decides singular vs. plural verbs.Cascading deletes with
dependent=. The five legal values aredelete,deleteAll,remove,removeAll, andfalse. Two things worth flagging up front for anyone coming from another ORM:nullifyvalue. FK-nulling isremove(instantiated, runs callbacks) orremoveAll(bulk, no callbacks). Anything outside the five throwsWheels.InvalidArgumentwhen the parent is deleted and the cascade runs.belongsTotakes nodependentargument — it doesn't own the related records.Nested properties. Save a parent and its children from one
paramsstruct with one.save().autoSavedefaults totrue(children save automatically);allowDeletedefaults tofalse(opt in before_deleteworks). Must be declared after the association it names, or you getWheels.AssociationNotFound.Eager loading with
include=. Collapse the N+1 into a single JOIN. Comma-list direct associations; use parentheses for nested includes (include="artist(genre)") — but those are query-only.findOneoptimizes tomaxRows=1unless you include ahasMany.has-many-through for many-to-many via
shortcut(+throughwhen names diverge), and polymorphic associations (as=on the parent,polymorphic=trueon the child) — including the hard rule that a polymorphicbelongsTocannot be eager-loaded (Wheels.PolymorphicIncludeNotSupported); you resolve it per row through the dynamic getter.The sharp edges (the part worth arguing about)
The post closes with a Sharp Edges section built from real, cited behavior:
<cfloop query="posts">, notarray=.belongsTousesINNER JOIN,hasMany/hasOneuseLEFT OUTER JOIN. Including abelongsTowith the default join silently drops parent rows that have no matching child. PassjoinType="outer"if a post might have no author and you still want the post.Discussion
A few things I'd genuinely like to hear from people on:
returnAs="objects"in associations enough that the default feels wrong?dependent=defaults. It defaults tofalse(do nothing) — which is the safe-but-surprising choice. Do you wish a generated model wireddependent="delete"by convention, or is silent-no-cascade the right default?If the post is missing a scenario you hit in production, drop it below — happy to fold real cases into a follow-up.
Beta Was this translation helpful? Give feedback.
All reactions