Динамически менять службу активного хранилища на модели рельсов?

Я использую активное хранилище с s3, чтобы прикрепить файл к модели под названием Document. Мне нужно добавить поддержку пользователей из ЕС, которые хотят, чтобы их файлы документов хранились в корзине s3 в ЕС.

У меня есть файл Storage.yml, настроенный следующим образом:

amazon:
    service: S3
    access_key_id: <%= ENV['S3_ACCESS_KEY_ID'] %>
    secret_access_key: <%= ENV['S3_SECRET_KEY_ACCESS'] %>
    region: <%= ENV['S3_REGION_EU'] %>
    bucket: <%= ENV['S3_BUCKET_NAME'] %>

amazon_eu:
    service: S3
    access_key_id: <%= ENV['S3_ACCESS_KEY_ID'] %>
    secret_access_key: <%= ENV['S3_SECRET_KEY_ACCESS'] %>
    region: <%= ENV['S3_REGION_EU'] %>
    bucket: <%= ENV['S3_BUCKET_NAME_EU'] %>

Есть ли способ динамически переключать службу в модели документа на основе региона, установленного в учетной записи? Что-то вроде:

class Document < ApplicationRecord
  belongs_to :account
  
  if account.region == 'eu'
    has_one_attached :file, service: amazon_eu
  else
    has_one_attached :file, service: amazon
  end

end

Или вообще указать конкретную службу, которую я хочу использовать динамически во время выполнения?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
0
54
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Возможно, это не то решение, которое вы ищете, но вы можете рассмотреть возможность использования наследования одной таблицы и определения отдельного класса:

class EUDocument < Document
  belongs_to :account
  has_one_attached :file, service: :amazon_eu
end

ActiveStorage построен на основе макроса уровня класса и на самом деле не имеет каких-либо документированных функций, таких как реконфигурация уровня экземпляра.

Хотя вы можете делать сумасшедшие хаки, например использовать class_eval или заглушать методы экземпляра, сгенерированные макросом, это будет очень хрупко.

Если это является препятствием, вы можете рассмотреть возможность использования драгоценного камня S3 напрямую для реализации этой функции, поскольку ActiveStorage — это очень самоуверенный дизайн, который в то же время пытается быть сверхгибким.

В качестве альтернативы, если ваш трафик из ЕС проходит через другие серверы, вы можете использовать переменную ENV для настройки региона для каждого сервера.

max 05.09.2024 15:38
Ответ принят как подходящий

Я понял, как это сделать. Я создал специальную службу для активного хранилища, которая унаследована от службы s3, и создал второй экземпляр сегмента в классе для региона ЕС, который я хочу использовать.

# lib/active_storage/service/dynamic_storage_service.rb
require "active_storage/service/s3_service"

module ActiveStorage
  class Service::DynamicStorageService < ActiveStorage::Service::S3Service
    # create attributes for another client and bucket
    attr_reader :client_eu, :bucket_eu
    

    # override the initializer with options to pass in an eu bucket from the storage.yml
    # this is where you would create whatever extra buckets you need
    def initialize(bucket:, bucket_eu:, upload: {}, public: false, **options)
      eu_options = options.except(:region)
      eu_options[:region] = eu_options[:region_eu]

      @client_eu = Aws::S3::Resource.new(eu_options.except(:region_eu))
      @bucket_eu = @client_eu.bucket(bucket_eu)

      super(bucket: bucket, upload: upload, public: public, **options.except(:region_eu))
    end

    private
      # override the method where the bucket is used
      # this is where you would add whatever logic you need to select the bucket
      # this is the implementation that works for me
      # the method just needs to return the S3 object and everything else will work
      def object_for(key)
        # this is how you get the record based on the key
        document_id = ActiveStorage::Attachment.find_by(blob_id: ActiveStorage::Blob.find_by(key: key).id).record_id

        document = Document.find(document_id)

        if document.account.region == 'eu'
          return bucket_eu.object(key)
        else
          return bucket.object(key)
        end
      end
  end
end

И тогда вы будете использовать его в Storage.yml вместо обычного сервиса S3:

# config/storage.yml
amazon:
    service: DynamicStorage
    access_key_id: <%= ENV['S3_ACCESS_KEY_ID'] %>
    secret_access_key: <%= ENV['S3_SECRET_KEY_ACCESS'] %>
    region: <%= ENV['S3_REGION'] %>
    bucket: <%= ENV['S3_BUCKET_NAME'] %>
    region_eu: <%= ENV['S3_REGION_EU'] %>
    bucket_eu: <%= ENV['S3_BUCKET_NAME_EU'] %>

И Document.rb может работать как обычно.

# document.rb
class Document < ApplicationRecord
  belongs_to :account
  
  has_one_attached :file

end

Вероятно, вы могли бы переписать это лучше и сделать более динамичным и способным обрабатывать столько сегментов, сколько захотите, но это то, что сработало для меня.

Переопределить частный метод в классе фреймворка и добавить зависимость к вашей модели Document кажется ужасной идеей. Что, если этот класс Rails изменится в следующей версии? Или что, если вы хотите добавить файлы к другим моделям в вашем приложении?

spickermann 06.09.2024 10:22

Другие вопросы по теме