Class: Tapioca::Dsl::Compilers::ActiveRecordRelations
- Inherits:
-
Tapioca::Dsl::Compiler
- Object
- Tapioca::Dsl::Compiler
- Tapioca::Dsl::Compilers::ActiveRecordRelations
- Extended by:
- T::Sig
- Includes:
- Helpers::ActiveRecordConstantsHelper, SorbetHelper
- Defined in:
- lib/tapioca/dsl/compilers/active_record_relations.rb
Overview
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::PrivateRelationthat subclassesActiveRecord::Relation. This synthetic class represents a relation onModelwhose methods which return a relation always return aModel::PrivateRelationinstance. -
Model::PrivateAssocationRelationthat 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::PrivateAssocationRelationinstance. 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::PrivateCollectionProxythat 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::PrivateAssocationRelationinstance. This class represents a collection ofModelinstances with some extra methods tobuild,create, etc newModelinstances in the collection.
and the following modules:
-
Model::GeneratedRelationMethodsholds all the relation methods with the return type ofModel::PrivateRelation. For example, callingallon theModelclass or an instance ofModel::PrivateRelationclass will always return aModel::PrivateRelationinstance, thus the signature ofallis defined with that return type in this module. -
Model::GeneratedAssociationRelationMethodsholds all the relation methods with the return type ofModel::PrivateAssociationRelation. For example, callingallon an instance ofModel::PrivateAssociationRelationor an instance ofModel::PrivateCollectionProxyclass will always return aModel::PrivateAssociationRelationinstance, thus the signature ofallis defined with that return type in this module. -
Model::CommonRelationMethodsholds 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 (aModelinstance), 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.
A note on find: find is typed as T.untyped by default.
While it is often used in the manner of Model.find(id), Rails does support pasing in an array to find, which
would then return a T::Enumerable[Model]. This would force a static cast everywhere find is used to avoid type
errors. This is not ideal considering very few users of find use the array syntax over a where. With untyped,
this cast is optional and so it was decided to avoid typing it. If you need runtime guarentees when using find
the best method of doing so is by casting the return value to the model: T.cast(Model.find(id), Model).
find_by does guarentee a return value of Model, so find can can be refactored accordingly:
Model.find_by!(id: id). This will avoid the cast requirement at runtime.
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 sigs 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_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_ary; end
Elem = type_member { { fixed: ::Post } }
end
end
Constant Summary collapse
- ConstantType =
type_member { { fixed: T.class_of(::ActiveRecord::Base) } }
- ASSOCIATION_METHODS =
T.let( ::ActiveRecord::AssociationRelation.instance_methods - ::ActiveRecord::Relation.instance_methods, T::Array[Symbol], )
- COLLECTION_PROXY_METHODS =
T.let( ::ActiveRecord::Associations::CollectionProxy.instance_methods - ::ActiveRecord::AssociationRelation.instance_methods, T::Array[Symbol], )
- QUERY_METHODS =
T.let( begin # Grab all Query methods query_methods = ActiveRecord::QueryMethods.instance_methods(false) # Grab all Spawn methods query_methods |= ActiveRecord::SpawnMethods.instance_methods(false) # Remove the ones we know are private API query_methods -= [:arel, :build_subquery, :construct_join_dependency, :extensions, :spawn] # Remove "where" which needs a custom return type for WhereChains query_methods -= [:where] # Remove the methods that ... query_methods .grep_v(/_clause$/) # end with "_clause" .grep_v(/_values?$/) # end with "_value" or "_values" .grep_v(/=$/) # end with "="" .grep_v(/(?<!uniq)!$/) # end with "!" except for "uniq!" end, T::Array[Symbol], )
- WHERE_CHAIN_QUERY_METHODS =
T.let( ActiveRecord::QueryMethods::WhereChain.instance_methods(false), T::Array[Symbol], )
- FINDER_METHODS =
T.let(ActiveRecord::FinderMethods.instance_methods(false), T::Array[Symbol])
- SIGNED_FINDER_METHODS =
T.let( defined?(ActiveRecord::SignedId) ? ActiveRecord::SignedId::ClassMethods.instance_methods(false) : [], T::Array[Symbol], )
- BATCHES_METHODS =
T.let(ActiveRecord::Batches.instance_methods(false), T::Array[Symbol])
- CALCULATION_METHODS =
T.let(ActiveRecord::Calculations.instance_methods(false), T::Array[Symbol])
- ENUMERABLE_QUERY_METHODS =
T.let([:any?, :many?, :none?, :one?], T::Array[Symbol])
- FIND_OR_CREATE_METHODS =
T.let( [:find_or_create_by, :find_or_create_by!, :find_or_initialize_by, :create_or_find_by, :create_or_find_by!], T::Array[Symbol], )
- BUILDER_METHODS =
T.let([:new, :build, :create, :create!], T::Array[Symbol])
Constants included from SorbetHelper
SorbetHelper::FEATURE_REQUIREMENTS, SorbetHelper::SORBET_BIN, SorbetHelper::SORBET_EXE_PATH_ENV_VAR, SorbetHelper::SORBET_GEM_SPEC, SorbetHelper::SORBET_PAYLOAD_URL
Constants included from Helpers::ActiveRecordConstantsHelper
Helpers::ActiveRecordConstantsHelper::AssociationMethodsModuleName, Helpers::ActiveRecordConstantsHelper::AssociationRelationClassName, Helpers::ActiveRecordConstantsHelper::AssociationRelationMethodsModuleName, Helpers::ActiveRecordConstantsHelper::AssociationRelationWhereChainClassName, Helpers::ActiveRecordConstantsHelper::AssociationsCollectionProxyClassName, Helpers::ActiveRecordConstantsHelper::AttributeMethodsModuleName, Helpers::ActiveRecordConstantsHelper::CommonRelationMethodsModuleName, Helpers::ActiveRecordConstantsHelper::DelegatedTypesModuleName, Helpers::ActiveRecordConstantsHelper::ReflectionType, Helpers::ActiveRecordConstantsHelper::RelationClassName, Helpers::ActiveRecordConstantsHelper::RelationMethodsModuleName, Helpers::ActiveRecordConstantsHelper::RelationWhereChainClassName, Helpers::ActiveRecordConstantsHelper::SecureTokensModuleName
Constants included from Runtime::Reflection
Runtime::Reflection::ANCESTORS_METHOD, Runtime::Reflection::CLASS_METHOD, Runtime::Reflection::CONSTANTS_METHOD, Runtime::Reflection::EQUAL_METHOD, Runtime::Reflection::METHOD_METHOD, Runtime::Reflection::NAME_METHOD, Runtime::Reflection::OBJECT_ID_METHOD, Runtime::Reflection::PRIVATE_INSTANCE_METHODS_METHOD, Runtime::Reflection::PROTECTED_INSTANCE_METHODS_METHOD, Runtime::Reflection::PUBLIC_INSTANCE_METHODS_METHOD, Runtime::Reflection::REQUIRED_FROM_LABELS, Runtime::Reflection::SINGLETON_CLASS_METHOD, Runtime::Reflection::SUPERCLASS_METHOD, Runtime::Reflection::UNDEFINED_CONSTANT
Instance Attribute Summary
Attributes inherited from Tapioca::Dsl::Compiler
Class Method Summary collapse
Instance Method Summary collapse
Methods included from SorbetHelper
#sorbet, #sorbet_path, #sorbet_supports?
Methods inherited from Tapioca::Dsl::Compiler
#add_error, #compiler_enabled?, handles?, #initialize, processable_constants
Methods included from T::Generic::TypeStoragePatch
#[], #type_member, #type_template
Methods included from Runtime::Reflection
#ancestors_of, #are_equal?, #class_of, #constant_defined?, #constantize, #constants_of, #descendants_of, #file_candidates_for, #inherited_ancestors_of, #method_of, #name_of, #name_of_type, #object_id_of, #private_instance_methods_of, #protected_instance_methods_of, #public_instance_methods_of, #qualified_name_of, #resolve_loc, #signature_of, #singleton_class_of, #superclass_of
Methods included from Runtime::AttachedClassOf
Methods included from RBIHelper
#as_nilable_type, #create_block_param, #create_kw_opt_param, #create_kw_param, #create_kw_rest_param, #create_opt_param, #create_param, #create_rest_param, #create_typed_param, #sanitize_signature_types, serialize_type_variable, #valid_method_name?, #valid_parameter_name?
Constructor Details
This class inherits a constructor from Tapioca::Dsl::Compiler
Class Method Details
.gather_constants ⇒ Object
177 178 179 |
# File 'lib/tapioca/dsl/compilers/active_record_relations.rb', line 177 def gather_constants descendants_of(ActiveRecord::Base).reject(&:abstract_class?) end |
Instance Method Details
#decorate ⇒ Object
166 167 168 169 170 171 |
# File 'lib/tapioca/dsl/compilers/active_record_relations.rb', line 166 def decorate create_classes_and_includes create_common_methods create_relation_methods create_association_relation_methods end |