For setting up "big" Rails upgrades that could take a while and still allow for feature work to continue while the upgrade is in progress, at Merchants, we put together a process that combines dual booting with Shopify's bootboot
gem along with adding a DeprecationSubscriber
as described by Ali in this blog post.
Here I'm sharing the DeprecationSubscriber
piece of this. The challenges around this were finding the correct placement/initialization process of the subscriber to ensure that it caught as many deprecations as we could, and including useful information in the local exception output and the Sentry errors.
The benefits are being able to have deprecation warning bubble up in a much more noticeable way as we're working through the Rails upgrade. This includes in tests, and usage in their staging and production environments
This was a joint effort between A.J. Hekman and myself, but my contributions were meaningful enough that it seemed appropriate for this group.
# config/initializers/deprecation_subscriber.rb
class DeprecationSubscriber < ActiveSupport::Subscriber
class UnallowedDeprecation < StandardError
def initialize(message)
super("Unallowed deprecation found. Please fix it.\n#{message}")
end
end
attach_to :rails
# Rails 6.1 deprecations that do not need to be resolved before shipping 6.1
# Deprecations listed here should be removed before shipping Rails 7
RAILS_6_1_DEPRECATIONS = [
"Rendering actions with '.' in the name is deprecated"
]
# Known deprecations that we are going to fix.
# As deprecations are fixed, they should be removed from this list to prevent regressions.
ALLOWED_DEPRECATIONS = [
# Autoloading autoloadable constants will cause a NameError when classic mode
# is removed.
# *** In MBC, we need to take another look at this. Several more constants are autoloaded
# *** than active support. We may need to move some of the initializers to a "load once" path,
# *** for example.
"Initialization autoloaded the constants"
]
# append RAILS_6_1_DEPRECATIONS to ALLOWED_DEPRECATIONS
ALLOWED_DEPRECATIONS.concat(RAILS_6_1_DEPRECATIONS)
def deprecation(event)
return if ALLOWED_DEPRECATIONS.any? { |allowed| event.payload[:message].include?(allowed) }
exception = UnallowedDeprecation.new(event.payload[:message])
exception.set_backtrace(event.payload[:callstack].map(&:to_s))
if Rails.env.development? || Rails.env.test?
raise exception
else
Rails.logger.warn("Unallowed deprecation found\n#{event.payload[:message]}")
if defined?(Sentry) && !ENV["DEPENDENCIES_NEXT"]
Sentry.capture_exception(exception)
end
end
end
end
# config/environments/*.rb
# Send deprecation notices to subscriber.
REMOVE: config.active_support.deprecation = :log
ADD: config.active_support.deprecation = :notify