How can I download a file from a URL and save it in Rails?
Asked Answered
C

8

231

I have a URL to an image which i want to save locally, so that I can use Paperclip to produce a thumbnail for my application. What's the best way to download and save the image? (I looked into ruby file handling but did not come across anything.)

Cutworm answered 25/3, 2010 at 13:36 Comment(0)
G
337

Try this:

require 'open-uri'
open('image.png', 'wb') do |file|
  file << open('http://example.com/image.png').read
end
Georama answered 25/3, 2010 at 16:19 Comment(8)
This works. Thanks. I tried this on an image. The image was not properly written to the file. As if it has been corrupted or something. Also could you guide me to do this same in a Rails application. Can i use this in a Rails application ?Cutworm
Yes, you can use it in a rails application. The file not being written properly is probably because I forgot to open the destination in write-binary mode. I'll edit that now.Georama
This reads the whole file in memory. The best way is IO.copy_stream(open('http://example.com/image.png'), 'destination.png')Pia
this doesn't work for me. I've created a question about this: #37072431Titbit
rails 5, 2016, I didn't need to require 'open-uri', just used the code under itTypical
this answer did not work for me. correct answer is here: https://mcmap.net/q/117388/-how-can-i-download-a-file-from-a-url-and-save-it-in-railsCardoso
Is it possible to provide a path of where the file should be stored? Currently, is being stored on root in Rails appEadwine
Extreme caution if the url can be provided by the user! sakurity.com/blog/2015/02/28/openuri.htmlImperialism
V
187

An even shorter version:

require 'open-uri'
download = open('http://example.com/image.png')
IO.copy_stream(download, '~/image.png')

To keep the same filename:

IO.copy_stream(download, "~/#{download.base_uri.to_s.split('/')[-1]}")
Vesta answered 20/4, 2015 at 9:0 Comment(4)
And more efficient as the output is not stored as a string in memory. +1Pyle
Though it's actually useful having both answers displayed, so people like me can see both, think about the differences and then choose this answer consciously for the right reasons :)Comity
I'm getting a security warning from Rubocop when using open. I replaced it with URI.parse(url).open instead. Read more about that cop here: rubydoc.info/gems/rubocop/RuboCop/Cop/Security/OpenRunoff
pure open does not work for me, had to use URI.openWholehearted
H
32

If you're using PaperClip, downloading from a URL is now handled automatically.

Assuming you've got something like:

class MyModel < ActiveRecord::Base
  has_attached_file :image, ...
end

On your model, just specify the image as a URL, something like this (written in deliberate longhand):

@my_model = MyModel.new
image_url = params[:image_url]
@my_model.image = URI.parse(image_url)

You'll probably want to put this in a method in your model. This will also work just fine on Heroku's temporary filesystem.

Paperclip will take it from there.

source: paperclip documentation

Herein answered 5/3, 2013 at 15:27 Comment(1)
For future searchers, Paperclip no longer allows this by default: medium.com/in-the-weeds/…Llama
M
19

I think this is the clearest way:

require 'open-uri'

File.write 'image.png', open('http://example.com/image.png').read
Mckellar answered 1/10, 2017 at 20:12 Comment(2)
sage i am new to rails, where i can add this code in model or controller or in viewBeaverbrook
It depends, but typically you'd do something like this inside a model or controller method. In a Rails context, you're likely better off using the framework's tools like ActiveStorage, rather than writing directly to disk.Mckellar
C
13

Possibly the simplest way:

require 'open-uri'
image_url = "https://i.imgur.com/ZWnhY9T.png"
IO.copy_stream(URI.open(image_url), 'destination.png')
Couchant answered 19/12, 2020 at 14:18 Comment(4)
In my situation, I tried all the codes above. only this floor worked. The difference is copy_stream()'s first param URI.open(img_url), if i dont wrap the img_url by URI.open(), i got the following ERROR No such file or directory @ rb_sysopen.Peevish
Is there a way to get the download progress?Dianemarie
@Dianemarie sorry I don't know of a way, however it could make a great question to ask, as I suspect it would be valuable to others too. Please drop the link here if you ask it.Couchant
I was able to do it in this gist: gist.github.com/pioz/…Dianemarie
W
11

Check out Net::HTTP in the standard library. The documentation provides several examples on how to download documents using HTTP.

Waddell answered 25/3, 2010 at 13:42 Comment(1)
This answer needs more visibility as Kernel#open enables 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.Campball
P
4

Using Ruby 3 and above, you'll get the following error using the accepted answer:

No such file or directory @ rb_sysopen - http://example.com/image.png (Errno::ENOENT)

The solution is to use URI.open in place of the Kernel.open. Example:

require "uri"

download = URI.open('http://example.com/image.png')
File.write('~/image.png', download)
Portingale answered 4/10, 2022 at 13:9 Comment(0)
F
0

All above examples are great. In my case I just wanted to create a download link from the image from URL.

If you want to make it downloadable (to your downloads folder), you can use the following code in your controller:

require 'open-uri'
file_type = url.to_s.split(".")[-1]

send_data open(url).read, filename: "some_name.#{file_type}", type: "image/#{file_type}", disposition: "attachment"
Faultless answered 24/8, 2021 at 9:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.