373 lines
8.9 KiB
Ruby
373 lines
8.9 KiB
Ruby
# 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.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
|
|
|
|
# A* has_many B
|
|
# A1 has_many C
|
|
# B belongs_to A
|
|
# C belongs_to A1
|
|
end
|
|
|
|
class ModelA < ActiveRecord::Base
|
|
include HasAuxTable
|
|
has_many :model_bs, inverse_of: :model_a
|
|
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
|