How to download image files in Ruby
Tags: ruby
Published: 2019-03-25 12:13:23

---
title: How to download image files in Ruby
published: true
tags: ruby
canonical_url: https://www.twilio.com/blog/download-image-files-ruby
cover_image: https://thepracticaldev.s3.amazonaws.com/i/ubrx38s5kzuw2w2o8vbc.png
---

Have you ever needed to download and save an image in your Ruby application? Read on to find out how.

## Plain old Ruby

The most popular way to download a file without any dependencies is to use the standard library [`open-uri`](https://ruby-doc.org/stdlib-2.6.2/libdoc/open-uri/rdoc/OpenURI.html).

[`Kernel#open`](https://ruby-doc.org/core-2.6.2/Kernel.html#method-i-open) is a method that you can use to open files, streams, or processes to read to or write from. For example, you can open a file and read its contents with the following code:

```ruby
open("./test.txt") do |file|
  puts file.read
end
```

`open-uri` extends `Kernel#open` so that it can open URIs as if they were files. We can use this to download an image and then save it as a file.

To do so, we first require `open-uri` then use the `open` method to access an image URL. We can then open up a file and write the contents of the image to the file. Open up IRB and try the following:

```ruby
require "open-uri"

open("https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/test.original.jpg") do |image|
  File.open("./test.jpg", "wb") do |file|
    file.write(image.read)
  end
end
```

In the directory in which you opened IRB you will now find the image you downloaded.

This is a success, but this was a straightforward example. In practice you would want to handle potential errors, such as a 404 error for a missing image. Plus, there's a bunch of other potential issues with using `open-uri`.

## Problems with open-uri

The thing is, using `open-uri` like this is not ideal. First up, the above code is not very memory efficient, it loads the entire image into memory and then writes to disk. It also turns out that `open-uri` has some other quirks. [Janko Marohnić discovered a bunch of these issues](https://twin.github.io/improving-open-uri/) while working on [Shrine](https://github.com/shrinerb/shrine). Notably, `open-uri`:

- [leaves you vulnerable to remote code execution](https://sakurity.com/blog/2015/02/28/openuri.html)
- sometimes returns a `Tempfile` other times `StringIO`
- doesn't let you limit redirects or file size

To solve all of this, Janko created the [`Down` gem](https://github.com/janko/down). It allows you to avoid these issues to safely and efficiently download files.

## Improving downloads with the `Down` gem

Let's download the same image using `Down`. Start by installing the gem:

```bash
gem install down
```

We can download the image to a [`Tempfile`](https://docs.ruby-lang.org/en/2.6.0/Tempfile.html) using `Down.download`:

```ruby
require "down"

tempfile = Down.download("https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/test.original.jpg")
```

If you want to save this file to the file system, `Down` has an option for that. Passing a directory as a `:destination` option will save the file to that directory.

```ruby
require "down"

Down.download(
  "https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/test.original.jpg",
  destination: "./"
)
```

This will give the downloaded file a random name generated by `Tempfile`. If you want to keep the name of the file from the URL you need to do a bit more work. In this case we can download the file to a `Tempfile` and then move it to a permanent location on our drive.

For this we'll use [`FileUtils#mv`](https://ruby-doc.org/stdlib-2.6.2/libdoc/fileutils/rdoc/FileUtils.html) which takes two arguments, the file name and the destination we want to move it to. To get the original name of the file, the `Tempfile` object that `Down` returns has an `original_filename` method we can use.

```ruby
require "down"
require "fileutils"

tempfile = Down.download("https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/test.original.jpg")
FileUtils.mv(tempfile.path, "./#{tempfile.original_filename}")
```

Now we've downloaded the file safely and efficiently and stored it permanently on our hard drive.

## Advanced usage

`Down` takes a bunch of other options too, to control the download experience. By default it only allows 2 redirects, but you can control that with the `max_redirects` options. You can also limit the size of the file with the `max_size` options. This stops attacker tying up your server with giant image downloads. If you wanted to download a file that was at most 5MB in size with at most 5 redirects, you would use:

```ruby
Down.download(
  "https://s3.amazonaws.com/com.twilio.prod.twilio-docs/images/test.original.jpg",
  max_redirects: 5,
  max_size: 5 * 1024 * 1024
)
```

There are [more options in the documentation](https://github.com/janko/down), including how to stream files down and change the back-end of the gem from `open-uri` and `Net::HTTP` to [HTTP.rb](https://github.com/janko/down#httprb) or [Wget](https://github.com/janko/down#wget-experimental).

## Next steps

In this post we've seen how to download images using both `open-uri` and `Down`. `Down` is much safer as it saves us from infinite redirect loops and remote code injection, and it is more user friendly as it always returns a `Tempfile` and lets us easily restrict file size.

You could use this to [download media from Twilio MMS or WhatsApp messages](https://www.twilio.com/docs/sms/tutorials/how-to-receive-and-download-images-incoming-mms-ruby-sinatra) or hook this up with [Active Storage in Rails](https://edgeguides.rubyonrails.org/active_storage_overview.html) to make more options for users uploading images.

Have you used alternative methods for downloading images in Ruby? Let me know how in the comments below or on Twitter at [@philnash](https://twitter.com/philnash).