Reading the Source · Card 8
What does has_many :posts actually add?
One line. A collection proxy with about twenty methods on it, plus class-level metadata. Most developers can name three.
The familiar line
Standard. You've written it. What does it generate?
class User < ApplicationRecord
has_many :posts
end The question
List every method has_many :posts adds to a User instance. The mid-level answer names user.posts and maybe one or two more. The senior answer is closer to twenty.
Take a moment. The reader (user.posts) is obvious. So is the writer. After that, think about: building, creating, counting, querying, checking emptiness, mutating.
What Rails generates
On every User instance, has_many :posts adds a collection proxy with these methods (and more):
user.posts # the collection (a CollectionProxy)
user.posts = [post1, post2] # replace the whole collection
user.posts << post # append
user.post_ids # array of foreign keys
user.post_ids = [1, 2, 3] # set by IDs (loads, syncs)
user.posts.build(title: "...") # in-memory, unsaved
user.posts.create(title: "...") # build + save
user.posts.create!(title: "...") # raises if invalid
user.posts.find(id) # scoped to this user's posts
user.posts.where(published: true)
user.posts.exists?
user.posts.size # uses cached count if loaded
user.posts.count # always runs SELECT COUNT
user.posts.empty?
user.posts.first / .last
user.posts.reload # re-fetch
user.posts.clear # remove all (per dependent: option)
user.posts.delete(post)
user.posts.destroy(post) On the User class itself, the same line also sets up:
- A reflection at
User.reflect_on_association(:posts), used by eager loading, fixtures, the form builder, and more. - If you pass
dependent: :destroy, a callback that destroys each associated Post when the User is destroyed. - If you pass
dependent: :delete_all, a callback that issues a single DELETE. - If you pass
through: :memberships, Rails sets up the join walk souser.postsjoins through the intermediate table.
Where this happens in Rails
The entry point is has_many in activerecord/lib/active_record/associations.rb. The heavy lifting is in Associations::Builder::HasMany and Associations::CollectionProxy, which provides most of the methods listed above. The CollectionProxy is what user.posts actually returns — it acts like an array but also like a Relation, which is why you can chain .where on it.
Why this is worth knowing
When you see user.posts.build(...) in someone else's code, you instantly trace it back to has_many instead of looking for a method definition that doesn't exist. The CollectionProxy is also why user.posts.where(published: true) reads as a chain on a Relation — it is one. Same shape as belongs_to: a single declaration that quietly generates a family of methods, a reflection, and sometimes callbacks.