# typed: strict # frozen_string_literal: true require "simplecov" require "simplecov-lcov" SimpleCov::Formatter::LcovFormatter.config do |c| c.report_with_single_file = true c.lcov_file_name = "lcov.info" end SimpleCov.start do enable_coverage :branch add_filter "spec" formatter( SimpleCov::Formatter::MultiFormatter.new( [ SimpleCov::Formatter::LcovFormatter, # Add Lcov as an output when generating code coverage report SimpleCov::Formatter::HTMLFormatter # Add other outputs for the code coverage report ] ) ) end require "pry" require "active_record" require "active_record/errors" require "has_aux_table" # Configure ActiveRecord to use in-memory SQLite database ActiveRecord::Base.establish_connection( adapter: "sqlite3", database: ":memory:" ) require_relative "spec_models" RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" # Disable RSpec exposing methods globally on `Module` and `main` config.disable_monkey_patching! config.backtrace_inclusion_patterns = [/\bactiverecord\b/] config.expect_with :rspec do |c| c.syntax = :expect end config.around(:each) do |example| ActiveRecord::Base.transaction do example.run raise ActiveRecord::Rollback end end config.include( Module.new do extend T::Sig sig do type_parameters(:T) .params(block: T.proc.returns(T.type_parameter(:T))) .returns(SpecHelper::CaptureQueries[T.type_parameter(:T)]) end def capture_queries(&block) SpecHelper.capture_queries(&block) end end ) config.extend( Module.new do extend T::Sig sig do type_parameters(:T) .params(binding: Symbol, block: T.proc.returns(T.type_parameter(:T))) .returns(T.type_parameter(:T)) end def let_and_capture(binding, &block) T.bind(self, RSpec::Core::MemoizedHelpers::ClassMethods) let(binding) { SpecHelper.capture_queries(&block) } end end ) end module SpecHelper extend T::Sig extend T::Helpers LOG_QUERIES = T.let(false, T::Boolean) class CaptureQueries < Array extend T::Sig extend T::Generic Elem = type_member { { fixed: String } } Result = type_member sig { params(queries: T::Array[String], result: Result).void } def initialize(queries, result) super(queries) @result = result end sig { returns(Result) } attr_reader :result end # Helper method to count queries sig do type_parameters(:T) .params(block: T.proc.returns(T.type_parameter(:T))) .returns(CaptureQueries[T.type_parameter(:T)]) end def self.capture_queries(&block) queries = T.let([], T::Array[String]) query_callback = lambda do |name, start, finish, message_id, values| queries << values[:sql] end result = T.let(nil, T.untyped) 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 result = block.call ensure ActiveRecord::Base.logger = old_logger if LOG_QUERIES end CaptureQueries.new(queries, result) end end