Interview Question
"What happens when belongs_to runs?"
The Rails interview question that separates candidates who treat the framework as magic from candidates who have read its source. What belongs_to actually generates, why it is a method call, and the depth that signals senior understanding.
What the interviewer is actually checking
Most Rails developers use belongs_to dozens of times before they think about what it does. Active Record declarations look like configuration: a special line that magically adds an association. The senior question is whether the candidate has crossed the line from "I write this and Rails handles it" to "this is Ruby code that runs at class load time and defines methods."
The reason it matters in production: when an association behaves unexpectedly (optional vs required, the foreign key column is named oddly, the inverse_of warning fires), the developer who knows what belongs_to generates can debug it in minutes. The developer who treats it as magic ends up in a four-hour rabbit hole.
The follow-ups extend the same test to has_many, enum, has_secure_password, and the rest of the macro-style declarations Rails apps rely on.
The mid-level answer
Typical mid-level response:
"belongs_to :usertells Rails that this model has a foreign key calleduser_idand that it can fetch the associated user. So if I have a Post, I can callpost.userand Rails will run a query to find the user."
This is correct at the user-facing level. What it misses is that belongs_to is itself a Ruby method, that it runs when the class is loaded, and that it defines several methods on the class. The mid-level answer treats belongs_to as a declaration; the senior answer treats it as a method call with side effects.
The senior answer
The senior response covers three points: it is a method call, what methods it generates, and what configuration it tracks for the rest of the framework.
It is a method call. "belongs_to is a class method defined in ActiveRecord::Associations::ClassMethods. When Rails loads the Post model, it evaluates the class body line by line. When it hits belongs_to :user, that calls the method on the class with :user as an argument. It is not a special declaration; it is plain Ruby."
What it generates. "The method builds a Reflection object that holds the association's metadata (the name, the foreign key, the target class, the options) and stores it on the class. Then it uses define_method to add several methods to the model:
user: returns the associated User, loading it lazily on first call, memoizing the result.user=: assigns a new User to the association, updating the foreign key.build_user: instantiates a new User without saving, linked via the foreign key.create_user: instantiates and saves a User, linked via the foreign key.create_user!: the bang version, raising on validation failure.reload_user: re-fetches the User from the database, bypassing the memoization.
Plus a validation (presence of the foreign key, default in Rails 5+, can be turned off with optional: true) and an autosave hook that saves the associated record if it has been modified."
The reflection metadata. "The Reflection object stays on the class and is used by other parts of the framework. Eager loading (includes, preload, eager_load) reads it to know what to join. The route helpers for nested resources read it. Form builders use it. Post.reflect_on_association(:user) exposes it for application code; it is the same object Rails internals read."
The follow-ups
"What is the difference between optional: true and the old required: false?" The right answer: pre-Rails 5, belongs_to was optional by default; you opted in to required with required: true. Rails 5 flipped the default: required by default; you opt out with optional: true. The old API still works but is discouraged. A senior who has upgraded a pre-Rails-5 app will mention this.
"What does inverse_of do?" The right answer: it tells Active Record that two associations on different models reference each other, so memoization is bidirectional. Without it, post.user.posts would re-query the posts even though Post is already loaded. Rails 4+ infers inverse_of automatically in most cases; explicit inverse_of is needed for non-conventional associations (custom foreign_key, class_name, scope).
"Where does the SQL come from?" The right answer walks the relevant layers. The association proxy (when you call post.user) checks if the target is loaded; if not, it builds a Relation, then Arel (Rails's SQL AST) converts the Relation to a SQL string, the database adapter sends it to Postgres. The result rows hydrate into a User object via Post::reflections[:user].build_record(...). The whole flow is in activerecord/lib/active_record/associations/ in the Rails source.
"What happens at post.user_id = 42?" The right answer: setting the foreign key directly does not change the cached user. The next call to post.user returns the old memoized User, not the user with id 42. To force a reload, call post.reload or post.user(true) (deprecated) or post.reload_user. This is one of the most common N+1-adjacent bugs in production Rails apps.
What signals what
- "It adds an association." Junior. Tautological.
- "It tells Rails about the foreign key." Mid. Mechanical but surface-level.
- "It is a method call that adds reader/writer/build/create methods, plus a reflection, plus a validation." Senior. Has crossed from declarations to code.
- "And the reflection is what eager loading and the form builders read; that is why
reflect_on_associationis part of the public API." Senior+. Has read the source and used the public reflection API.
The strongest signal: a candidate who has at some point opened the Rails source to figure something out and can mention which file or directory they looked at. "I once had a weird inverse_of issue and ended up reading activerecord/lib/active_record/associations/builder/" is the kind of detail that makes interviewers relax.
The principle at play
Rails declarations are method calls. belongs_to, has_many, enum, has_secure_password, delegated_type, before_action: every one of these is a class method that runs at load time and defines other methods or registers callbacks. Knowing this turns the framework from magic into Ruby.
The senior reflex is to ask, when something behaves unexpectedly: what did this declaration generate, and where is that code? The answer is almost always findable in 30 minutes if you know to look. Candidates who can demonstrate that reflex in an interview signal they will not be stuck when Rails surprises them in production.