How to save an image from a url with rails active storage?

Just found the answer to my own question. My first instinct was pretty close...

require 'open-uri'

class User < ApplicationRecord
  has_one_attached :avatar
  before_save :grab_image

  def grab_image
    downloaded_image = open("http://www.example.com/image.jpg")
    self.avatar.attach(io: downloaded_image  , filename: "foo.jpg")
  end

end

Update: please note comment below, "you have to be careful not to pass user input to open, it can execute arbitrary code, e.g. open("|date")"


Like said in the comments, the use of open or URI.open is very dangerous, since it can not only access files but also process invocation by prefixing a pipe symbol (e.g. open("| ls")).

Kernel#open and URI.open enable not only file access but also process invocation by prefixing a pipe symbol (e.g., open("| ls")). So, it may lead to a serious security risk by using variable input to the argument of Kernel#open and URI.open. It would be better to use File.open, IO.popen or URI.parse#open explicitly.

Extracted from the Rubocop documentation: https://docs.rubocop.org/rubocop/1.8/cops_security.html#securityopen

So, a safer solution would be:

class User < ApplicationRecord
  has_one_attached :avatar
  before_save :grab_image

  def grab_image
    downloaded_image = URI.parse("http://www.example.com/image.jpg").open
    avatar.attach(io: downloaded_image, filename: "foo.jpg")
  end
end

The simplest way to do this without having to enter filename explicitly is:

url = URI.parse("https://your-url.com/abc.mp3")
filename = File.basename(url.path)
file = URI.open(url)
user = User.first
user.avatar.attach(io: file, filename: filename)

This automatically saves the avatar against that particular user object.

In case you are using a remote service like S3 the URL can be retrieved by:

user.avatar.service_url

using the down gem to avoid the security issues of using open-uri:

image = Down.download(image_url)
user.image.attach(io: image, filename: "image.jpg")