Tapioca::Dsl::Compilers::ActiveRecordRelations
decorates RBI files for subclasses of
ActiveRecord::Base
and adds
relation,
collection proxy,
query,
spawn,
finder, and
calculation methods.
The compiler defines 3 (synthetic) modules and 3 (synthetic) classes to represent relations properly.
For a given model Model
, we generate the following classes:
-
A
Model::PrivateRelation
that subclassesActiveRecord::Relation
. This synthetic class represents a relation onModel
whose methods which return a relation always return aModel::PrivateRelation
instance. -
Model::PrivateAssociationRelation
that subclassesActiveRecord::AssociationRelation
. This synthetic class represents a relation on a singular association of typeModel
(e.g.foo.model
) whose methods which return a relation will always return aModel::PrivateAssociationRelation
instance. The difference between this class and the previous one is mainly that an association relation also keeps track of the resource association for this relation. -
Model::PrivateCollectionProxy
that subclasses fromActiveRecord::Associations::CollectionProxy
. This synthetic class represents a relation on a plural association of typeModel
(e.g.foo.models
) whose methods which return a relation will always return aModel::PrivateAssociationRelation
instance. This class represents a collection ofModel
instances with some extra methods tobuild
,create
, etc newModel
instances in the collection.
and the following modules:
-
Model::GeneratedRelationMethods
holds all the relation methods with the return type ofModel::PrivateRelation
. For example, callingall
on theModel
class or an instance ofModel::PrivateRelation
class will always return aModel::PrivateRelation
instance, thus the signature ofall
is defined with that return type in this module. -
Model::GeneratedAssociationRelationMethods
holds all the relation methods with the return type ofModel::PrivateAssociationRelation
. For example, callingall
on an instance ofModel::PrivateAssociationRelation
or an instance ofModel::PrivateCollectionProxy
class will always return aModel::PrivateAssociationRelation
instance, thus the signature ofall
is defined with that return type in this module. -
Model::CommonRelationMethods
holds all the relation methods that do not depend on the type of relation in their return type. For example,find_by!
will always return the same type (aModel
instance), regardless of what kind of relation it is called on, and so belongs in this module. This module is used to reduce the replication of methods between the previous two modules.
Additionally, the actual Model
class extends both Model::CommonRelationMethods
and
Model::PrivateRelation
modules, so that, for example, find_by
and all
can be chained off of the
Model
class.
CAUTION: The generated relation classes are named PrivateXXX
intentionally to reflect the fact
that they represent private subconstants of the Active Record model. As such, these types do not
exist at runtime, and their counterparts that do exist at runtime are marked private_constant
anyway.
For that reason, these types cannot be used in user code or in sig
s inside Ruby files, since that will
make the runtime checks fail.
For example, with the following ActiveRecord::Base
subclass:
class Post < ApplicationRecord
end
this compiler will produce the RBI file post.rbi
with the following content:
# post.rbi
# typed: true
class Post
extend CommonRelationMethods
extend GeneratedRelationMethods
module CommonRelationMethods
sig { params(block: T.nilable(T.proc.params(record: ::Post).returns(T.untyped))).returns(T::Boolean) }
def any?(&block); end
# ...
end
module GeneratedAssociationRelationMethods
sig { returns(PrivateAssociationRelation) }
def all; end
# ...
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateAssociationRelation) }
def where(*args, &blk); end
end
module GeneratedRelationMethods
sig { returns(PrivateRelation) }
def all; end
# ...
sig { params(args: T.untyped, blk: T.untyped).returns(PrivateRelation) }
def where(*args, &blk); end
end
class PrivateAssociationRelation < ::ActiveRecord::AssociationRelation
include CommonRelationMethods
include GeneratedAssociationRelationMethods
sig { returns(T::Array[::Post]) }
def to_a; end
sig { returns(T::Array[::Post]) }
def to_ary; end
Elem = type_member { { fixed: ::Post } }
end
class PrivateCollectionProxy < ::ActiveRecord::Associations::CollectionProxy
include CommonRelationMethods
include GeneratedAssociationRelationMethods
sig do
params(records: T.any(::Post, T::Array[::Post], T::Array[PrivateCollectionProxy]))
.returns(PrivateCollectionProxy)
end
def <<(*records); end
# ...
end
class PrivateRelation < ::ActiveRecord::Relation
include CommonRelationMethods
include GeneratedRelationMethods
sig { returns(T::Array[::Post]) }
def to_a; end
sig { returns(T::Array[::Post]) }
def to_ary; end
Elem = type_member { { fixed: ::Post } }
end
end