Fix lazy-loading of ActiveRecord

An effort begun by Eric Proulx in
https://github.com/paper-trail-gem/paper_trail/pull/1237

- Replaces Engine with simpler Railtie
- Critically, defers certain `requires`
  - With Rails, defers via a Lazy Load Hook in the Railtie
  - Without Rails, just by require-ing AR first
This commit is contained in:
Eric Proulx 2020-03-12 09:57:41 +01:00 committed by Jared Beck
parent a69e678887
commit fc6c5f6941
8 changed files with 56 additions and 62 deletions

View File

@ -7,9 +7,11 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/).
### Breaking Changes ### Breaking Changes
- In the PT rails engine, the `paper_trail` configuration has been - Rails: Instead of an `Engine`, PT now provides a `Railtie`, which is simpler.
removed. This configuration object was deprecated in 10.2.0. Please review - Rails: The deprecated `config.paper_trail` configuration technique
docs section [2.d. Turning PaperTrail has been removed. This configuration object was deprecated in 10.2.0. It only
had one key, `config.paper_trail.enabled`. Please review docs section [2.d.
Turning PaperTrail
Off](https://github.com/paper-trail-gem/paper_trail/#2d-turning-papertrail-off) Off](https://github.com/paper-trail-gem/paper_trail/#2d-turning-papertrail-off)
for alternatives. for alternatives.

View File

@ -8,34 +8,17 @@
# can revisit this decision. # can revisit this decision.
require "active_support/all" require "active_support/all"
# AR is required for, eg. has_paper_trail.rb, so we could put this `require` in
# all of those files, but it seems easier to troubleshoot if we just make sure
# AR is loaded here before loading *any* of PT. See discussion of
# performance/simplicity tradeoff for activesupport above.
require "active_record"
require "request_store"
require "paper_trail/cleaner" require "paper_trail/cleaner"
require "paper_trail/compatibility" require "paper_trail/compatibility"
require "paper_trail/config" require "paper_trail/config"
require "paper_trail/has_paper_trail"
require "paper_trail/record_history" require "paper_trail/record_history"
require "paper_trail/reifier"
require "paper_trail/request" require "paper_trail/request"
require "paper_trail/version_concern"
require "paper_trail/version_number" require "paper_trail/version_number"
require "paper_trail/serializers/json" require "paper_trail/serializers/json"
require "paper_trail/serializers/yaml"
# An ActiveRecord extension that tracks changes to your models, for auditing or # An ActiveRecord extension that tracks changes to your models, for auditing or
# versioning. # versioning.
module PaperTrail module PaperTrail
E_RAILS_NOT_LOADED = <<-EOS.squish.freeze
PaperTrail has been loaded too early, before rails is loaded. This can
happen when another gem defines the ::Rails namespace, then PT is loaded,
all before rails is loaded. You may want to reorder your Gemfile, or defer
the loading of PT by using `require: false` and a manual require elsewhere.
EOS
E_TIMESTAMP_FIELD_CONFIG = <<-EOS.squish.freeze E_TIMESTAMP_FIELD_CONFIG = <<-EOS.squish.freeze
PaperTrail.timestamp_field= has been removed, without replacement. It is no PaperTrail.timestamp_field= has been removed, without replacement. It is no
longer configurable. The timestamp column in the versions table must now be longer configurable. The timestamp column in the versions table must now be
@ -126,27 +109,18 @@ module PaperTrail
end end
end end
# We use the `on_load` "hook" instead of `ActiveRecord::Base.include` because we # PT is built on ActiveRecord, but does not require Rails. If Rails is defined,
# don't want to cause all of AR to be autloaded yet. See # our Railtie makes sure not to load the AR-dependent parts of PT until AR is
# https://guides.rubyonrails.org/engines.html#what-are-on-load-hooks-questionmark # ready. A typical Rails `application.rb` has:
# to learn more about `on_load`. #
ActiveSupport.on_load(:active_record) do # ```
include PaperTrail::Model # require 'rails/all' # Defines `Rails`
end # Bundler.require(*Rails.groups) # require 'paper_trail' (this file)
# ```
# Require frameworks #
if defined?(::Rails) # Non-rails applications should take similar care to load AR before PT.
# Rails module is sometimes defined by gems like rails-html-sanitizer if defined?(Rails)
# so we check for presence of Rails.application. require "paper_trail/frameworks/rails"
if defined?(::Rails.application)
require "paper_trail/frameworks/rails"
else
::Kernel.warn(::PaperTrail::E_RAILS_NOT_LOADED)
end
else else
require "paper_trail/frameworks/active_record" require "paper_trail/frameworks/active_record"
end end
if defined?(::ActiveRecord)
::PaperTrail::Compatibility.check_activerecord(::ActiveRecord.gem_version)
end

View File

@ -1,5 +1,12 @@
# frozen_string_literal: true # frozen_string_literal: true
# This file only needs to be loaded if the gem is being used outside of Rails, # Either ActiveRecord has already been loaded by the Lazy Load Hook in our
# since otherwise the model(s) will get loaded in via the `Rails::Engine`. # Railtie, or else we load it now.
require "active_record"
::PaperTrail::Compatibility.check_activerecord(::ActiveRecord.gem_version)
# Now we can load the parts of PT that depend on AR.
require "paper_trail/has_paper_trail"
require "paper_trail/reifier"
require "paper_trail/frameworks/active_record/models/paper_trail/version" require "paper_trail/frameworks/active_record/models/paper_trail/version"
ActiveRecord::Base.include PaperTrail::Model

View File

@ -1,4 +1,3 @@
# frozen_string_literal: true # frozen_string_literal: true
require "paper_trail/frameworks/rails/controller" require "paper_trail/frameworks/rails/railtie"
require "paper_trail/frameworks/rails/engine"

View File

@ -101,9 +101,3 @@ module PaperTrail
end end
end end
end end
if defined?(::ActionController)
::ActiveSupport.on_load(:action_controller) do
include ::PaperTrail::Rails::Controller
end
end

View File

@ -1,10 +0,0 @@
# frozen_string_literal: true
module PaperTrail
module Rails
# See http://guides.rubyonrails.org/engines.html
class Engine < ::Rails::Engine
paths["app/models"] << "lib/paper_trail/frameworks/active_record/models"
end
end
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
module PaperTrail
# Represents code to load within Rails framework. See documentation in
# `rails/railtie.rb`.
# @api private
class Railtie < ::Rails::Railtie
# PaperTrail only has one initializer. The `initializer` method can take a
# `before:` or `after:` argument, but that's only relevant for railties with
# more than one initializer.
initializer "paper_trail" do
# `on_load` is a "lazy load hook". It "declares a block that will be
# executed when a Rails component is fully loaded". (See
# `active_support/lazy_load_hooks.rb`)
ActiveSupport.on_load(:action_controller) do
require "paper_trail/frameworks/rails/controller"
# Mix our extensions into `ActionController::Base`, which is `self`
# because of the `class_eval` in `lazy_load_hooks.rb`.
include PaperTrail::Rails::Controller
end
ActiveSupport.on_load(:active_record) do
require "paper_trail/frameworks/active_record"
end
end
end
end

View File

@ -7,7 +7,7 @@ require "byebug"
require_relative "support/pt_arel_helpers" require_relative "support/pt_arel_helpers"
unless File.exist?(File.expand_path("dummy_app/config/database.yml", __dir__)) unless File.exist?(File.expand_path("dummy_app/config/database.yml", __dir__))
warn "No database.yml detected for the dummy app, please run `rake prepare` first" warn "No database.yml detected for the dummy app, please run `rake install_database_yml` first"
end end
RSpec.configure do |config| RSpec.configure do |config|