Compare commits
2 Commits
3a80c2b8dd
...
8e1c193801
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e1c193801 | ||
|
|
ca9eae138a |
@@ -32,12 +32,7 @@ module HasAuxTable
|
||||
module ClassMethods
|
||||
extend T::Sig
|
||||
extend T::Helpers
|
||||
requires_ancestor { Kernel }
|
||||
requires_ancestor { T.class_of(BasicObject) }
|
||||
requires_ancestor { T.class_of(ActiveRecord::Base) }
|
||||
requires_ancestor { ActiveRecord::ModelSchema::ClassMethods }
|
||||
requires_ancestor { ActiveRecord::Associations::ClassMethods }
|
||||
requires_ancestor { ActiveModel::Attributes::ClassMethods }
|
||||
|
||||
include RelationExtensions
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
.
|
||||
--ignore=/tmp/
|
||||
--ignore=/vendor/bundle
|
||||
--enable-experimental-requires-ancestor
|
||||
--enable-experimental-requires-ancestor
|
||||
|
||||
@@ -1,155 +1,8 @@
|
||||
# typed: false
|
||||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe HasAuxTable do
|
||||
LOG_QUERIES = false
|
||||
|
||||
# Helper method to count queries
|
||||
def count_queries(&block)
|
||||
query_count = 0
|
||||
query_callback =
|
||||
lambda { |name, start, finish, message_id, values| query_count += 1 }
|
||||
|
||||
ActiveSupport::Notifications.subscribed(
|
||||
query_callback,
|
||||
"sql.active_record"
|
||||
) do
|
||||
if LOG_QUERIES
|
||||
old_logger = ActiveRecord::Base.logger
|
||||
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
||||
ActiveRecord::Base.logger.level = Logger::DEBUG
|
||||
end
|
||||
block.call
|
||||
ActiveRecord::Base.logger = old_logger if LOG_QUERIES
|
||||
end
|
||||
|
||||
query_count
|
||||
end
|
||||
|
||||
before(:all) do
|
||||
# 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
|
||||
|
||||
t.create_aux :boat do |t|
|
||||
t.boolean :only_freshwater
|
||||
end
|
||||
end
|
||||
|
||||
create_base_table :people do |t|
|
||||
t.string :name
|
||||
t.timestamps
|
||||
|
||||
t.create_aux :driver do |t|
|
||||
t.integer :license_number
|
||||
t.references :car,
|
||||
null: false,
|
||||
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 :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
|
||||
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 Person < ActiveRecord::Base
|
||||
include HasAuxTable
|
||||
self.table_name = "people"
|
||||
end
|
||||
|
||||
class Driver < Person
|
||||
aux_table :driver
|
||||
belongs_to :car, inverse_of: :drivers
|
||||
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
|
||||
end
|
||||
before(:all) { SpecHelper.initialize_spec_schema! }
|
||||
|
||||
# Car class will be defined after schema setup
|
||||
|
||||
@@ -270,7 +123,7 @@ RSpec.describe HasAuxTable do
|
||||
it "allows saving the model with auxiliary columns" do
|
||||
car = Car.create!(name: "Honda Civic")
|
||||
num_queries =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
car.fuel_type = "hybrid"
|
||||
car.engine_size = 1.8
|
||||
car.save!
|
||||
@@ -450,7 +303,7 @@ RSpec.describe HasAuxTable do
|
||||
|
||||
it "loads single model with auxiliary data in one query using find" do
|
||||
query_count =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
car = Car.find(@car1.id)
|
||||
# Access auxiliary attributes to ensure they're loaded
|
||||
car.fuel_type
|
||||
@@ -462,7 +315,7 @@ RSpec.describe HasAuxTable do
|
||||
|
||||
it "loads single model with auxiliary data in one query using find_by" do
|
||||
query_count =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
car = Car.find_by(name: "Toyota Prius")
|
||||
# Access auxiliary attributes to ensure they're loaded
|
||||
car.fuel_type
|
||||
@@ -474,7 +327,7 @@ RSpec.describe HasAuxTable do
|
||||
|
||||
it "loads multiple models with auxiliary data in one query using where" do
|
||||
query_count =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
cars = Car.where(fuel_type: %w[hybrid electric])
|
||||
# Access auxiliary attributes for all cars
|
||||
cars.each do |car|
|
||||
@@ -499,7 +352,7 @@ RSpec.describe HasAuxTable do
|
||||
|
||||
cars = nil
|
||||
query_count =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
cars = Car.where(fuel_type: "gasoline")
|
||||
# Access auxiliary attributes for all cars - should not trigger additional queries
|
||||
cars.each do |car|
|
||||
@@ -515,7 +368,7 @@ RSpec.describe HasAuxTable do
|
||||
|
||||
it "uses single query when ordering by auxiliary columns" do
|
||||
query_count =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
cars = Car.where(engine_size: 1.0..3.0).order(:engine_size)
|
||||
# Access all attributes
|
||||
cars.each do |car|
|
||||
@@ -530,7 +383,7 @@ RSpec.describe HasAuxTable do
|
||||
|
||||
it "uses single query for complex auxiliary column queries" do
|
||||
query_count =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
cars =
|
||||
Car.where(fuel_type: "hybrid").or(Car.where(engine_size: 0.0))
|
||||
# Access all attributes
|
||||
@@ -546,7 +399,7 @@ RSpec.describe HasAuxTable do
|
||||
|
||||
it "uses single query when finding by auxiliary columns" do
|
||||
query_count =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
car = Car.find_by(fuel_type: "hybrid", name: "Toyota Prius")
|
||||
# Access all attributes
|
||||
car.name
|
||||
@@ -563,7 +416,7 @@ RSpec.describe HasAuxTable do
|
||||
|
||||
# Now count queries when accessing auxiliary attributes
|
||||
query_count =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
car.fuel_type
|
||||
car.engine_size
|
||||
car.fuel_type? # presence check
|
||||
@@ -576,7 +429,7 @@ RSpec.describe HasAuxTable do
|
||||
|
||||
it "handles mixed queries with main and auxiliary columns in single query" do
|
||||
query_count =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
cars = Car.where(name: "Toyota Prius", fuel_type: "hybrid")
|
||||
cars.each do |car|
|
||||
car.name
|
||||
@@ -590,7 +443,7 @@ RSpec.describe HasAuxTable do
|
||||
|
||||
it "uses single query for range queries on auxiliary columns" do
|
||||
query_count =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
cars = Car.where(engine_size: 0.0..1.9)
|
||||
cars.each do |car|
|
||||
car.name
|
||||
@@ -604,7 +457,7 @@ RSpec.describe HasAuxTable do
|
||||
|
||||
it "maintains single query performance with limit and offset" do
|
||||
query_count =
|
||||
count_queries do
|
||||
SpecHelper.count_queries do
|
||||
car = Car.where(fuel_type: %w[hybrid electric]).limit(1).first
|
||||
# Access auxiliary attributes
|
||||
car.fuel_type
|
||||
@@ -814,7 +667,7 @@ RSpec.describe HasAuxTable do
|
||||
end
|
||||
|
||||
it "reloads with one query" do
|
||||
num_queries = count_queries { @car.reload }
|
||||
num_queries = SpecHelper.count_queries { @car.reload }
|
||||
expect(num_queries).to eq(1)
|
||||
end
|
||||
end
|
||||
@@ -846,12 +699,12 @@ RSpec.describe HasAuxTable do
|
||||
expect(Car.count).to eq(1)
|
||||
expect(Boat.count).to eq(1)
|
||||
|
||||
expect(count_queries { car = Vehicle.find(car.id) }).to eq(1)
|
||||
expect(SpecHelper.count_queries { car = Vehicle.find(car.id) }).to eq(1)
|
||||
expect(car.fuel_type).to eq("gasoline")
|
||||
expect(car.engine_size).to eq(2.0)
|
||||
expect(car.name).to eq("Honda Civic")
|
||||
|
||||
expect(count_queries { boat = Vehicle.find(boat.id) }).to eq(1)
|
||||
expect(SpecHelper.count_queries { boat = Vehicle.find(boat.id) }).to eq(1)
|
||||
expect(boat.only_freshwater).to eq(true)
|
||||
end
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "active_record"
|
||||
@@ -29,3 +30,107 @@ RSpec.configure do |config|
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module SpecHelper
|
||||
extend T::Sig
|
||||
extend T::Helpers
|
||||
LOG_QUERIES = T.let(false, T::Boolean)
|
||||
|
||||
# Helper method to count queries
|
||||
sig { params(block: T.proc.void).returns(Integer) }
|
||||
def self.count_queries(&block)
|
||||
query_count = 0
|
||||
query_callback =
|
||||
lambda { |name, start, finish, message_id, values| query_count += 1 }
|
||||
|
||||
ActiveSupport::Notifications.subscribed(
|
||||
query_callback,
|
||||
"sql.active_record"
|
||||
) do
|
||||
if LOG_QUERIES
|
||||
old_logger = ActiveRecord::Base.logger
|
||||
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
||||
ActiveRecord::Base.logger.level = Logger::DEBUG
|
||||
end
|
||||
block.call
|
||||
ActiveRecord::Base.logger = old_logger if LOG_QUERIES
|
||||
end
|
||||
|
||||
query_count
|
||||
end
|
||||
|
||||
sig { void }
|
||||
def self.initialize_spec_schema!
|
||||
# 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
|
||||
|
||||
t.create_aux :boat do |t|
|
||||
t.boolean :only_freshwater
|
||||
end
|
||||
end
|
||||
|
||||
create_base_table :people do |t|
|
||||
t.string :name
|
||||
t.timestamps
|
||||
|
||||
t.create_aux :driver do |t|
|
||||
t.integer :license_number
|
||||
t.references :car,
|
||||
null: false,
|
||||
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 :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
|
||||
|
||||
require_relative "spec_models"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
56
spec/spec_models.rb
Normal file
56
spec/spec_models.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
# typed: strict
|
||||
# frozen_string_literal: true
|
||||
|
||||
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 Person < ActiveRecord::Base
|
||||
include HasAuxTable
|
||||
self.table_name = "people"
|
||||
end
|
||||
|
||||
class Driver < Person
|
||||
aux_table :driver
|
||||
belongs_to :car, inverse_of: :drivers
|
||||
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
|
||||
Reference in New Issue
Block a user