Last active
December 10, 2024 21:54
-
-
Save kieranklaassen/b11183b6df88b169cbc19c72271ac360 to your computer and use it in GitHub Desktop.
active_storage_from_url.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# config/initializers/active_storage_from_url.rb | |
# frozen_string_literal: true | |
require "open-uri" | |
require "securerandom" | |
require "marcel" | |
# This initializer extends Active Storage attachments to support attaching files directly from a remote URL. | |
# | |
# Example Usage: | |
# | |
# class User < ApplicationRecord | |
# has_one_attached :avatar | |
# end | |
# | |
# user = User.find(params[:id]) | |
# user.avatar.attach_from_url("https://example.com/image.jpg") | |
# | |
# class Gallery < ApplicationRecord | |
# has_many_attached :photos | |
# end | |
# | |
# gallery = Gallery.find(params[:id]) | |
# gallery.photos.attach_from_url("https://example.com/another_image.jpg") | |
# | |
module ActiveStorage | |
class Attached | |
# Common logic for attaching files from remote URLs. | |
module URLAttachment # :nodoc: | |
private | |
def attach_from_url_impl(url) | |
return if url.blank? | |
uri = validate_remote_url!(url) | |
downloaded_file = nil | |
begin | |
downloaded_file = URI.open(uri) | |
filename = extract_filename(uri) || default_filename | |
content_type = determine_content_type(downloaded_file) | |
attach(io: downloaded_file, filename: filename, content_type: content_type) | |
ensure | |
cleanup_file(downloaded_file) | |
end | |
end | |
def validate_remote_url!(url) | |
uri = URI.parse(url) | |
unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS) | |
raise URI::InvalidURIError, "Invalid URL scheme: #{uri.scheme.inspect}" | |
end | |
uri | |
end | |
def extract_filename(uri) | |
path = uri.path | |
File.basename(path) if path.present? && path != "/" | |
end | |
def default_filename | |
"image_#{SecureRandom.uuid}.jpg" | |
end | |
def determine_content_type(file) | |
if file.respond_to?(:content_type) && file.content_type.present? | |
file.content_type | |
else | |
Marcel::MimeType.for( | |
file, | |
name: file.respond_to?(:path) ? File.basename(file.path) : nil | |
) | |
end | |
end | |
def cleanup_file(file) | |
return unless file | |
if file.respond_to?(:close!) | |
file.close! | |
else | |
file.close if file.respond_to?(:close) | |
file.unlink if file.respond_to?(:unlink) | |
end | |
end | |
end | |
# Reopen the One class to add attach_from_url. | |
class One | |
include URLAttachment | |
# Attaches a file from a remote URL to a has_one_attached association. | |
# | |
# @param url [String] The URL of the remote file. | |
# @raise [URI::InvalidURIError] If the URL is invalid or uses an unsupported scheme. | |
# @return [void] | |
def attach_from_url(url) | |
attach_from_url_impl(url) | |
end | |
end | |
# Reopen the Many class to add attach_from_url. | |
class Many | |
include URLAttachment | |
# Attaches a file from a remote URL to a has_many_attached association. | |
# | |
# @param url [String] The URL of the remote file. | |
# @raise [URI::InvalidURIError] If the URL is invalid or uses an unsupported scheme. | |
# @return [void] | |
def attach_from_url(url) | |
attach_from_url_impl(url) | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment