rails
rails copied to clipboard
support more cloud storage: direct upload with form data, customize HTTP method and response type
Summary
When building a new Service for ActiveStorage, we got two requirements:
- We need to direct upload a file with
POST
method instead ofPUT
, and send credential(token), key(filename) and file(binary to upload) withContent-Type: multipart/form-data; boundary=<frontier>
, instead of sending them with headers. - We are creating a SaaS that make each Tenant has their own Cloud Service configuration, hence it's not possible to write all service configurations into
config/storage.yml
. For now,ActiveStorage::Blob
has an anonymous validator to checkservice_name
has to be declared inActiveStorage::Blob.services
, we can't disable this validator.
So, I think it's better to make them configurable like this:
# write my own service
module ActiveStorage
class Service::QiniumService < Service
# declare direct upload with HTTP POST method
def http_method_for_direct_upload
'POST'
end
# declare direct upload response type, "text" or "json"
def http_response_type_for_direct_upload
'json'
end
# declare direct upload file as multipart/form-data, the value of ':file' is the form data key to file
def form_data_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, **)
put_policy = Qinium::PutPolicy.new(config, key: key, expires_in: expires_in)
put_policy.fsize_limit = content_length.to_i + 1000
put_policy.mime_limit = content_type
put_policy.detect_mime = 1
put_policy.insert_only = 1
{
key: key,
token: put_policy.to_token,
':file': 'file'
}
end
end
With above changes, ActiveStorage::Service
can support all types of HTTP methods, send token to cloud service with HTTP header or form data.
change activestorage/app/models/active_storage/blob.rb
anonymous validator to named one:
validate do
if service_name_changed? && service_name.present?
services.fetch(service_name) do
errors.add(:service_name, :invalid)
end
end
end
changes to
validate :validate_service_name_in_services, if: -> { service_name_changed? && service_name.present? }
private
def validate_service_name_in_services
services.fetch(service_name) do
errors.add(:service_name, :invalid)
end
end
Now, we can write our own Module prepend to ActiveStorage::Blob, to override validate_service_name_in_services
, such as:
module ActiveStorageSaas::BlobModelMixin
private
def validate_service_name_in_services
# dynamically define service name per TenantStorageService#id, later we can resolve tenant storage
# configuration by parsing TenantStorageService:1 to tenant_storage = TenantStorageService.find(1)
# tenant_storage.service_name => Real Storage Service Name
#. tenant_storage.service_options => options to make instance of Service
/^TenantStorageService:\d+$/.match?(service_name) || super
end
end
ActiveSupport.on_load(:active_storage_blob) do
prepend ActiveStorageSaas::BlobModelMixin
end
Other Information
- This is another PR #45442 that has the same requirement to customize HTTP Method
- This is my real project that is working on this PR : https://github.com/xiaohui-zhangxh/activestorage_qinium/blob/main/lib/active_storage/service/qinium_service.rb
- This is my real project that need to make our Tenants have their own storage configurations : https://github.com/xiaohui-zhangxh/activestorage_saas/blob/main/lib/active_storage/service/saas_service.rb
Now, I'm not ready to write test code before this idea of PR is accepted. Once this idea is accepted, I'd like to write some tests for it.