Skip to content

Add types for methods ActiveRecord::Relation delegates to records - #1112

Draft
akitoshiga wants to merge 1 commit into
ruby:mainfrom
akitoshiga:activerecord-relation-delegate-to-records
Draft

akitoshiga wants to merge 1 commit into
ruby:mainfrom
akitoshiga:activerecord-relation-delegate-to-records

Conversation

@akitoshiga

@akitoshiga akitoshiga commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

ActiveRecord::Relation delegates some Array methods to records:
https://github.com/rails/rails/blob/v8.1.3.1/activerecord/lib/active_record/relation/delegation.rb#L100-L103

In the generated RBS, these methods are defined in ActiveRecord::Delegation as (*untyped, **untyped) ?{ (*untyped) -> untyped } -> untyped, so the element type of a relation is lost (e.g. User.all[0] is untyped).

This PR adds ActiveRecord::Delegation::_DelegateToRecords[Model] and includes it into ActiveRecord::Relation::Methods[Model, PrimaryKey], so they are typed with the model of each relation.

users = User.all
users.length    # => Integer
users[0]        # => User?
users.sample(2) # => Array[User]

The signatures follow Array in core RBS and Array extensions in activesupport. each is excluded because Relation::Methods already defines it.

Why the signatures are written one by one

RBS has no syntax for delegation, and there is no way to refer to the method types of another class or to pick only some methods of Array[Model]. So reusing Array's signatures would mean taking all of them, which does not match the actual behavior:

  • Inheriting from Array[Model]: Relation is not an Array, and its methods would conflict with query methods such as select and find.
  • Including an interface that covers all of Array: methods Relation does not have (e.g. push, map!) would also type-check.

The interface takes the model as a type parameter, so this single definition applies to the ActiveRecord_Relation of every model generated by rbs_rails.

Notes

ActiveRecord::Associations::CollectionProxy defines some of these methods (e.g. length) by itself, and those definitions take precedence over the included interface. So user.articles.length is still untyped, while methods not defined in CollectionProxy (e.g. user.articles[0]) are typed.

ActiveRecord::Relation delegates Array methods such as length, [],
join and sample to records. The generated RBS defines them in
ActiveRecord::Delegation as untyped, so the element type of the
relation is lost and callers have to insert to_a to get typed values.

Define them in an interface and include it into
ActiveRecord::Relation::Methods so that they are typed with the model
of each relation.

Co-authored-by: Cursor <[email protected]>
@akitoshiga
akitoshiga force-pushed the activerecord-relation-delegate-to-records branch from dff6d9a to 5c03ec9 Compare October 22, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant