简体   繁体   English

Rails控制台 - 重新加载! 模块中的第三方服务

[英]Rails console - reload! third party services in modules

My app is connected to some third-party APIs. 我的应用已连接到某些第三方API。

I have several APIconnector module-singletons that are initialized only once at application start (initialized means the client is instanciated once with the credentials retrieved from secrets) 我有几个APIconnector模块 - 单例,在应用程序启动时只初始化一次(初始化意味着客户端使用从秘密中检索的凭据进行一次实例化)

When I reload! 当我reload! the application in my console, I am losing those services and I have to exit and restart the console from scratch. 我的控制台中的应用程序,我正在丢失这些服务,我必须从头开始退出并重新启动控制台。

Basically all my connectors include a ServiceConnector module like this one 基本上我的所有连接器都包含像这样的ServiceConnector模块

module ServiceConnector
  extend ActiveSupport::Concern

  included do
    @activated = false
    @activation_attempt = false
    @client = nil

    attr_reader :client, :activated

    def self.client
      @client ||= service_client
    end

    def self.service_name
      name.gsub('Connector', '')
    end

    def self.activate
      @activation_attempt = true
      if credentials_present?
        @client = service_client
        @activated = true
      end
    end

Here is an example of a service implementation 以下是服务实现的示例

module My Connector
  include ServiceConnector

  @app_id = nil
  @api_key = nil

  def self.set_credentials(id, key)
    @app_id = id
    @api_key = key
  end

  def self.credentials_present?
    @app_id.present? and @api_key.present?
  end

  def self.service_client
    ::SomeAPI::Client.new(
      app_id: @app_id,
      api_key: @api_key
    )
  end
end

I use this pattern that lets me reuse those services outside Rails (eg Capistrano, worker without Rails, etc.). 我使用这种模式让我在Rails之外重用这些服务(例如Capistrano,没有Rails的worker等)。 In Rails I would load the services that way 在Rails中,我会以这种方式加载服务

# config/initializers/my_service.rb
if my_service_should_be_activated?
  my_service.set_credentials(
    Rails.application.secrets.my_service_app_id,
    Rails.application.secrets.my_service_app_key
  )
  my_service.activate
end

I guess that executing reload! 我想那正在执行reload! seems to clear all my instance variables including @client , @app_id , @api_key . 似乎清除了我的所有实例变量,包括@client@app_id@api_key

Is it possible to add code to be executed after a reload! 是否可以添加reload!后要执行的代码reload! ? In my case I would need to re-run the initializer. 在我的情况下,我需要重新运行初始化程序。 Or is there a way to make sure the instance variables of my services are not cleared with a reload! 或者有没有办法确保我的服务的实例变量不会被重新加载清除! ?

So I have come up with a solution involving two initializers 所以我想出了一个涉及两个初始化器的解决方案

First, a 000_initializer that will report which secrets were loaded successfully 首先,000_initializer将报告成功加载了哪些秘密

module SecretChecker
  module_function

  # Return true if all secrets are present
  def secrets?(secret_list, under:)
    secret_root = Rails.application.secrets
    if under
      if under.is_a?(Array)
        secret_root = secret_root.public_send(under.shift)&.dig(*under.map(&:to_s))
      else
        secret_root = secret_root.public_send(under)
      end
      secret_list.map do |secret|
        secret_root&.dig(secret.to_s).present?
      end
    else
      secret_list.map do |secret|
        secret_root&.public_send(secret.to_s).present?
      end
    end.reduce(:&)
  end

  def check_secrets(theme, secret_list, under: nil)
    return if secrets?(secret_list, under: under)
    message = "WARNING - Missing secrets for #{theme} - #{yield}"
    puts message and Rails.logger.warn(message)
  end
end

SecretChecker.check_secrets('Slack', %i[martine], under: [:slack, :webhooks]) do
  'Slack Notifications will not work'
end

SecretChecker.check_secrets('MongoDB', %i[user password], under: :mongodb) do
  'No Database Connection if auth is activated'
end

Then, a module to reload the services with ActiveSupport::Reloader (an example featuring Slack) 然后,使用ActiveSupport :: Reloader重新加载服务的模块(以Slack为特色的示例)

# config/initializers/0_service_activation.rb
module ServiceActivation
  def self.with_reload
    ActiveSupport::Reloader.to_prepare do
      yield
    end
  end

  module Slack
    def self.service
      ::SlackConnector
    end

    def self.should_be_activated?
      Rails.env.production? ||
      Rails.env.staging? ||
      (Rails.env.development? && ENV['ENABLE_SLACK'] == 'true')
    end

    def self.activate
      slack = service
      slack.webhook = Rails.application.secrets.slack&.dig('webhooks', 'my_webhook')
      ENV['SLACK_INTERCEPT_CHANNEL'].try do |channel|
        slack.intercept_channel = channel if channel.present?
      end
      slack.activate
      slack
    end
  end
end

[
  ...,
  ServiceActivation::Slack
] .each do |activator|
  ServiceActivation.with_reload do
    activator.activate if activator.should_be_activated?
    activator.service.status_report
  end
end

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM