# typed: strict # frozen_string_literal: true extend T::Sig # Set up the database schema for testing ActiveRecord::Schema.define do create_table :vehicle_lots do |t| t.string :name t.timestamps end create_base_table :vehicles do |t| t.string :name t.references :vehicle_lot, foreign_key: { to_table: :vehicle_lots } t.timestamps t.create_aux :car do |t| t.string :fuel_type t.decimal :engine_size, precision: 3, scale: 1 end end change_base_table :vehicles do |t| t.create_aux :boat do |t| t.boolean :only_freshwater end t.create_aux :plane do |t| t.integer :engine_type end end create_base_table :people do |t| t.string :name t.timestamps t.create_aux :driver do |t| t.integer :license_number, index: true, null: false t.references :car, foreign_key: { to_table: :vehicles_car_aux, primary_key: :base_table_id } end t.create_aux :captain do |t| t.references :boat, null: false, foreign_key: { to_table: :vehicles_boat_aux, primary_key: :base_table_id } end t.create_aux :passenger do |t| t.references :boat, null: false, foreign_key: { to_table: :vehicles_boat_aux, primary_key: :base_table_id } end end create_base_table :relationship_joins do |t| t.create_aux :doctor_patient do |t| t.integer :num_exams t.references :doctor, foreign_key: { to_table: :people } t.references :patient, foreign_key: { to_table: :people } end t.create_aux :employer_employee do |t| t.boolean :signed_nda t.references :employer, foreign_key: { to_table: :people } t.references :employee, foreign_key: { to_table: :people } end end create_base_table :utensils do |t| t.string :name t.string :material t.timestamps t.create_aux :fork do |t| t.integer :num_tongs end t.create_aux :spoon do |t| t.string :curvature end end create_aux_table :people, :reader do |t| t.integer :reading_speed t.integer :read_book_joins_count end create_table :books do |t| t.string :title t.string :author t.integer :pages t.integer :read_book_joins_count t.timestamps end create_table :read_book_joins, id: false, primary_key: %i[book_id reader_id] do |t| t.references :book, foreign_key: { to_table: :books } t.references :reader, foreign_key: { to_table: :people_reader_aux, primary_key: :base_table_id } end end class Vehicle < ActiveRecord::Base include HasAuxTable belongs_to :vehicle_lot end class VehicleLot < ActiveRecord::Base has_many :vehicles end class Car < Vehicle aux_table :car has_many :drivers, inverse_of: :car end class Boat < Vehicle aux_table :boat has_many :passengers, inverse_of: :boat belongs_to :captain, inverse_of: :boat end class Plane < Vehicle aux_table :plane enum :engine_type, { turbofan: 0, turboprop: 1, piston: 2, electric: 3 } end class Person < ActiveRecord::Base extend T::Sig include HasAuxTable validates :name, presence: true, uniqueness: true sig do params( associations: [Symbol, Symbol], table_name: Symbol, model_class_name: T.any(Symbol, String, T.class_of(ActiveRecord::Base)), join_class_name: T.any(Symbol, String, T.class_of(ActiveRecord::Base)) ).void end def self.has_and_belongs_to_many_through( associations, table_name:, model_class_name:, join_class_name: ) from_assoc_plural, to_assoc_plural = associations from_assoc_class_name, to_assoc_class_name = model_class_name, model_class_name from_assoc_singular = from_assoc_plural.to_s.singularize from_join_assoc_name = :"#{from_assoc_singular}_#{table_name}" to_assoc_singular = to_assoc_plural.to_s.singularize to_join_assoc_name = :"#{to_assoc_singular}_#{table_name}" has_many( from_join_assoc_name, primary_key: primary_key, foreign_key: "#{from_assoc_singular}_id", inverse_of: from_assoc_singular, class_name: join_class_name ) has_many( to_assoc_plural, through: from_join_assoc_name, source: to_assoc_singular, class_name: to_assoc_class_name ) has_many( to_join_assoc_name, primary_key: primary_key, foreign_key: "#{to_assoc_singular}_id", inverse_of: to_assoc_singular, class_name: join_class_name ) has_many( from_assoc_plural, through: to_join_assoc_name, source: from_assoc_singular, class_name: from_assoc_class_name ) end has_and_belongs_to_many_through( %i[doctors patients], model_class_name: "Person", join_class_name: "DoctorPatientJoin", table_name: :doctor_patient_joins ) has_and_belongs_to_many_through( %i[employers employees], model_class_name: "Person", join_class_name: "EmployerEmployeeJoin", table_name: :employer_employee_joins ) end class Driver < Person aux_table :driver belongs_to :car, optional: true validates :license_number, presence: true, uniqueness: true end class Captain < Person aux_table :captain has_one :boat, inverse_of: :captain end class Passenger < Person aux_table :passenger belongs_to :boat, inverse_of: :passengers end module Kitchen class Utensil < ActiveRecord::Base include HasAuxTable end class Fork < Utensil aux_table :fork end class Spoon < Utensil aux_table :spoon end end # Non-aux table model that has_and_belongs_to_many w/ counter cache class Book < ActiveRecord::Base has_many :read_book_joins, inverse_of: :book has_many :readers, through: :read_book_joins end # Aux table model that has_and_belongs_to_many w/ counter cache class Reader < Person aux_table :reader has_many :read_book_joins, inverse_of: :reader has_many :read_books, through: :read_book_joins, source: :book end # The join table for the has_and_belongs_to_many association between Reader and Book class ReadBookJoin < ActiveRecord::Base belongs_to :book, counter_cache: true belongs_to :reader, counter_cache: true end # Join table that has aux records class RelationshipJoin < ActiveRecord::Base include HasAuxTable end class DoctorPatientJoin < RelationshipJoin aux_table :doctor_patient belongs_to :doctor, class_name: "Person" belongs_to :patient, class_name: "Person" end class EmployerEmployeeJoin < RelationshipJoin aux_table :employer_employee belongs_to :employer, class_name: "Person" belongs_to :employee, class_name: "Person" end class User < ActiveRecord::Base include HasAuxTable has_many :posts, inverse_of: :user, class_name: "Post" end ActiveRecord::Schema.define do create_base_table :model_as do |t| t.string :a_field1 t.integer :model_bs_count t.integer :ad_joins_count t.create_aux :a1 do |t| t.integer :a1_field1 t.integer :model_cs_count end t.create_aux :a2 do |t| t.string :a2_field1 t.integer :model_b2s_count end end create_base_table :model_bs do |t| t.string :b_field1 t.references :model_a, foreign_key: { to_table: :model_as } t.create_aux :b1 do |t| t.integer :b1_field1 end t.create_aux :b2 do |t| t.references :model_a2, foreign_key: { to_table: :model_as_a2_aux, primary_key: :base_table_id } end end create_table :model_cs do |t| t.string :c_field1 t.references :model_a1, foreign_key: { to_table: :model_as_a1_aux, primary_key: :base_table_id } end create_base_table :model_ds do |t| t.integer :ad_joins_count t.create_aux :d1 do |t| t.string :d1_field1 end end create_table :ad_joins, primary_key: %i[model_a_id model_d_id] do |t| t.references :model_a, foreign_key: { to_table: :model_as } t.references :model_d, foreign_key: { to_table: :model_ds } end # A* has_many B # A1 has_many C # A* has_many ADJoin # B belongs_to A # C belongs_to A1 # D* has_many ADJoin # ADJoin belongs_to A # ADJoin belongs_to D end class ModelA < ActiveRecord::Base include HasAuxTable has_many :model_bs, inverse_of: :model_a has_many :ad_joins, inverse_of: :model_a has_many :model_ds, through: :ad_joins validates :a_field1, presence: true, uniqueness: true end class ModelA1 < ModelA aux_table :a1 has_many :model_cs, inverse_of: :model_a1 validates :a1_field1, presence: true, uniqueness: true end class ModelA2 < ModelA aux_table :a2 has_many :model_b2s, inverse_of: :model_a2 validates :a2_field1, presence: true, uniqueness: true end class ModelB < ActiveRecord::Base include HasAuxTable belongs_to :model_a, inverse_of: :model_bs, counter_cache: true validates :b_field1, presence: true, uniqueness: true end class ModelB1 < ModelB aux_table :b1 validates :b1_field1, presence: true, uniqueness: true end class ModelB2 < ModelB aux_table :b2 belongs_to :model_a2, inverse_of: :model_b2s, counter_cache: true end class ModelC < ActiveRecord::Base belongs_to :model_a1, inverse_of: :model_cs, counter_cache: true validates :c_field1, presence: true, uniqueness: true end class ModelD < ActiveRecord::Base include HasAuxTable has_many :ad_joins, inverse_of: :model_d has_many :model_as, through: :ad_joins end class ModelD1 < ModelD aux_table :d1 validates :d1_field1, presence: true, uniqueness: true end class AdJoin < ActiveRecord::Base belongs_to :model_a, inverse_of: :ad_joins, counter_cache: true belongs_to :model_d, inverse_of: :ad_joins, counter_cache: true end ActiveRecord::Schema.define do create_base_table :model_es do |t| t.integer :pk_base_id, index: true t.integer :fk_base_id, index: true t.create_aux :e do |t| t.integer :pk_aux_id, index: true t.integer :fk_aux_id, index: true end end end class ModelE < ActiveRecord::Base include HasAuxTable end class ModelECustom < ModelE aux_table :e belongs_to :base_to_base, class_name: "ModelECustom", foreign_key: :fk_base_id, primary_key: :pk_base_id, optional: true belongs_to :base_to_aux, class_name: "ModelECustom", foreign_key: :fk_base_id, primary_key: :pk_aux_id, optional: true belongs_to :aux_to_base, class_name: "ModelECustom", foreign_key: :fk_aux_id, primary_key: :pk_base_id, optional: true belongs_to :aux_to_aux, class_name: "ModelECustom", foreign_key: :fk_aux_id, primary_key: :pk_aux_id, optional: true has_one :owned_base_to_base, class_name: "ModelECustom", primary_key: :pk_base_id, foreign_key: :fk_base_id has_one :owned_base_to_aux, class_name: "ModelECustom", primary_key: :pk_base_id, foreign_key: :fk_aux_id has_one :owned_aux_to_base, class_name: "ModelECustom", primary_key: :pk_aux_id, foreign_key: :fk_base_id has_one :owned_aux_to_aux, class_name: "ModelECustom", primary_key: :pk_aux_id, foreign_key: :fk_aux_id has_many :owned_base_to_base_many, class_name: "ModelECustom", primary_key: :pk_base_id, foreign_key: :fk_base_id has_many :owned_base_to_aux_many, class_name: "ModelECustom", primary_key: :pk_base_id, foreign_key: :fk_aux_id has_many :owned_aux_to_base_many, class_name: "ModelECustom", primary_key: :pk_aux_id, foreign_key: :fk_base_id has_many :owned_aux_to_aux_many, class_name: "ModelECustom", primary_key: :pk_aux_id, foreign_key: :fk_aux_id end