spec refactor
This commit is contained in:
@@ -2,4 +2,4 @@
|
|||||||
.
|
.
|
||||||
--ignore=/tmp/
|
--ignore=/tmp/
|
||||||
--ignore=/vendor/bundle
|
--ignore=/vendor/bundle
|
||||||
--enable-experimental-requires-ancestor
|
--enable-experimental-requires-ancestor
|
||||||
|
|||||||
@@ -1,155 +1,8 @@
|
|||||||
|
# typed: false
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
RSpec.describe HasAuxTable do
|
RSpec.describe HasAuxTable do
|
||||||
LOG_QUERIES = false
|
before(:all) { SpecHelper.initialize_spec_schema! }
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# Car class will be defined after schema setup
|
# 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
|
it "allows saving the model with auxiliary columns" do
|
||||||
car = Car.create!(name: "Honda Civic")
|
car = Car.create!(name: "Honda Civic")
|
||||||
num_queries =
|
num_queries =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
car.fuel_type = "hybrid"
|
car.fuel_type = "hybrid"
|
||||||
car.engine_size = 1.8
|
car.engine_size = 1.8
|
||||||
car.save!
|
car.save!
|
||||||
@@ -450,7 +303,7 @@ RSpec.describe HasAuxTable do
|
|||||||
|
|
||||||
it "loads single model with auxiliary data in one query using find" do
|
it "loads single model with auxiliary data in one query using find" do
|
||||||
query_count =
|
query_count =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
car = Car.find(@car1.id)
|
car = Car.find(@car1.id)
|
||||||
# Access auxiliary attributes to ensure they're loaded
|
# Access auxiliary attributes to ensure they're loaded
|
||||||
car.fuel_type
|
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
|
it "loads single model with auxiliary data in one query using find_by" do
|
||||||
query_count =
|
query_count =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
car = Car.find_by(name: "Toyota Prius")
|
car = Car.find_by(name: "Toyota Prius")
|
||||||
# Access auxiliary attributes to ensure they're loaded
|
# Access auxiliary attributes to ensure they're loaded
|
||||||
car.fuel_type
|
car.fuel_type
|
||||||
@@ -474,7 +327,7 @@ RSpec.describe HasAuxTable do
|
|||||||
|
|
||||||
it "loads multiple models with auxiliary data in one query using where" do
|
it "loads multiple models with auxiliary data in one query using where" do
|
||||||
query_count =
|
query_count =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
cars = Car.where(fuel_type: %w[hybrid electric])
|
cars = Car.where(fuel_type: %w[hybrid electric])
|
||||||
# Access auxiliary attributes for all cars
|
# Access auxiliary attributes for all cars
|
||||||
cars.each do |car|
|
cars.each do |car|
|
||||||
@@ -499,7 +352,7 @@ RSpec.describe HasAuxTable do
|
|||||||
|
|
||||||
cars = nil
|
cars = nil
|
||||||
query_count =
|
query_count =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
cars = Car.where(fuel_type: "gasoline")
|
cars = Car.where(fuel_type: "gasoline")
|
||||||
# Access auxiliary attributes for all cars - should not trigger additional queries
|
# Access auxiliary attributes for all cars - should not trigger additional queries
|
||||||
cars.each do |car|
|
cars.each do |car|
|
||||||
@@ -515,7 +368,7 @@ RSpec.describe HasAuxTable do
|
|||||||
|
|
||||||
it "uses single query when ordering by auxiliary columns" do
|
it "uses single query when ordering by auxiliary columns" do
|
||||||
query_count =
|
query_count =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
cars = Car.where(engine_size: 1.0..3.0).order(:engine_size)
|
cars = Car.where(engine_size: 1.0..3.0).order(:engine_size)
|
||||||
# Access all attributes
|
# Access all attributes
|
||||||
cars.each do |car|
|
cars.each do |car|
|
||||||
@@ -530,7 +383,7 @@ RSpec.describe HasAuxTable do
|
|||||||
|
|
||||||
it "uses single query for complex auxiliary column queries" do
|
it "uses single query for complex auxiliary column queries" do
|
||||||
query_count =
|
query_count =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
cars =
|
cars =
|
||||||
Car.where(fuel_type: "hybrid").or(Car.where(engine_size: 0.0))
|
Car.where(fuel_type: "hybrid").or(Car.where(engine_size: 0.0))
|
||||||
# Access all attributes
|
# Access all attributes
|
||||||
@@ -546,7 +399,7 @@ RSpec.describe HasAuxTable do
|
|||||||
|
|
||||||
it "uses single query when finding by auxiliary columns" do
|
it "uses single query when finding by auxiliary columns" do
|
||||||
query_count =
|
query_count =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
car = Car.find_by(fuel_type: "hybrid", name: "Toyota Prius")
|
car = Car.find_by(fuel_type: "hybrid", name: "Toyota Prius")
|
||||||
# Access all attributes
|
# Access all attributes
|
||||||
car.name
|
car.name
|
||||||
@@ -563,7 +416,7 @@ RSpec.describe HasAuxTable do
|
|||||||
|
|
||||||
# Now count queries when accessing auxiliary attributes
|
# Now count queries when accessing auxiliary attributes
|
||||||
query_count =
|
query_count =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
car.fuel_type
|
car.fuel_type
|
||||||
car.engine_size
|
car.engine_size
|
||||||
car.fuel_type? # presence check
|
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
|
it "handles mixed queries with main and auxiliary columns in single query" do
|
||||||
query_count =
|
query_count =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
cars = Car.where(name: "Toyota Prius", fuel_type: "hybrid")
|
cars = Car.where(name: "Toyota Prius", fuel_type: "hybrid")
|
||||||
cars.each do |car|
|
cars.each do |car|
|
||||||
car.name
|
car.name
|
||||||
@@ -590,7 +443,7 @@ RSpec.describe HasAuxTable do
|
|||||||
|
|
||||||
it "uses single query for range queries on auxiliary columns" do
|
it "uses single query for range queries on auxiliary columns" do
|
||||||
query_count =
|
query_count =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
cars = Car.where(engine_size: 0.0..1.9)
|
cars = Car.where(engine_size: 0.0..1.9)
|
||||||
cars.each do |car|
|
cars.each do |car|
|
||||||
car.name
|
car.name
|
||||||
@@ -604,7 +457,7 @@ RSpec.describe HasAuxTable do
|
|||||||
|
|
||||||
it "maintains single query performance with limit and offset" do
|
it "maintains single query performance with limit and offset" do
|
||||||
query_count =
|
query_count =
|
||||||
count_queries do
|
SpecHelper.count_queries do
|
||||||
car = Car.where(fuel_type: %w[hybrid electric]).limit(1).first
|
car = Car.where(fuel_type: %w[hybrid electric]).limit(1).first
|
||||||
# Access auxiliary attributes
|
# Access auxiliary attributes
|
||||||
car.fuel_type
|
car.fuel_type
|
||||||
@@ -814,7 +667,7 @@ RSpec.describe HasAuxTable do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "reloads with one query" do
|
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)
|
expect(num_queries).to eq(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -846,12 +699,12 @@ RSpec.describe HasAuxTable do
|
|||||||
expect(Car.count).to eq(1)
|
expect(Car.count).to eq(1)
|
||||||
expect(Boat.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.fuel_type).to eq("gasoline")
|
||||||
expect(car.engine_size).to eq(2.0)
|
expect(car.engine_size).to eq(2.0)
|
||||||
expect(car.name).to eq("Honda Civic")
|
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)
|
expect(boat.only_freshwater).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# typed: strict
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require "active_record"
|
require "active_record"
|
||||||
@@ -29,3 +30,107 @@ RSpec.configure do |config|
|
|||||||
end
|
end
|
||||||
end
|
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