lib/dotenv/rails.rb



# Since rubygems doesn't support optional dependencies, we have to manually check
unless Gem::Requirement.new(">= 6.1").satisfied_by?(Gem::Version.new(Rails.version))
  warn "dotenv 3.0 only supports Rails 6.1 or later. Use dotenv ~> 2.0."
  return
end

require "dotenv/replay_logger"
require "dotenv/log_subscriber"

Dotenv.instrumenter = ActiveSupport::Notifications

# Watch all loaded env files with Spring
ActiveSupport::Notifications.subscribe("load.dotenv") do |*args|
  if defined?(Spring) && Spring.respond_to?(:watch)
    event = ActiveSupport::Notifications::Event.new(*args)
    Spring.watch event.payload[:env].filename if Rails.application
  end
end

module Dotenv
  # Rails integration for using Dotenv to load ENV variables from a file
  class Rails < ::Rails::Railtie
    delegate :files, :files=, :overwrite, :overwrite=, :autorestore, :autorestore=, :logger, to: "config.dotenv"

    def initialize
      super
      config.dotenv = ActiveSupport::OrderedOptions.new.update(
        # Rails.logger is not available yet, so we'll save log messages and replay them when it is
        logger: Dotenv::ReplayLogger.new,
        overwrite: false,
        files: [
          ".env.#{env}.local",
          (".env.local" unless env.test?),
          ".env.#{env}",
          ".env"
        ].compact,
        autorestore: env.test? && !defined?(ClimateControl) && !defined?(IceAge)
      )
    end

    # Public: Load dotenv
    #
    # This will get called during the `before_configuration` callback, but you
    # can manually call `Dotenv::Rails.load` if you needed it sooner.
    def load
      Dotenv.load(*files.map { |file| root.join(file).to_s }, overwrite: overwrite)
    end

    def overload
      deprecator.warn("Dotenv::Rails.overload is deprecated. Set `Dotenv::Rails.overwrite = true` and call Dotenv::Rails.load instead.")
      Dotenv.load(*files.map { |file| root.join(file).to_s }, overwrite: true)
    end

    # Internal: `Rails.root` is nil in Rails 4.1 before the application is
    # initialized, so this falls back to the `RAILS_ROOT` environment variable,
    # or the current working directory.
    def root
      ::Rails.root || Pathname.new(ENV["RAILS_ROOT"] || Dir.pwd)
    end

    # Set a new logger and replay logs
    def logger=(new_logger)
      logger.replay new_logger if logger.is_a?(ReplayLogger)
      config.dotenv.logger = new_logger
    end

    # The current environment that the app is running in.
    #
    # When running `rake`, the Rails application is initialized in development, so we have to
    # check which rake tasks are being run to determine the environment.
    #
    # See https://github.com/bkeepers/dotenv/issues/219
    def env
      @env ||= if defined?(Rake.application) && Rake.application.top_level_tasks.grep(TEST_RAKE_TASKS).any?
        env = Rake.application.options.show_tasks ? "development" : "test"
        ActiveSupport::EnvironmentInquirer.new(env)
      else
        ::Rails.env
      end
    end
    TEST_RAKE_TASKS = /^(default$|test(:|$)|parallel:spec|spec(:|$))/

    def deprecator # :nodoc:
      @deprecator ||= ActiveSupport::Deprecation.new
    end

    # Rails uses `#method_missing` to delegate all class methods to the
    # instance, which means `Kernel#load` gets called here. We don't want that.
    def self.load
      instance.load
    end

    initializer "dotenv", after: :initialize_logger do |app|
      if logger.is_a?(ReplayLogger)
        self.logger = ActiveSupport::TaggedLogging.new(::Rails.logger).tagged("dotenv")
      end
    end

    initializer "dotenv.deprecator" do |app|
      app.deprecators[:dotenv] = deprecator if app.respond_to?(:deprecators)
    end

    initializer "dotenv.autorestore" do |app|
      require "dotenv/autorestore" if autorestore
    end

    config.before_configuration { load }
  end

  Railtie = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("Dotenv::Railtie", "Dotenv::Rails", Dotenv::Rails.deprecator)
end