File: advanced_associations.rdoc [sequel-4.20.0 Documentation]

### Home Classes Methods

### Files

- CHANGELOG
- MIT-LICENSE
- README.rdoc
- active_record.rdoc
- advanced_associations.rdoc
- association_basics.rdoc
- bin_sequel.rdoc
- cheat_sheet.rdoc
- code_order.rdoc
- core_extensions.rdoc
- dataset_basics.rdoc
- dataset_filtering.rdoc
- extensions.rdoc
- mass_assignment.rdoc
- migration.rdoc
- model_hooks.rdoc
- model_plugins.rdoc
- mssql_stored_procedures.rdoc
- object_model.rdoc
- opening_databases.rdoc
- postgresql.rdoc
- prepared_statements.rdoc
- querying.rdoc
- reflection.rdoc
- 1.0.txt
- 1.1.txt
- 1.3.txt
- 1.4.0.txt
- 1.5.0.txt
- 2.0.0.txt
- 2.1.0.txt
- 2.10.0.txt
- 2.11.0.txt
- 2.12.0.txt
- 2.2.0.txt
- 2.3.0.txt
- 2.4.0.txt
- 2.5.0.txt
- 2.6.0.txt
- 2.7.0.txt
- 2.8.0.txt
- 2.9.0.txt
- 3.0.0.txt
- 3.1.0.txt
- 3.10.0.txt
- 3.11.0.txt
- 3.12.0.txt
- 3.13.0.txt
- 3.14.0.txt
- 3.15.0.txt
- 3.16.0.txt
- 3.17.0.txt
- 3.18.0.txt
- 3.19.0.txt
- 3.2.0.txt
- 3.20.0.txt
- 3.21.0.txt
- 3.22.0.txt
- 3.23.0.txt
- 3.24.0.txt
- 3.25.0.txt
- 3.26.0.txt
- 3.27.0.txt
- 3.28.0.txt
- 3.29.0.txt
- 3.3.0.txt
- 3.30.0.txt
- 3.31.0.txt
- 3.32.0.txt
- 3.33.0.txt
- 3.34.0.txt
- 3.35.0.txt
- 3.36.0.txt
- 3.37.0.txt
- 3.38.0.txt
- 3.39.0.txt
- 3.4.0.txt
- 3.40.0.txt
- 3.41.0.txt
- 3.42.0.txt
- 3.43.0.txt
- 3.44.0.txt
- 3.45.0.txt
- 3.46.0.txt
- 3.47.0.txt
- 3.48.0.txt
- 3.5.0.txt
- 3.6.0.txt
- 3.7.0.txt
- 3.8.0.txt
- 3.9.0.txt
- 4.0.0.txt
- 4.1.0.txt
- 4.10.0.txt
- 4.11.0.txt
- 4.12.0.txt
- 4.13.0.txt
- 4.14.0.txt
- 4.15.0.txt
- 4.16.0.txt
- 4.17.0.txt
- 4.18.0.txt
- 4.19.0.txt
- 4.2.0.txt
- 4.20.0.txt
- 4.3.0.txt
- 4.4.0.txt
- 4.5.0.txt
- 4.6.0.txt
- 4.7.0.txt
- 4.8.0.txt
- 4.9.0.txt
- schema_modification.rdoc
- security.rdoc
- sharding.rdoc
- sql.rdoc
- testing.rdoc
- thread_safety.rdoc
- transactions.rdoc
- validations.rdoc
- virtual_rows.rdoc

### Class Index

Quicksearch

- Sequel
- Sequel::ADO
- Sequel::ADO::Access
- Sequel::ADO::Access::AdoSchema
- Sequel::ADO::Access::AdoSchema::Column
- Sequel::ADO::Access::DatabaseMethods
- Sequel::ADO::Access::Dataset
- Sequel::ADO::Database
- Sequel::ADO::Dataset
- Sequel::ADO::MSSQL
- Sequel::ADO::MSSQL::DatabaseMethods
- Sequel::ADO::MSSQL::Dataset
- Sequel::ASTTransformer
- Sequel::Access
- Sequel::Access::DatabaseMethods
- Sequel::Access::DatasetMethods
- Sequel::AdapterNotFound
- Sequel::Amalgalite
- Sequel::Amalgalite::Database
- Sequel::Amalgalite::Dataset
- Sequel::Amalgalite::SequelTypeMap
- Sequel::ArbitraryServers
- Sequel::BasicObject
- Sequel::BeforeHookFailed
- Sequel::CheckConstraintViolation
- Sequel::ColumnsIntrospection
- Sequel::ConnectionPool
- Sequel::ConnectionPool::ClassMethods
- Sequel::ConnectionValidator
- Sequel::ConnectionValidator::Retry
- Sequel::ConstraintValidations
- Sequel::ConstraintValidations::AlterTableGeneratorMethods
- Sequel::ConstraintValidations::CreateTableGeneratorMethods
- Sequel::ConstraintValidations::Generator
- Sequel::ConstraintViolation
- Sequel::CoreRefinements
- Sequel::Cubrid
- Sequel::Cubrid::Database
- Sequel::Cubrid::DatabaseMethods
- Sequel::Cubrid::Dataset
- Sequel::Cubrid::DatasetMethods
- Sequel::CurrentDateTimeTimestamp
- Sequel::CurrentDateTimeTimestamp::DatasetMethods
- Sequel::CurrentDateTimeTimestamp::DateTime
- Sequel::CurrentDateTimeTimestamp::Time
- Sequel::DB2
- Sequel::DB2::DB2Error
- Sequel::DB2::Database
- Sequel::DB2::DatabaseMethods
- Sequel::DB2::Dataset
- Sequel::DB2::DatasetMethods
- Sequel::DBI
- Sequel::DBI::Database
- Sequel::DBI::Dataset
- Sequel::DataObjects
- Sequel::DataObjects::Database
- Sequel::DataObjects::Dataset
- Sequel::DataObjects::MySQL
- Sequel::DataObjects::MySQL::DatabaseMethods
- Sequel::DataObjects::MySQL::Dataset
- Sequel::DataObjects::Postgres
- Sequel::DataObjects::Postgres::DatabaseMethods
- Sequel::DataObjects::SQLite
- Sequel::DataObjects::SQLite::DatabaseMethods
- Sequel::Database
- Sequel::Database::DatasetClass
- Sequel::Database::FromBlock
- Sequel::Database::ResetIdentifierMangling
- Sequel::Database::SplitAlterTable
- Sequel::DatabaseConnectionError
- Sequel::DatabaseDisconnectError
- Sequel::DatabaseError
- Sequel::DatabaseQuery
- Sequel::Dataset
- Sequel::Dataset::ArgumentMapper
- Sequel::Dataset::DatasetSourceAlias
- Sequel::Dataset::NullDataset
- Sequel::Dataset::Nullifiable
- Sequel::Dataset::Pagination
- Sequel::Dataset::PlaceholderLiteralizer
- Sequel::Dataset::PlaceholderLiteralizer::Argument
- Sequel::Dataset::PlaceholderLiteralizer::Recorder
- Sequel::Dataset::PreparedStatementMethods
- Sequel::Dataset::Query
- Sequel::Dataset::Replace
- Sequel::Dataset::RoundTimestamps
- Sequel::Dataset::SplitArrayNil
- Sequel::Dataset::StoredProcedureMethods
- Sequel::Dataset::StoredProcedures
- Sequel::Dataset::UnnumberedArgumentMapper
- Sequel::DatasetPagination
- Sequel::DatasetPrinter
- Sequel::DatasetQuery
- Sequel::Deprecation
- Sequel::EmptyArrayIgnoreNulls
- Sequel::EmulateOffsetWithReverseAndCount
- Sequel::EmulateOffsetWithRowNumber
- Sequel::Error
- Sequel::Error::AdapterNotFound
- Sequel::Error::InvalidOperation
- Sequel::Error::InvalidValue
- Sequel::Error::PoolTimeoutError
- Sequel::Error::Rollback
- Sequel::ErrorSQL
- Sequel::EvalInspect
- Sequel::Fdbsql
- Sequel::Fdbsql::Connection
- Sequel::Fdbsql::Database
- Sequel::Fdbsql::DatabaseMethods
- Sequel::Fdbsql::Dataset
- Sequel::Fdbsql::Dataset::BindArgumentMethods
- Sequel::Fdbsql::Dataset::PreparedStatementMethods
- Sequel::Fdbsql::DatasetMethods
- Sequel::Fdbsql::DatasetMethods::PreparedStatementMethods
- Sequel::FilterHaving
- Sequel::Firebird
- Sequel::Firebird::Database
- Sequel::Firebird::DatabaseMethods
- Sequel::Firebird::Dataset
- Sequel::Firebird::DatasetMethods
- Sequel::ForeignKeyConstraintViolation
- Sequel::GraphEach
- Sequel::HashAliases
- Sequel::HookFailed
- Sequel::IBMDB
- Sequel::IBMDB::Connection
- Sequel::IBMDB::Connection::Error
- Sequel::IBMDB::Database
- Sequel::IBMDB::Dataset
- Sequel::IBMDB::Dataset::CallableStatementMethods
- Sequel::IBMDB::Dataset::PreparedStatementMethods
- Sequel::IBMDB::Statement
- Sequel::Inflections
- Sequel::Informix
- Sequel::Informix::Database
- Sequel::Informix::DatabaseMethods
- Sequel::Informix::Dataset
- Sequel::Informix::DatasetMethods
- Sequel::IntegerMigrator
- Sequel::InvalidOperation
- Sequel::InvalidValue
- Sequel::JDBC
- Sequel::JDBC::AS400
- Sequel::JDBC::AS400::DatabaseMethods
- Sequel::JDBC::AS400::Dataset
- Sequel::JDBC::Cubrid
- Sequel::JDBC::Cubrid::DatabaseMethods
- Sequel::JDBC::DB2
- Sequel::JDBC::DB2::DatabaseMethods
- Sequel::JDBC::DB2::Dataset
- Sequel::JDBC::Database
- Sequel::JDBC::Dataset
- Sequel::JDBC::Dataset::PreparedStatementMethods
- Sequel::JDBC::Dataset::StoredProcedureMethods
- Sequel::JDBC::Derby
- Sequel::JDBC::Derby::DatabaseMethods
- Sequel::JDBC::Derby::Dataset
- Sequel::JDBC::Fdbsql
- Sequel::JDBC::Fdbsql::DatabaseMethods
- Sequel::JDBC::Fdbsql::Dataset
- Sequel::JDBC::Firebird
- Sequel::JDBC::Firebird::DatabaseMethods
- Sequel::JDBC::H2
- Sequel::JDBC::H2::DatabaseMethods
- Sequel::JDBC::H2::Dataset
- Sequel::JDBC::HSQLDB
- Sequel::JDBC::HSQLDB::DatabaseMethods
- Sequel::JDBC::HSQLDB::Dataset
- Sequel::JDBC::Informix
- Sequel::JDBC::Informix::DatabaseMethods
- Sequel::JDBC::JTDS
- Sequel::JDBC::JTDS::DatabaseMethods
- Sequel::JDBC::JTDS::Dataset
- Sequel::JDBC::JavaLang
- Sequel::JDBC::JavaSQL
- Sequel::JDBC::JavaxNaming
- Sequel::JDBC::MSSQL
- Sequel::JDBC::MSSQL::DatabaseMethods
- Sequel::JDBC::MySQL
- Sequel::JDBC::MySQL::DatabaseMethods
- Sequel::JDBC::Oracle
- Sequel::JDBC::Oracle::DatabaseMethods
- Sequel::JDBC::Oracle::Dataset
- Sequel::JDBC::Postgres
- Sequel::JDBC::Postgres::DatabaseMethods
- Sequel::JDBC::Postgres::Dataset
- Sequel::JDBC::Progress
- Sequel::JDBC::Progress::DatabaseMethods
- Sequel::JDBC::SQLServer
- Sequel::JDBC::SQLServer::DatabaseMethods
- Sequel::JDBC::SQLServer::DatabaseMethods::MetadataDatasetMethods
- Sequel::JDBC::SQLite
- Sequel::JDBC::SQLite::DatabaseMethods
- Sequel::JDBC::SqlAnywhere
- Sequel::JDBC::SqlAnywhere::DatabaseMethods
- Sequel::JDBC::SqlAnywhere::Dataset
- Sequel::JDBC::Transactions
- Sequel::JDBC::TypeConvertor
- Sequel::LiteralString
- Sequel::LooserTypecasting
- Sequel::MSSQL
- Sequel::MSSQL::DatabaseMethods
- Sequel::MSSQL::DatasetMethods
- Sequel::MSSQL::EmulateLateralWithApply
- Sequel::Metaprogramming
- Sequel::Migration
- Sequel::MigrationAlterTableReverser
- Sequel::MigrationDSL
- Sequel::MigrationReverser
- Sequel::Migrator
- Sequel::Migrator::Error
- Sequel::Migrator::NotCurrentError
- Sequel::Mock
- Sequel::Mock::Connection
- Sequel::Mock::Database
- Sequel::Mock::Dataset
- Sequel::Model
- Sequel::Model::Associations
- Sequel::Model::Associations::AssociationDatasetMethods
- Sequel::Model::Associations::AssociationReflection
- Sequel::Model::Associations::ClassMethods
- Sequel::Model::Associations::DatasetMethods
- Sequel::Model::Associations::EagerGraphLoader
- Sequel::Model::Associations::InstanceMethods
- Sequel::Model::Associations::ManyToManyAssociationReflection
- Sequel::Model::Associations::ManyToOneAssociationReflection
- Sequel::Model::Associations::OneThroughOneAssociationReflection
- Sequel::Model::Associations::OneToManyAssociationReflection
- Sequel::Model::Associations::OneToOneAssociationReflection
- Sequel::Model::Associations::SingularAssociationReflection
- Sequel::Model::ClassMethods
- Sequel::Model::DatasetMethods
- Sequel::Model::DatasetModule
- Sequel::Model::Errors
- Sequel::Model::InstanceMethods
- Sequel::MySQL
- Sequel::MySQL::Database
- Sequel::MySQL::DatabaseMethods
- Sequel::MySQL::Dataset
- Sequel::MySQL::DatasetMethods
- Sequel::MySQL::PreparedStatements
- Sequel::MySQL::PreparedStatements::DatabaseMethods
- Sequel::MySQL::PreparedStatements::DatasetMethods
- Sequel::MySQL::PreparedStatements::DatasetMethods::CallableStatementMethods
- Sequel::MySQL::PreparedStatements::DatasetMethods::PreparedStatementMethods
- Sequel::MySQL::PreparedStatements::DatasetMethods::StoredProcedureMethods
- Sequel::Mysql2
- Sequel::Mysql2::Database
- Sequel::Mysql2::Dataset
- Sequel::NamedTimezones
- Sequel::NamedTimezones::DatabaseMethods
- Sequel::NoExistingObject
- Sequel::NoMatchingRow
- Sequel::NotNullConstraintViolation
- Sequel::ODBC
- Sequel::ODBC::Database
- Sequel::ODBC::Dataset
- Sequel::ODBC::MSSQL
- Sequel::ODBC::MSSQL::DatabaseMethods
- Sequel::ODBC::MSSQL::Dataset
- Sequel::OpenBase
- Sequel::OpenBase::Database
- Sequel::OpenBase::Dataset
- Sequel::Oracle
- Sequel::Oracle::Database
- Sequel::Oracle::DatabaseMethods
- Sequel::Oracle::Dataset
- Sequel::Oracle::Dataset::ArgumentMapper
- Sequel::Oracle::Dataset::BindArgumentMethods
- Sequel::Oracle::Dataset::PreparedStatementMethods
- Sequel::Oracle::DatasetMethods
- Sequel::Plugins
- Sequel::Plugins::AccessedColumns
- Sequel::Plugins::AccessedColumns::InstanceMethods
- Sequel::Plugins::ActiveModel
- Sequel::Plugins::ActiveModel::ClassMethods
- Sequel::Plugins::ActiveModel::Errors
- Sequel::Plugins::ActiveModel::InstanceMethods
- Sequel::Plugins::AfterInitialize
- Sequel::Plugins::AfterInitialize::ClassMethods
- Sequel::Plugins::AfterInitialize::InstanceMethods
- Sequel::Plugins::AssociationAutoreloading
- Sequel::Plugins::AssociationDependencies
- Sequel::Plugins::AssociationDependencies::ClassMethods
- Sequel::Plugins::AssociationDependencies::InstanceMethods
- Sequel::Plugins::AssociationPks
- Sequel::Plugins::AssociationPks::ClassMethods
- Sequel::Plugins::AssociationPks::InstanceMethods
- Sequel::Plugins::AssociationProxies
- Sequel::Plugins::AssociationProxies::AssociationProxy
- Sequel::Plugins::AssociationProxies::ClassMethods
- Sequel::Plugins::AutoValidations
- Sequel::Plugins::AutoValidations::ClassMethods
- Sequel::Plugins::AutoValidations::InstanceMethods
- Sequel::Plugins::BlacklistSecurity
- Sequel::Plugins::BlacklistSecurity::ClassMethods
- Sequel::Plugins::BlacklistSecurity::InstanceMethods
- Sequel::Plugins::BlacklistSecurity::InstanceMethods::ExceptionList
- Sequel::Plugins::BooleanReaders
- Sequel::Plugins::BooleanReaders::ClassMethods
- Sequel::Plugins::Caching
- Sequel::Plugins::Caching::ClassMethods
- Sequel::Plugins::Caching::InstanceMethods
- Sequel::Plugins::ClassTableInheritance
- Sequel::Plugins::ClassTableInheritance::ClassMethods
- Sequel::Plugins::ClassTableInheritance::InstanceMethods
- Sequel::Plugins::ColumnConflicts
- Sequel::Plugins::ColumnConflicts::ClassMethods
- Sequel::Plugins::ColumnConflicts::InstanceMethods
- Sequel::Plugins::ColumnSelect
- Sequel::Plugins::ColumnSelect::ClassMethods
- Sequel::Plugins::Composition
- Sequel::Plugins::Composition::ClassMethods
- Sequel::Plugins::Composition::InstanceMethods
- Sequel::Plugins::ConstraintValidations
- Sequel::Plugins::ConstraintValidations::ClassMethods
- Sequel::Plugins::ConstraintValidations::DatabaseMethods
- Sequel::Plugins::ConstraintValidations::InstanceMethods
- Sequel::Plugins::DatasetAssociations
- Sequel::Plugins::DatasetAssociations::ClassMethods
- Sequel::Plugins::DatasetAssociations::DatasetMethods
- Sequel::Plugins::DefaultsSetter
- Sequel::Plugins::DefaultsSetter::ClassMethods
- Sequel::Plugins::DefaultsSetter::InstanceMethods
- Sequel::Plugins::Dirty
- Sequel::Plugins::Dirty::InstanceMethods
- Sequel::Plugins::EagerEach
- Sequel::Plugins::EagerEach::DatasetMethods
- Sequel::Plugins::ErrorSplitter
- Sequel::Plugins::ErrorSplitter::InstanceMethods
- Sequel::Plugins::ForceEncoding
- Sequel::Plugins::ForceEncoding::ClassMethods
- Sequel::Plugins::ForceEncoding::InstanceMethods
- Sequel::Plugins::HookClassMethods
- Sequel::Plugins::HookClassMethods::ClassMethods
- Sequel::Plugins::HookClassMethods::InstanceMethods
- Sequel::Plugins::InputTransformer
- Sequel::Plugins::InputTransformer::ClassMethods
- Sequel::Plugins::InputTransformer::InstanceMethods
- Sequel::Plugins::InsertReturningSelect
- Sequel::Plugins::InsertReturningSelect::ClassMethods
- Sequel::Plugins::InsertReturningSelect::InstanceMethods
- Sequel::Plugins::InstanceFilters
- Sequel::Plugins::InstanceFilters::Error
- Sequel::Plugins::InstanceFilters::InstanceMethods
- Sequel::Plugins::InstanceHooks
- Sequel::Plugins::InstanceHooks::InstanceMethods
- Sequel::Plugins::JsonSerializer
- Sequel::Plugins::JsonSerializer::ClassMethods
- Sequel::Plugins::JsonSerializer::DatasetMethods
- Sequel::Plugins::JsonSerializer::InstanceMethods
- Sequel::Plugins::JsonSerializer::Literal
- Sequel::Plugins::LazyAttributes
- Sequel::Plugins::LazyAttributes::ClassMethods
- Sequel::Plugins::LazyAttributes::InstanceMethods
- Sequel::Plugins::List
- Sequel::Plugins::List::ClassMethods
- Sequel::Plugins::List::InstanceMethods
- Sequel::Plugins::ManyThroughMany
- Sequel::Plugins::ManyThroughMany::ClassMethods
- Sequel::Plugins::ManyThroughMany::DatasetMethods
- Sequel::Plugins::ManyThroughMany::ManyThroughManyAssociationReflection
- Sequel::Plugins::ManyThroughMany::OneThroughManyAssociationReflection
- Sequel::Plugins::ManyToOnePkLookup
- Sequel::Plugins::ModificationDetection
- Sequel::Plugins::ModificationDetection::ClassMethods
- Sequel::Plugins::ModificationDetection::InstanceMethods
- Sequel::Plugins::MssqlOptimisticLocking
- Sequel::Plugins::MssqlOptimisticLocking::ClassMethods
- Sequel::Plugins::MssqlOptimisticLocking::InstanceMethods
- Sequel::Plugins::NestedAttributes
- Sequel::Plugins::NestedAttributes::ClassMethods
- Sequel::Plugins::NestedAttributes::InstanceMethods
- Sequel::Plugins::OptimisticLocking
- Sequel::Plugins::OptimisticLocking::ClassMethods
- Sequel::Plugins::OptimisticLocking::Error
- Sequel::Plugins::OptimisticLocking::InstanceMethods
- Sequel::Plugins::PgArrayAssociations
- Sequel::Plugins::PgArrayAssociations::ClassMethods
- Sequel::Plugins::PgArrayAssociations::DatasetMethods
- Sequel::Plugins::PgArrayAssociations::ManyToPgArrayAssociationReflection
- Sequel::Plugins::PgArrayAssociations::PgArrayToManyAssociationReflection
- Sequel::Plugins::PgRow
- Sequel::Plugins::PgRow::ClassMethods
- Sequel::Plugins::PgRow::DatabaseMethods
- Sequel::Plugins::PgRow::InstanceMethods
- Sequel::Plugins::PgTypecastOnLoad
- Sequel::Plugins::PgTypecastOnLoad::ClassMethods
- Sequel::Plugins::PgTypecastOnLoad::InstanceMethods
- Sequel::Plugins::PreparedStatements
- Sequel::Plugins::PreparedStatements::ClassMethods
- Sequel::Plugins::PreparedStatements::InstanceMethods
- Sequel::Plugins::PreparedStatementsAssociations
- Sequel::Plugins::PreparedStatementsAssociations::InstanceMethods
- Sequel::Plugins::PreparedStatementsSafe
- Sequel::Plugins::PreparedStatementsSafe::ClassMethods
- Sequel::Plugins::PreparedStatementsSafe::InstanceMethods
- Sequel::Plugins::PreparedStatementsWithPk
- Sequel::Plugins::PreparedStatementsWithPk::ClassMethods
- Sequel::Plugins::PreparedStatementsWithPk::DatasetMethods
- Sequel::Plugins::RcteTree
- Sequel::Plugins::Schema
- Sequel::Plugins::Schema::ClassMethods
- Sequel::Plugins::Scissors
- Sequel::Plugins::Scissors::ClassMethods
- Sequel::Plugins::Serialization
- Sequel::Plugins::Serialization::ClassMethods
- Sequel::Plugins::Serialization::InstanceMethods
- Sequel::Plugins::SerializationModificationDetection
- Sequel::Plugins::SerializationModificationDetection::InstanceMethods
- Sequel::Plugins::Sharding
- Sequel::Plugins::Sharding::ClassMethods
- Sequel::Plugins::Sharding::DatasetMethods
- Sequel::Plugins::Sharding::InstanceMethods
- Sequel::Plugins::SingleTableInheritance
- Sequel::Plugins::SingleTableInheritance::ClassMethods
- Sequel::Plugins::SingleTableInheritance::InstanceMethods
- Sequel::Plugins::SkipCreateRefresh
- Sequel::Plugins::SkipCreateRefresh::InstanceMethods
- Sequel::Plugins::SplitValues
- Sequel::Plugins::SplitValues::ClassMethods
- Sequel::Plugins::SplitValues::InstanceMethods
- Sequel::Plugins::StaticCache
- Sequel::Plugins::StaticCache::ClassMethods
- Sequel::Plugins::StaticCache::InstanceMethods
- Sequel::Plugins::StringStripper
- Sequel::Plugins::StringStripper::ClassMethods
- Sequel::Plugins::Subclasses
- Sequel::Plugins::Subclasses::ClassMethods
- Sequel::Plugins::TableSelect
- Sequel::Plugins::TableSelect::ClassMethods
- Sequel::Plugins::TacticalEagerLoading
- Sequel::Plugins::TacticalEagerLoading::DatasetMethods
- Sequel::Plugins::TacticalEagerLoading::InstanceMethods
- Sequel::Plugins::Timestamps
- Sequel::Plugins::Timestamps::ClassMethods
- Sequel::Plugins::Timestamps::InstanceMethods
- Sequel::Plugins::Touch
- Sequel::Plugins::Touch::ClassMethods
- Sequel::Plugins::Touch::InstanceMethods
- Sequel::Plugins::Tree
- Sequel::Plugins::Tree::ClassMethods
- Sequel::Plugins::Tree::InstanceMethods
- Sequel::Plugins::Tree::SingleRoot
- Sequel::Plugins::Tree::SingleRoot::ClassMethods
- Sequel::Plugins::Tree::SingleRoot::InstanceMethods
- Sequel::Plugins::Tree::TreeMultipleRootError
- Sequel::Plugins::TypecastOnLoad
- Sequel::Plugins::TypecastOnLoad::ClassMethods
- Sequel::Plugins::TypecastOnLoad::InstanceMethods
- Sequel::Plugins::UnlimitedUpdate
- Sequel::Plugins::UnlimitedUpdate::InstanceMethods
- Sequel::Plugins::UpdateOrCreate
- Sequel::Plugins::UpdateOrCreate::ClassMethods
- Sequel::Plugins::UpdatePrimaryKey
- Sequel::Plugins::UpdatePrimaryKey::InstanceMethods
- Sequel::Plugins::ValidationClassMethods
- Sequel::Plugins::ValidationClassMethods::ClassMethods
- Sequel::Plugins::ValidationClassMethods::ClassMethods::Generator
- Sequel::Plugins::ValidationClassMethods::InstanceMethods
- Sequel::Plugins::ValidationHelpers
- Sequel::Plugins::ValidationHelpers::InstanceMethods
- Sequel::Plugins::XmlSerializer
- Sequel::Plugins::XmlSerializer::ClassMethods
- Sequel::Plugins::XmlSerializer::DatasetMethods
- Sequel::Plugins::XmlSerializer::InstanceMethods
- Sequel::PoolTimeout
- Sequel::Postgres
- Sequel::Postgres::Adapter
- Sequel::Postgres::AlterTableGenerator
- Sequel::Postgres::ArrayOp
- Sequel::Postgres::ArrayOpMethods
- Sequel::Postgres::CreateTableGenerator
- Sequel::Postgres::Database
- Sequel::Postgres::DatabaseMethods
- Sequel::Postgres::Dataset
- Sequel::Postgres::Dataset::ArgumentMapper
- Sequel::Postgres::Dataset::BindArgumentMethods
- Sequel::Postgres::Dataset::PreparedStatementMethods
- Sequel::Postgres::DatasetMethods
- Sequel::Postgres::DatasetMethods::PreparedStatementMethods
- Sequel::Postgres::EnumDatabaseMethods
- Sequel::Postgres::ExclusionConstraintViolation
- Sequel::Postgres::HStore
- Sequel::Postgres::HStore::DatabaseMethods
- Sequel::Postgres::HStore::Parser
- Sequel::Postgres::HStoreOp
- Sequel::Postgres::HStoreOpMethods
- Sequel::Postgres::InetDatabaseMethods
- Sequel::Postgres::InetDatasetMethods
- Sequel::Postgres::IntervalDatabaseMethods
- Sequel::Postgres::IntervalDatabaseMethods::Parser
- Sequel::Postgres::IntervalDatasetMethods
- Sequel::Postgres::JSONArray
- Sequel::Postgres::JSONArrayBase
- Sequel::Postgres::JSONBArray
- Sequel::Postgres::JSONBHash
- Sequel::Postgres::JSONBOp
- Sequel::Postgres::JSONBaseOp
- Sequel::Postgres::JSONDatabaseMethods
- Sequel::Postgres::JSONHash
- Sequel::Postgres::JSONHashBase
- Sequel::Postgres::JSONOp
- Sequel::Postgres::JSONOpMethods
- Sequel::Postgres::LooseCount
- Sequel::Postgres::PGArray
- Sequel::Postgres::PGArray::Creator
- Sequel::Postgres::PGArray::DatabaseMethods
- Sequel::Postgres::PGArray::JSONCreator
- Sequel::Postgres::PGArray::Parser
- Sequel::Postgres::PGRange
- Sequel::Postgres::PGRange::DatabaseMethods
- Sequel::Postgres::PGRange::DatasetMethods
- Sequel::Postgres::PGRange::Parser
- Sequel::Postgres::PGRow
- Sequel::Postgres::PGRow::ArrayRow
- Sequel::Postgres::PGRow::DatabaseMethods
- Sequel::Postgres::PGRow::HashRow
- Sequel::Postgres::PGRow::Parser
- Sequel::Postgres::PGRow::Splitter
- Sequel::Postgres::PGRowOp
- Sequel::Postgres::PGRowOp::ExpressionMethods
- Sequel::Postgres::RangeOp
- Sequel::Postgres::RangeOpMethods
- Sequel::Postgres::StaticCacheUpdater
- Sequel::PrettyTable
- Sequel::Progress
- Sequel::Progress::DatabaseMethods
- Sequel::Progress::DatasetMethods
- Sequel::Qualifier
- Sequel::QueryLiterals
- Sequel::Rollback
- Sequel::SQL
- Sequel::SQL::AliasMethods
- Sequel::SQL::AliasedExpression
- Sequel::SQL::BitwiseMethods
- Sequel::SQL::Blob
- Sequel::SQL::BooleanConstant
- Sequel::SQL::BooleanExpression
- Sequel::SQL::BooleanMethods
- Sequel::SQL::Builders
- Sequel::SQL::CaseExpression
- Sequel::SQL::Cast
- Sequel::SQL::CastMethods
- Sequel::SQL::ColumnAll
- Sequel::SQL::ComplexExpression
- Sequel::SQL::ComplexExpressionMethods
- Sequel::SQL::Constant
- Sequel::SQL::Constants
- Sequel::SQL::DateAdd
- Sequel::SQL::DateAdd::DatasetMethods
- Sequel::SQL::DelayedEvaluation
- Sequel::SQL::Expression
- Sequel::SQL::Function
- Sequel::SQL::GenericExpression
- Sequel::SQL::Identifier
- Sequel::SQL::InequalityMethods
- Sequel::SQL::JoinClause
- Sequel::SQL::JoinOnClause
- Sequel::SQL::JoinUsingClause
- Sequel::SQL::NegativeBooleanConstant
- Sequel::SQL::NumericExpression
- Sequel::SQL::NumericMethods
- Sequel::SQL::OperatorBuilders
- Sequel::SQL::OrderMethods
- Sequel::SQL::OrderedExpression
- Sequel::SQL::PlaceholderLiteralString
- Sequel::SQL::QualifiedIdentifier
- Sequel::SQL::QualifyingMethods
- Sequel::SQL::StringConcatenationMethods
- Sequel::SQL::StringExpression
- Sequel::SQL::StringMethods
- Sequel::SQL::Subscript
- Sequel::SQL::SubscriptMethods
- Sequel::SQL::ValueList
- Sequel::SQL::VirtualRow
- Sequel::SQL::Window
- Sequel::SQL::Wrapper
- Sequel::SQLTime
- Sequel::SQLite
- Sequel::SQLite::Database
- Sequel::SQLite::DatabaseMethods
- Sequel::SQLite::Dataset
- Sequel::SQLite::Dataset::ArgumentMapper
- Sequel::SQLite::Dataset::BindArgumentMethods
- Sequel::SQLite::Dataset::PreparedStatementMethods
- Sequel::SQLite::DatasetMethods
- Sequel::Schema
- Sequel::Schema::AlterTableGenerator
- Sequel::Schema::CreateTableGenerator
- Sequel::Schema::Generator
- Sequel::SchemaCaching
- Sequel::SchemaDumper
- Sequel::SelectRemove
- Sequel::Sequel3DatasetMethods
- Sequel::SerializationFailure
- Sequel::ServerBlock
- Sequel::SetOverrides
- Sequel::ShardedSingleConnectionPool
- Sequel::ShardedThreadedConnectionPool
- Sequel::SimpleMigration
- Sequel::SingleConnectionPool
- Sequel::SqlAnywhere
- Sequel::SqlAnywhere::Database
- Sequel::SqlAnywhere::DatabaseMethods
- Sequel::SqlAnywhere::Dataset
- Sequel::SqlAnywhere::DatasetMethods
- Sequel::SqlAnywhere::SQLAnywhereException
- Sequel::Swift
- Sequel::Swift::Database
- Sequel::Swift::Dataset
- Sequel::Swift::MySQL
- Sequel::Swift::MySQL::DatabaseMethods
- Sequel::Swift::MySQL::Dataset
- Sequel::Swift::Postgres
- Sequel::Swift::Postgres::DatabaseMethods
- Sequel::Swift::SQLite
- Sequel::Swift::SQLite::DatabaseMethods
- Sequel::Swift::SQLite::Dataset
- Sequel::ThreadLocalTimezones
- Sequel::ThreadedConnectionPool
- Sequel::ThreadedServerBlock
- Sequel::TimestampMigrator
- Sequel::Timezones
- Sequel::TinyTDS
- Sequel::TinyTDS::Database
- Sequel::TinyTDS::Dataset
- Sequel::TinyTDS::Dataset::ArgumentMapper
- Sequel::TinyTDS::Dataset::PreparedStatementMethods
- Sequel::ToDot
- Sequel::ToDot::DatasetMethods
- Sequel::UnbindDuplicate
- Sequel::Unbinder
- Sequel::UndefinedAssociation
- Sequel::UniqueConstraintViolation
- Sequel::UnthreadedServerBlock
- Sequel::ValidationFailed
- String
- String::Inflections
- Array
- FalseClass
- Hash
- NilClass
- Numeric
- Object
- PGconn
- PGresult
- Range
- Symbol
- TrueClass

No matching classes.

# Advanced Associations

Sequel::Model has the most powerful and flexible associations of any ruby ORM.

## Background: Sequel::Model association options

There are a bunch of advanced association options that are available to handle more complex cases. First we'll go over some of the simpler ones:

All associations take a block that can be used to further filter/modify the default dataset. There's also an :eager_block option if you want to use a different block when eager loading via `Dataset#eager`. Association blocks are useful for things like:

    Artist.one_to_many :gold_albums, :class=>:Album do |ds|
      ds.where{copies_sold > 500000}
    end

There are a whole bunch of options for changing how the association is eagerly loaded via `Dataset#eager_graph`: `:graph_block`, `:graph_conditions`, `:graph_only_conditions`, `:graph_join_type` (and `:graph_join_table_*` ones for JOINing to the join table in a many_to_many association).

:graph_join_type | The type of join to do (`:inner`, `:left`, `:right`)
:graph_conditions | Additional conditions to put on join (needs to be a hash or array of all two pairs). Automatically assumes unqualified symbols or first element of the pair to be columns of the associated model, and unqualified symbols of the second element of the pair to be columns of the current model.
:graph_block | A block passed to `join_table`, allowing you to specify conditions other than equality, or to use OR, or set up any arbitrary condition. The block is passed the associated table alias, current table alias, and an array of previous joins clause objects.
:graph_only_conditions | Use these conditions instead of the standard association conditions. This is necessary when you don't want to have an equal condition between the foreign key and primary key of the tables. You can also use this to have a JOIN USING (array of symbols), or a NATURAL or CROSS JOIN (nil, with the appropriate `:graph_join_type`).

These can be used like this:

    # Makes Artist.eager_graph(:required_albums).all not return artists that
    # don't have any albums
    Artist.one_to_many :required_albums, :class=>:Album, :graph_join_type=>:inner

    # Makes sure all returned albums have the active flag set
    Artist.one_to_many :active_albums, :class=>:Album, \
      :graph_conditions=>{:active=>true}

    # Only returns albums that have sold more than 500,000 copies
    Artist.one_to_many :gold_albums, :class=>:Album, \
      :graph_block=>proc{|j,lj,js| Sequel.qualify(j, :copies_sold) > 500000}

    # Handles the case where the tables are associated by a case insensitive name string
    Artist.one_to_many :albums, :key=>:artist_name, \
      :graph_only_conditions=>nil, \
      :graph_block=>proc{|j,lj,js| {Sequel.function(:lower, Sequel.qualify(j, :artist_name))=>Sequel.function(:lower, Sequel.qualify(lj, :name))}}

    # Handles the case where both key columns have the name artist_name, and you want to use
    # a JOIN USING
    Artist.one_to_many :albums, :key=>:artist_name, :graph_only_conditions=>[:artist_name]

Remember, using `eager_graph` is generally only necessary when you need to filter/order based on columns in an associated table, it is recommended to use `eager` for eager loading if possible.

One advantage of using `eager_graph` is that you can easily filter/order on columns in an associated table on a per-query basis, using regular Sequel dataset methods. For example, if you only want to retrieve artists who have albums that start with A, and eager load just those albums, ordered by the albums name, you can do:

    albums = Artist.
      eager_graph(:albums).
      where(Sequel.like(:albums__name, 'A%')).
      order(:albums__name).
      all

For lazy loading (e.g. Model.association), the `:dataset` option can be used to specify an arbitrary dataset (one that uses different keys, multiple keys, joins to other tables, etc.).

For eager loading via `eager`, the `:eager_loader` option can be used to specify how to eagerly load a complex association. This is an extremely powerful option. Though it can often be verbose (compared to other things in Sequel), it allows you complete control over how to eagerly load associations for a group of objects.

:eager_loader should be a proc that takes a single hash argument, which will have at least the following keys:

:id_map | A mapping of key values to arrays of current model instances, usage described below
:rows | An array of model objects
:associations | A hash of dependent associations to eagerly load
:self | The dataset that is doing the eager loading
:eager_block | A dynamic callback for this eager load.

Since you are given all of the records, you can do things like filter on associations that are specified by multiple keys, or do multiple queries depending on the content of the records (which would be necessary for polymorphic associations). Inside the `:eager_loader` proc, you should get the related objects and populate the associations cache for all objects in the array of records. The hash of dependent associations is available for you to cascade the eager loading down multiple levels, but it is up to you to use it.

The id_map is a performance enhancement that is used by the default association loaders and is also available to you. It is a hash with keys foreign/primary key values, and values being arrays of current model objects having the foreign/primary key value associated with the key. This may be hard to visualize, so I'll give an example. Let's say you have the following associations

    Album.many_to_one :artist
    Album.one_to_many :tracks

and the following three albums in the database:

    album1 = Album.create(:artist_id=>3) # id: 1
    album2 = Album.create(:artist_id=>3) # id: 2
    album3 = Album.create(:artist_id=>2) # id: 3

If you try to eager load this dataset:

    Album.eager(:artist, :tracks).all

Then the id_map provided to the artist :eager_loader proc would be:

    {3=>[album1, album2], 2=>[album3]}

The artist id_map contains a mapping of artist_id values to arrays of album objects. Since both album1 and album2 have the same artist_id, the are both in the array related to that key. album3 has a different artist_id, so it is in a different array. Eager loading of artists is done by looking for any artist having one of the keys in the hash:

    artists = Artist.where(:id=>id_map.keys).all

When the artists are retrieved, you can iterate over them, find entries with matching keys, and manually associate them to the albums:

    artists.each do |artist|
      # Find related albums using the artist_id_map
      if albums = id_map[artist.id]
        # Iterate over the albums
        albums.each do |album|
          # Manually set the artist association for each album
          album.associations[:artist] = artist
        end
      end
    end

The id_map provided to the tracks :eager_loader proc would be:

    {1=>[album1], 2=>[album2], 3=>[album3]}

Now the id_map contains a mapping of id values to arrays of album objects (in this case each array only has a single object, because id is the primary key). So when looking for tracks to eagerly load, you only need to look for ones that have an album_id with one of the keys in the hash:

    tracks = Track.where(:album_id=>id_map.keys).all

When the tracks are retrieved, you can iterate over them, find entries with matching keys, and manually associate them to the albums:

    tracks.each do |track|
      if albums = id_map[track.album_id]
        albums.each do |album|
          album.associations[:tracks] << track
        end
      end
    end

### Two basic example eager loaders

Putting the code in the above examples together, you almost have enough for a basic working eager loader. The main important thing that is missing is you need to set initial values for the eagerly loaded associations. For the artist association, you need to initial the values to nil:

    # rows here is the :rows entry in the hash passed to the eager loader
    rows.each{|album| album.associations[:artist] = nil}

For the tracks association, you set the initial value to an empty array:

    rows.each{|album| album.associations[:track] = []}

These are done so that if an album currently being loaded doesn't have an associated artist or any associated tracks, the lack of them will be cached, so calling the artist or tracks method on the album will not do another database lookup.

So putting everything together, the artist eager loader looks like:

    Album.many_to_one :artist, :eager_loader=>(proc do |eo_opts|
      eo_opts[:rows].each{|album| album.associations[:artist] = nil}
      id_map = eo_opts[:id_map]
      Artist.where(:id=>id_map.keys).all do |artist|
        if albums = id_map[artist.id]
          albums.each do |album|
            album.associations[:artist] = artist
          end
        end
      end
    end)

and the tracks eager loader looks like:

    Album.one_to_many :tracks, :eager_loader=>(proc do |eo_opts|
      eo_opts[:rows].each{|album| album.associations[:tracks] = []}
      id_map = eo_opts[:id_map]
      Track.where(:id=>id_map.keys).all do |tracks|
        if albums = id_map[track.album_id]
          albums.each do |album|
            album.associations[:tracks] << track
          end
        end
      end
    end)

Now, these are both overly simplistic eager loaders that don't respect cascaded associations or any of the association options. But hopefully they both provide simple examples that you can more easily build and learn from, as the custom eager loaders described later in this page are more complex.

Basically, the eager loading steps can be broken down into:

1. Set default association values (nil/[]) for each of the current objects

2. Return just related associated objects by filtering the associated class to include only rows with keys present in the id_map.

3. Iterating over the returned associated objects, indexing into the id_map using the foreign/primary key value in the associated object to get current values associated to that specific object.

4. For each of those current values, updating the cached association value to include that specific object.

Using the :eager_loader proc, you should be able to eagerly load all associations that can be eagerly loaded, even if Sequel doesn't natively support such eager loading.

## Limited Associations

Sequel supports specifying limits and/or offsets for associations:

    Artist.one_to_many :first_10_albums, :class=>:Album, :order=>:release_date, :limit=>10

For retrieving the associated objects for a single object, this just uses a LIMIT:

    artist.first_10_albums
    # SELECT * FROM albums WHERE (artist_id = 1) LIMIT 10

### Eager Loading via eager

However, if you want to eagerly load an association, you must use a different approach. Sequel has 4 separate strategies for dealing with such cases.

The default strategy used on all databases is a UNION-based approach, which will submit multiple subqueries in a UNION query:

    Artist.where(:id=>[1,2]).eager(:first_10_albums).all
    # SELECT * FROM (SELECT * FROM albums WHERE (artist_id = 1) LIMIT 10) UNION ALL
    # SELECT * FROM (SELECT * FROM albums WHERE (artist_id = 2) LIMIT 10)

This is the fastest way to load the associated objects on most databases, as long as there is an index on albums.artist_id. Without an index it is probably the slowest approach, so make sure you have an index on the key columns. If you cannot add an index, you'll want to manually specify the :eager_limit_strategy option as shown below.

On PostgreSQL, for *_one associations that don't use an offset, you can choose to use a the distinct on strategy:

    Artist.one_to_one :first_album, :class=>:Album, :order=>:release_date,
      :eager_limit_strategy=>:distinct_on
    Artist.where(:id=>[1,2]).eager(:first_album).all
    # SELECT DISTINCT ON (albums.artist_id) *
    # FROM albums
    # WHERE (albums.artist_id IN (1, 2))
    # ORDER BY albums.artist_id, release_date

Otherwise, if the database supports window functions, you can choose to use the window function strategy:

    Artist.one_to_many :first_10_albums, :class=>:Album, :order=>:release_date, :limit=>10,
      :eager_limit_strategy=>:window_function
    Artist.where(:id=>[1,2]).eager(:first_10_albums).all
    # SELECT * FROM (
    #   SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY release_date) AS x_sequel_row_number_x
    #   FROM tracks
    #   WHERE (tracks.album_id IN (1, 2))
    # ) AS t1
    # WHERE (x_sequel_row_number_x <= 10)

Alternatively, you can use the :ruby strategy, which will fall back to retrieving all records, and then will slice the resulting array to get the first 10 after retrieval.

### Eager Loading via eager_graph_with_options

When eager loading an association via eager_graph (which uses JOINs), the situation is similar. While the UNION-based strategy cannot be used as you don't know the records being eagerly loaded in advance, Sequel can use a variant of the other 3 strategies. By default it retrieves all records and then does the array slice in ruby. As eager_graph does not support options, to use an eager_graph limit strategy you have to use the eager_graph_with_options method with the :limit_strategy option.

The :distinct_on strategy uses DISTINCT ON in a subquery and JOINs that subquery:

    Artist.eager_graph_with_options(:first_album, :limit_strategy=>true).all
    # SELECT artists.id, artists.name, first_album.id AS first_album_id,
    #        first_album.name AS first_album_name, first_album.artist_id,
    #        first_album.release_date
    # FROM artists
    # LEFT OUTER JOIN (
    #   SELECT DISTINCT ON (albums.artist_id) *
    #   FROM albums
    #   ORDER BY albums.artist_id, release_date
    # ) AS first_album ON (first_album.artist_id = artists.id)

The :window_function approach JOINs to a nested subquery using a window function:

    Artist.eager_graph_with_options(:first_10_albums, :limit_strategy=>true).all
    # SELECT artists.id, artists.name, first_10_albums.id AS first_10_albums_id,
    #        first_10_albums.name AS first_10_albums_name, first_10_albums.artist_id,
    #        first_10_albums.release_date
    # FROM artists
    # LEFT OUTER JOIN (
    #   SELECT id, name, artist_id, release_date
    #   FROM (
    #     SELECT *, row_number() OVER (PARTITION BY tracks.album_id ORDER BY release_date) AS x_sequel_row_number_x
    #     FROM albums
    #   ) AS t1 WHERE (x_sequel_row_number_x <= 10)
    # ) AS first_10_albums ON (first_10_albums.artist_id = artists.id)

The :correlated_subquery approach JOINs to a nested subquery using a correlated subquery:

    Artist.eager_graph_with_options(:first_10_albums, :limit_strategy=>true).all
    # SELECT artists.id, artists.name, first_10_albums.id AS first_10_albums_id,
    #        first_10_albums.name AS first_10_albums_name, first_10_albums.artist_id,
    #        first_10_albums.release_date
    # FROM artists
    # LEFT OUTER JOIN (
    #   SELECT *
    #   FROM albums
    #   WHERE albums.id IN (
    #     SELECT t1.id
    #     FROM tracks AS t1
    #     WHERE (t1.album_id = tracks.album_id)
    #     ORDER BY release_date
    #     LIMIT 10
    #   )
    # ) AS first_10_albums ON (first_10_albums.artist_id = artists.id)

(SELECT * FROM tracks WHERE (tracks.id IN (SELECT t1.id FROM tracks AS t1 WHERE (t1.album_id = tracks.album_id) LIMIT 1)))

The reason that Sequel does not automatically use the :distinct_on, :window function or :correlated_subquery strategy for eager_graph is that it can perform much worse than the default of just doing the array slicing in ruby. If you are only using eager_graph to return a few records, it may be cheaper to get all of their associated records and filter them in ruby as opposed to computing the set of limited associated records for all rows.

It's recommended to only use an eager_graph limit strategy if you have benchmarked it against the default behavior and found it is faster for your use case.

### Filtering By Associations

In order to return correct results, Sequel automatically uses a limit strategy when using filtering by associations with limited associations, if the database supports it. As in the eager_graph case, the UNION-based strategy doesn't work. Unlike in the eager and eager_graph cases, the array slicing in ruby approach does not work, you must use an SQL-based strategy. Sequel will select an appropriate default strategy based on the database you are using, and you can override it using the :filter_limit_strategy option.

The :distinct_on strategy:

    Artist.where(:first_album=>Album[1]).all
    # SELECT *
    # FROM artists
    # WHERE (artists.id IN (
    #   SELECT albums.artist_id
    #   FROM albums
    #   WHERE ((albums.artist_id IS NOT NULL) AND (albums.id IN (
    #     SELECT DISTINCT ON (albums.artist_id) albums.id
    #     FROM albums
    #     ORDER BY albums.artist_id, release_date
    #   )) AND (albums.id = 1))))

The :window_function strategy:

    Artist.where(:first_10_albums=>Album[1]).all
    # SELECT *
    # FROM artists
    # WHERE (artists.id IN (
    #   SELECT albums.artist_id
    #   FROM albums
    #   WHERE ((albums.artist_id IS NOT NULL) AND (albums.id IN (
    #     SELECT id FROM (
    #       SELECT albums.id, row_number() OVER (PARTITION BY albums.artist_id ORDER BY release_date) AS x_sequel_row_number_x
    #       FROM albums
    #     ) AS t1
    #     WHERE (x_sequel_row_number_x <= 10)
    #   )) AND (albums.id = 1))))

The :correlated_subquery strategy:

    Artist.where(:first_10_albums=>Album[1]).all
    # SELECT *
    # FROM artists
    # WHERE (artists.id IN (
    #   SELECT albums.artist_id
    #   FROM albums
    #   WHERE ((albums.artist_id IS NOT NULL) AND (albums.id IN (
    #     SELECT t1.id
    #     FROM albums AS t1
    #     WHERE (t1.artist_id = albums.artist_id)
    #     ORDER BY release_date
    #     LIMIT 1
    #   )) AND (albums.id = 1))))

Note that filtering by limited associations does not work on MySQL, as does not support any of the strategies. It's also not supported when using composite keys on databases that don't support window functions and don't support multiple columns in IN.

## Additional Association Types

While the above examples for limited associations showed one_to_many and one_to_one associations, it's just because those are the simplest examples. Sequel supports all of the same features for many_to_many and one_through_one associations that are enabled by default, as well as the many_through_many and one_through_many associations that are added by the many_through_many plugin.

## ActiveRecord associations

Sequel supports all of associations that ActiveRecord supports, though some require different approaches or custom `:eager_loader` options.

### Association callbacks

Sequel supports the same callbacks that ActiveRecord does for `one_to_many` and `many_to_many` associations: `:before_add`, `:before_remove`, `:after_add`, and `:after_remove`. For `many_to_one` associations and `one_to_one` associations, Sequel supports the `:before_set` and `:after_set` callbacks. On all associations, Sequel supports `:after_load`, which is called after the association has been loaded.

Each of these options can be a symbol specifying an instance method that takes one argument (the associated object), or a proc that takes two arguments (the current object and the associated object), or an array of symbols and procs. For `:after_load` with a *_to_many association, the associated object argument is an array of associated objects.

If any of the before callbacks return `false`, the adding/removing does not happen and it either raises a `Sequel::HookFailed` (the default), or returns false (if `raise_on_save_failure` is false).

### Association extensions

All associations come with an `association_dataset` method that can be further filtered or otherwise modified:

    class Author < Sequel::Model
      one_to_many :authorships
    end
    Author.first.authorships_dataset.where{number < 10}.first

You can extend a dataset with a module using the `:extend` association option. You can reference the model object that created the association dataset via the dataset's `model_object` method, and the related association reflection via the dataset's `association_reflection` method:

    module FindOrCreate
      def find_or_create(vals)
        first(vals) || model.create(vals.merge(association_reflection[:key]=>model_object.id))
      end
    end
    class Author < Sequel::Model
      one_to_many :authorships, :extend=>FindOrCreate
    end
    Author.first.authorships_dataset.find_or_create(:name=>'Blah', :number=>10)

### `has_many :through` associations

`many_to_many` handles the usual case of a `has_many :through` with a `belongs_to` in the associated model. It doesn't break on the case where the join table is a model table, unlike ActiveRecord's `has_and_belongs_to_many`.

ActiveRecord:

    class Author < ActiveRecord::Base
      has_many :authorships
      has_many :books, :through => :authorships
    end

    class Authorship < ActiveRecord::Base
      belongs_to :author
      belongs_to :book
    end

    @author = Author.find :first
    @author.books

Sequel::Model:

    class Author < Sequel::Model
      one_to_many :authorships
      many_to_many :books, :join_table=>:authorships
    end

    class Authorship < Sequel::Model
      many_to_one :author
      many_to_one :book
    end

    @author = Author.first
    @author.books

If you use an association other than `belongs_to` in the associated model, such as a `has_many`, you still use a `many_to_many` association, but you need to use some options:

ActiveRecord:

    class Firm < ActiveRecord::Base
      has_many :clients
      has_many :invoices, :through => :clients
    end

    class Client < ActiveRecord::Base
      belongs_to :firm
      has_many :invoices
    end

    class Invoice < ActiveRecord::Base
      belongs_to :client
      has_one :firm, :through => :client
    end

    Firm.find(:first).invoices

Sequel::Model:

    class Firm < Sequel::Model
      one_to_many :clients
      many_to_many :invoices, :join_table=>:clients, :right_key=>:id, :right_primary_key=>:client_id
    end

    class Client < Sequel::Model
      many_to_one :firm
      one_to_many :invoices
    end

    class Invoice < Sequel::Model
      many_to_one :client
      one_through_one :firm, :join_table=>:clients, :left_key=>:id, :left_primary_key=>:client_id, :right_key=>:firm_id
    end

    Firm.first.invoices

To handle cases where there are multiple join tables, use the many_through_many plugin that ships with Sequel.

### Polymorphic Associations

Sequel discourages the use of polymorphic associations, which is the reason they are not supported by default. All polymorphic associations can be made non-polymorphic by using additional tables and/or columns instead of having a column containing the associated class name as a string.

Polymorphic associations break referential integrity and are significantly more complex than non-polymorphic associations, so their use is not recommended unless you are stuck with an existing design that uses them.

If you must use them, look for the sequel_polymorphic external plugin, as it makes using polymorphic associations in Sequel about as easy as it is in ActiveRecord. However, here's how they can be done using Sequel's custom associations (the sequel_polymorphic plugin is just a generic version of this code):

ActiveRecord:

    class Asset < ActiveRecord::Base
      belongs_to :attachable, :polymorphic => true
    end

    class Post < ActiveRecord::Base
      has_many :assets, :as => :attachable
    end

    class Note < ActiveRecord::Base
      has_many :assets, :as => :attachable
    end

    @asset.attachable = @post
    @asset.attachable = @note

Sequel::Model:

    class Asset < Sequel::Model
      many_to_one :attachable, :reciprocal=>:assets,
        :setter=>(proc do |attachable|
          self[:attachable_id] = (attachable.pk if attachable)
          self[:attachable_type] = (attachable.class.name if attachable)
        end),
        :dataset=>(proc do
          klass = attachable_type.constantize
          klass.where(klass.primary_key=>attachable_id)
        end),
        :eager_loader=>(proc do |eo|
          id_map = {}
          eo[:rows].each do |asset|
            asset.associations[:attachable] = nil
            ((id_map[asset.attachable_type] ||= {})[asset.attachable_id] ||= []) << asset
          end
          id_map.each do |klass_name, id_map|
            klass = klass_name.constantize
            klass.where(klass.primary_key=>id_map.keys).all do |attach|
              id_map[attach.pk].each do |asset|
                asset.associations[:attachable] = attach
              end
            end
          end
        end)
    end

    class Post < Sequel::Model
      one_to_many :assets, :key=>:attachable_id, :reciprocal=>:attachable, :conditions=>{:attachable_type=>'Post'},
        :adder=>proc{|asset| asset.update(:attachable_id=>pk, :attachable_type=>'Post')},
        :remover=>proc{|asset| asset.update(:attachable_id=>nil, :attachable_type=>nil)},
        :clearer=>proc{assets_dataset.update(:attachable_id=>nil, :attachable_type=>nil)}
    end

    class Note < Sequel::Model
      one_to_many :assets, :key=>:attachable_id, :reciprocal=>:attachable, :conditions=>{:attachable_type=>'Note'},
        :adder=>proc{|asset| asset.update(:attachable_id=>pk, :attachable_type=>'Note')},
        :remover=>proc{|asset| asset.update(:attachable_id=>nil, :attachable_type=>nil)},
        :clearer=>proc{assets_dataset.update(:attachable_id=>nil, :attachable_type=>nil)}
    end

    @asset.attachable = @post
    @asset.attachable = @note

## Other advanced associations

### Joining on multiple keys

Let's say you have two tables that are associated with each other with multiple keys. This can be handled using Sequel's built in composite key support for associations:

    # Both of these models have an album_id, number, and disc_number fields.
    # All FavoriteTracks have an associated track, but not all tracks have an
    # associated favorite track

    class Track < Sequel::Model
      many_to_one :favorite_track, :key=>[:disc_number, :number, :album_id], :primary_key=>[:disc_number, :number, :album_id]
    end
    class FavoriteTrack < Sequel::Model
      one_to_one :tracks, :key=>[:disc_number, :number, :album_id], :primary_key=>[:disc_number, :number, :album_id]
    end

### Tree - All Ancestors and Descendents

Let's say you want to store a tree relationship in your database, it's pretty simple:

    class Node < Sequel::Model
      many_to_one :parent, :class=>self
      one_to_many :children, :key=>:parent_id, :class=>self
    end

You can easily get a node's parent with node.parent, and a node's children with node.children. You can even eager load the relationship up to a certain depth:

    # Eager load three generations of generations of children for a given node
    Node.filter(:id=>1).eager(:children=>{:children=>:children}).all.first
    # Load parents and grandparents for a group of nodes
    Node.filter{id < 10}.eager(:parent=>:parent).all

What if you want to get all ancestors up to the root node, or all descendents, without knowing the depth of the tree?

    class Node < Sequel::Model
      many_to_one :ancestors, :class=>self,
       :eager_loader=>(proc do |eo|
        # Handle cases where the root node has the same parent_id as primary_key
        # and also when it is NULL
        non_root_nodes = eo[:rows].reject do |n|
          if [nil, n.pk].include?(n.parent_id)
            # Make sure root nodes have their parent association set to nil
            n.associations[:parent] = nil
            true
          else
            false
          end
        end
        unless non_root_nodes.empty?
          id_map = {}
          # Create an map of parent_ids to nodes that have that parent id
          non_root_nodes.each{|n| (id_map[n.parent_id] ||= []) << n}
          # Doesn't cause an infinte loop, because when only the root node
          # is left, this is not called.
          Node.where(Node.primary_key=>id_map.keys).eager(:ancestors).all do |node|
            # Populate the parent association for each node
            id_map[node.pk].each{|n| n.associations[:parent] = node}
          end
        end
      end)
      many_to_one :descendants, :eager_loader=>(proc do |eo|
        id_map = {}
        eo[:rows].each do |n|
          # Initialize an empty array of child associations for each parent node
          n.associations[:children] = []
          # Populate identity map of nodes
          id_map[n.pk] = n
        end
        # Doesn't cause an infinite loop, because the :eager_loader is not called
        # if no records are returned.  Exclude id = parent_id to avoid infinite loop
        # if the root note is one of the returned records and it has parent_id = id
        # instead of parent_id = NULL.
        Node.where(:parent_id=>id_map.keys).exclude(:id=>:parent_id).eager(:descendants).all do |node|
          # Get the parent from the identity map
          parent = id_map[node.parent_id]
          # Set the child's parent association to the parent
          node.associations[:parent] = parent
          # Add the child association to the array of children in the parent
          parent.associations[:children] << node
        end
      end)
    end

Note that unlike ActiveRecord, Sequel supports common table expressions, which allows you to use recursive queries. The results are not the same as in the above case, as all descendents are stored in a single association, but all descendants can be both lazy loaded or eager loaded in a single query (assuming your database supports recursive common table expressions). Sequel ships with an `rcte_tree` plugin that makes this easy:

    class Node < Sequel::Model
      plugin :rcte_tree
    end

### Joining multiple keys to a single key, through a third table

Let's say you have a database of songs, lyrics, and artists. Each song may or may not have a lyric (most songs are instrumental). The lyric can be associated to an artist in each of four ways: composer, arranger, vocalist, or lyricist. These may all be the same, or they could all be different, and none of them are required. The songs table has a lyric_id field to associate it to the lyric, and the lyric table has four fields to associate it to the artist (composer_id, arranger_id, vocalist_id, and lyricist_id).

What you want to do is get all songs for a given artist, ordered by the song's name, with no duplicates?

    class Artist < Sequel::Model
      one_to_many :songs, :order=>:songs__name, \
        :dataset=>proc{Song.select_all(:songs).join(Lyric, :id=>:lyric_id, id=>[:composer_id, :arranger_id, :vocalist_id, :lyricist_id])}, \
        :eager_loader=>(proc do |eo|
          h = eo[:id_map]
          ids = h.keys
          eo[:rows].each{|r| r.associations[:songs] = []}
          Song.select_all(:songs).
            select_append(:lyrics__composer_id, :lyrics__arranger_id, :lyrics__vocalist_id, :lyrics__lyricist_id).
            join(Lyric, :id=>:lyric_id){Sequel.or(:composer_id=>ids, :arranger_id=>ids, :vocalist_id=>ids, :lyricist_id=>ids)}.
            order(:songs__name).all do |song|
            [:composer_id, :arranger_id, :vocalist_id, :lyricist_id].each do |x|
              recs = h[song.values.delete(x)]
              recs.each{|r| r.associations[:songs] << song} if recs
            end
          end
          eo[:rows].each{|r| r.associations[:songs].uniq!}
        end)
    end

### Statistics Associations (Sum of Associated Table Column)

In addition to getting associated records, you can use Sequel's association support to get aggregate information for columns in associated tables (sums, averages, etc.).

Let's say you have a database with projects and tickets. A project can have many tickets, and each ticket has a number of hours associated with it. You can use the association support to create a Project association that gives the sum of hours for all associated tickets.

    class Project < Sequel::Model
      one_to_many :tickets
      many_to_one :ticket_hours, :read_only=>true, :key=>:id,
       :dataset=>proc{Ticket.where(:project_id=>id).select{sum(hours).as(hours)}},
       :eager_loader=>(proc do |eo|
        eo[:rows].each{|p| p.associations[:ticket_hours] = nil}
        Ticket.where(:project_id=>eo[:id_map].keys).
         select_group(:project_id).
         select_append{sum(hours).as(hours)}.
         all do |t|
          p = eo[:id_map][t.values.delete(:project_id)].first
          p.associations[:ticket_hours] = t
         end
       end)
      # The association method returns a Ticket object with a single aggregate
      # sum-of-hours value, but you want it to return an Integer/Float of just the
      # sum of hours, so you call super and return just the sum-of-hours value.
      # This works for both lazy loading and eager loading.
      def ticket_hours
        if s = super
          s[:hours]
        end
      end
    end
    class Ticket < Sequel::Model
      many_to_one :project
    end

Note that it is often better to use a sum cache instead of this approach. You can implement a sum cache using `after_create` and `after_delete` hooks, or preferrably using a database trigger.

[Validate]

Generated with the Darkfish Rdoc Generator 2.