Skip to content

Commit

Permalink
Use active support lazy load hooks
Browse files Browse the repository at this point in the history
Extend `ActiveRecord::Base` functionality only after `ActiveRecord` has
been fully loaded.

Ref: thoughtbot/factory_bot_rails#426


Close #231
  • Loading branch information
tagliala committed Oct 19, 2023
1 parent cfcc9fd commit 7eba89b
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 89 deletions.
65 changes: 16 additions & 49 deletions lib/chrono_model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

require 'active_record'

require 'chrono_model/chrono'
require 'chrono_model/conversions'
require 'chrono_model/patches'
require 'chrono_model/adapter'
require 'chrono_model/time_machine'
require 'chrono_model/time_gate'
require 'chrono_model/version'
require 'chrono_model/railtie'

module ChronoModel
class Error < ActiveRecord::ActiveRecordError # :nodoc:
Expand All @@ -33,59 +35,24 @@ def self.history_models
end
end

if defined?(Rails::Railtie)
require 'chrono_model/railtie'
end
ActiveSupport.on_load :active_record do
extend ChronoModel::Chrono

ActiveRecord::Base.instance_eval do
# Checks whether this Active Recoed model is backed by a temporal table
#
def chrono?
return false unless connection.respond_to? :is_chrono?
# Hooks into Association#scope to pass the As-Of time automatically
# to methods that load associated ChronoModel records.
ActiveRecord::Associations::Association.prepend ChronoModel::Patches::Association

connection.is_chrono?(table_name)
end
end
# Hooks into Relation#build_arel to use `:joins` on your ChronoModels
# and join data from associated records As-Of time.
ActiveRecord::Relation.prepend ChronoModel::Patches::Relation

# Hooks into Association#scope to pass the As-Of time automatically
# to methods that load associated ChronoModel records.
#
ActiveRecord::Associations::Association.instance_eval do
prepend ChronoModel::Patches::Association
end
# Hooks in two points of the AR Preloader to preload As-Of time records of
# associated ChronoModels. is used by `.includes`, `.preload`, and `.eager_load`.
ActiveRecord::Associations::Preloader.prepend ChronoModel::Patches::Preloader

# Hooks into Relation#build_arel to use :joins on your ChronoModels
# and join data from associated records As-Of time.
#
ActiveRecord::Relation.instance_eval do
prepend ChronoModel::Patches::Relation
end
ActiveRecord::Associations::Preloader::Association.prepend ChronoModel::Patches::Preloader::Association

# Hooks in two points of the AR Preloader to preload As-Of time records of
# associated ChronoModels. is used by .includes, .preload and .eager_load.
#
ActiveRecord::Associations::Preloader.instance_eval do
prepend ChronoModel::Patches::Preloader
end
ActiveRecord::Associations::Preloader::ThroughAssociation.prepend ChronoModel::Patches::Preloader::ThroughAssociation

ActiveRecord::Associations::Preloader::Association.instance_eval do
prepend ChronoModel::Patches::Preloader::Association
end

ActiveRecord::Associations::Preloader::ThroughAssociation.instance_eval do
prepend ChronoModel::Patches::Preloader::ThroughAssociation
end

ActiveRecord::Batches::BatchEnumerator.instance_eval do
prepend ChronoModel::Patches::Batches::BatchEnumerator
end

if defined?(Rails::DBConsole) && Rails.version < '7.1'
Rails::DBConsole.instance_eval do
if Rails.version < '6.1'
prepend ChronoModel::Patches::DBConsole::Config
else
prepend ChronoModel::Patches::DBConsole::DbConfig
end
end
ActiveRecord::Batches::BatchEnumerator.prepend ChronoModel::Patches::Batches::BatchEnumerator
end
17 changes: 17 additions & 0 deletions lib/chrono_model/chrono.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module ChronoModel
# A module to add to ActiveRecord::Base to check if they are backed by
# temporal tables.
module Chrono
# Checks whether this Active Record model is backed by a temporal table
#
# @return [Boolean] false if the connection does not respond to is_chrono?
# the result of connection.is_chrono?(table_name) otherwise
def chrono?
return false unless connection.respond_to? :is_chrono?

connection.is_chrono?(table_name)
end
end
end
88 changes: 48 additions & 40 deletions lib/chrono_model/railtie.rb
Original file line number Diff line number Diff line change
@@ -1,54 +1,62 @@
# frozen_string_literal: true

require 'active_record/tasks/chronomodel_database_tasks'

module ChronoModel
class Railtie < ::Rails::Railtie
TASKS_CLASS = ActiveRecord::Tasks::ChronomodelDatabaseTasks
if defined?(Rails)
module ChronoModel
class Railtie < ::Rails::Railtie
rake_tasks do
load 'active_record/tasks/chronomodel_database_tasks.rb'

def task_config
if Rails.version < '6.1'
ActiveRecord::Tasks::DatabaseTasks.current_config.with_indifferent_access
else
ActiveRecord::Base.connection_db_config
end
end

def task_config
if Rails.version < '6.1'
ActiveRecord::Tasks::DatabaseTasks.current_config.with_indifferent_access
else
ActiveRecord::Base.connection_db_config
end
end
# Register our database tasks under our adapter name
if Rails.version < '5.2'
ActiveRecord::Tasks::DatabaseTasks.register_task(/chronomodel/, ActiveRecord::Tasks::ChronomodelDatabaseTasks)
else
ActiveRecord::Tasks::DatabaseTasks.register_task(/chronomodel/, 'ActiveRecord::Tasks::ChronomodelDatabaseTasks')
end

# Register our database tasks under our adapter name
if Rails.version < '5.2'
ActiveRecord::Tasks::DatabaseTasks.register_task(/chronomodel/, TASKS_CLASS)
else
ActiveRecord::Tasks::DatabaseTasks.register_task(/chronomodel/, TASKS_CLASS.to_s)
end
if Rails.application.config.active_record.schema_format != :sql
raise 'In order to use ChronoModel, config.active_record.schema_format must be :sql!'
end

rake_tasks do
if Rails.application.config.active_record.schema_format != :sql
raise 'In order to use ChronoModel, config.active_record.schema_format must be :sql!'
end
if Rails.version < '6.1'
# Make schema:dump and schema:load invoke structure:dump and structure:load
Rake::Task['db:schema:dump'].clear.enhance(['environment']) do
Rake::Task['db:structure:dump'].invoke
end

if Rails.version < '6.1'
# Make schema:dump and schema:load invoke structure:dump and structure:load
Rake::Task['db:schema:dump'].clear.enhance(['environment']) do
Rake::Task['db:structure:dump'].invoke
Rake::Task['db:schema:load'].clear.enhance(['environment']) do
Rake::Task['db:structure:load'].invoke
end
end

Rake::Task['db:schema:load'].clear.enhance(['environment']) do
Rake::Task['db:structure:load'].invoke
desc 'Dumps database into db/data.NOW.sql or file specified via DUMP='
task 'db:data:dump' => :environment do
target = ENV['DUMP'] || Rails.root.join('db', "data.#{Time.now.to_f}.sql")
ActiveRecord::Tasks::ChronomodelDatabaseTasks.new(task_config).data_dump(target)
end
end

desc 'Dumps database into db/data.NOW.sql or file specified via DUMP='
task 'db:data:dump' => :environment do
target = ENV['DUMP'] || Rails.root.join('db', "data.#{Time.now.to_f}.sql")
TASKS_CLASS.new(task_config).data_dump(target)
end

desc 'Loads database dump from file specified via DUMP='
task 'db:data:load' => :environment do
source = ENV['DUMP'].presence or
raise ArgumentError, 'Invoke as rake db:data:load DUMP=/path/to/data.sql'
TASKS_CLASS.new(task_config).data_load(source)
desc 'Loads database dump from file specified via DUMP='
task 'db:data:load' => :environment do
source = ENV['DUMP'].presence or
raise ArgumentError, 'Invoke as rake db:data:load DUMP=/path/to/data.sql'
ActiveRecord::Tasks::ChronomodelDatabaseTasks.new(task_config).data_load(source)
end
end
end
end
end

if defined?(Rails::DBConsole) && Rails.version < '7.1'
if Rails.version < '6.1'
Rails::DBConsole.prepend ChronoModel::Patches::DBConsole::Config
else
Rails::DBConsole.prepend ChronoModel::Patches::DBConsole::DbConfig
end
end

0 comments on commit 7eba89b

Please sign in to comment.