Upload an Image to Active Storage From a Url
The author selected the Diverseness in Tech fund to receive a donation equally part of the Write for DOnations program.
Introduction
When you're edifice web applications that let users upload and shop files, you'll want to use a scalable file storage solution. This way you're not in danger of running out of space if your application gets wildly popular. Afterwards all, these uploads tin be anything from profile pictures to house photos to PDF reports. Yous too desire your file storage solution to be reliable then you lot don't lose your important customer files, and fast so your visitors aren't waiting for files to transfer. ou'll want this all to be affordable too.
DigitalOcean Spaces tin can address all of these needs. Because information technology's uniform with Amazon's S3 service, you tin can quickly integrate information technology into a Ruddy on Rails application using the new ActiveStorage library that ships with Rails 6.
In this guide, y'all'll configure a Track application, so information technology uses ActiveStorage with DigitalOcean Spaces. Y'all'll and so run through the configuration necessary to become uploads and downloads blazing fast using direct uploads and Spaces' built-in CDN (Content Delivery Network).
When you're finished, you lot'll be set up to integrate file storage with DigitalOcean spaces into your own Rail application.
Prerequisites
Before you begin this guide, you'll need the following:
- A DigitalOcean account.
- A development surroundings for Ruby on Runway. Follow How to Install Ruby on Rails with Rbenv on macOS or How to Install Crimson on Rails with Rbenv on Ubuntu depending on which operating system you're using.
- Some initial Blood-red on Rails knowledge. Tutorials similar How to build a Blood-red on Rails Application tin help with this, or you can follow the Official Getting Started Guide.
- Git installed on your development machine. Yous tin follow the tutorial Contributing to Open Source: Getting Started with Git to install and set up Git on your figurer.
- Node.js installed, which y'all can practice by following How to Install Node.js and Create a Local Evolution Environment. This tutorial uses Node.js version xiv.
- The Yarn package manager installed, which you can install with
npm install -g yarn
. - SQLite installed, which y'all can do by following Stride i of How to Build a Red on Track Application.
- Imagemagick installed, which you can install past post-obit the tutorial How To Resize Images with ImageMagick.
- (Optional) If y'all'd like to experiment with the Spaces CDN, you'll need a domain name and the ability to modify DNS records for that domain. You lot can follow the How to Add Domains tutorial to manage your domain with DigitalOcean.
- (Optional) If you'd similar to use Direct Uploads, y'all'll need to configure
s3cmd
to piece of work with DigitalOcean Spaces. Follow Setting Up s3cmd two.10 with DigitalOcean Spaces to become that set up.
Step 1 — Getting the Sample App Running
Rather than build a complete Rails application from scratch, y'all'll clone an existing Rails 6 application that uses ActiveStorage and change it to use DigitalOcean Spaces as its image storage backend. The app yous'll piece of work with is Space Puppies, an epitome gallery that will allow people upload and view photographs of their favorite puppies. The application looks like the following figure:
Open your last and clone the application from GitHub with the following command:
- git clone https://github.com/exercise-community/infinite-puppies
You lot'll run across output that looks similar to this:
Output
Cloning into 'space-puppies'... remote: Enumerating objects: 122, done. remote: Counting objects: 100% (122/122), done. remote: Compressing objects: 100% (103/103), washed. remote: Total 122 (delta three), reused 122 (delta three), pack-reused 0 Receiving objects: 100% (122/122), 163.17 KiB | 1018.00 KiB/southward, done. Resolving deltas: 100% (3/iii), done.
Next, bank check your Reddish version. Space Puppies uses Cherry 2.7.1, and then run rbenv versions
to check which version you accept installed:
- rbenv versions
If you've followed the prerequisite tutorials, you'll merely have Ruby ii.5.one in that list, and your output will look like this:
Output
* organization ii.5.1
If yous don't have Ruby two.7.1 in that list, install it using cherry-red-build
:
- rbenv install 2.7.ane
Depending on your machine's speed and operating organisation, this might take a while. Y'all'll see output that looks like this:
Output
Downloading ruby-ii.vii.ane.tar.bz2... -> https://enshroud.ruby-lang.org/pub/ruby/2.7/ruby-2.7.1.tar.bz2 Installing ruby-2.7.i... Installed ruby-ii.7.1 to /root/.rbenv/versions/ii.seven.ane
Modify to the space-puppies
directory:
- cd infinite-puppies
rbenv
will automatically alter your Ruby version when y'all enter the directory. Verify the version:
- reddish --version
You'll run into output similar to the following:
Output
reddish 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]
Adjacent, you will install the Ruby gems and JavaScript packages that the app needs to run. Then y'all'll the database migrations needed for the Space Puppies app to run.
Install all the necessary gems using the bundle
command:
- package install
Then, to tell rbenv
about any new binaries installed past Bundler, utilise the rehash
command:
- rbenv rehash
Side by side, tell yarn
to install the necessary JavaScript dependencies:
- yarn install
Now create the database schema with Rail' built-in migration tool:
- rails db:migrate
With all the libraries installed and the database created, get-go the built-in web server with the post-obit command:
- rails s
Notation: By default, rails s
only binds to the local loopback address, significant you lot tin only access the server from the same computer that runs the command. If y'all're running on a Droplet and you'd like to access your server from a browser running on your local motorcar, yous'll need to tell the Track server to respond to remote requests by bounden to 0.0.0.0
. Y'all can do that with this control:
- rails s -b 0.0.0.0
Your server starts, and you'll receive output similar this:
Output
=> Booting Puma => Rails vi.0.three.2 application starting in development => Run `rails server --help` for more startup options Puma starting in single manner... * Version four.3.five (ruby 2.seven.i-p83), codename: Mysterious Traveller * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://127.0.0.ane:3000 * Listening on tcp://[::one]:3000 Use Ctrl-C to stop
Now you lot tin can admission your application in a web browser. If you're running the awarding on your local motorcar, navigate to http://localhost:3000
. If you lot're running on a Droplet or other remote server, then navigate to http://your_server_ip:3000
.
Y'all'll encounter the app'southward interface, but this time without any puppies. Endeavour adding a couple of images by clicking the New Puppy push button.
If you need puppy photos to use for testing, Unsplash has an extensive list you can use for testing. Review the Unsplash license if y'all plan to use these images in your projects.
Earlier moving on, allow's walk through each layer of the application and await at how ActiveStorage works with each function so you can brand the necessary changes for DigitalOcean Spaces. For a more than detailed look at ActiveStorage, read the Active Storage Overview page in the official Runway documentation.
First, wait at the model, which represents an object in your awarding that you're storing in the database. Yous'll discover the Puppy
model in app/models/puppy.rb
. Open this file in your text editor and you lot'll see this code:
app/models/puppy.rb
class Puppy < ApplicationRecord has_one_attached :photo end
Y'all'll find the has_one_attached
macro in the model, which indicates there'due south a photo attached to each Puppy
model instance. These photos will be stored as ActiveStorage::Blob
instances via an ActiveStorage::Attached::One
proxy.
Shut this file.
The next layer up the stack is the controller. In a Runway application, the controller is responsible for controlling access to database models and responding to requests from the user. The respective controller for the Puppy
model is the PuppiesController
which you lot will find in app/controllers/puppies_controller.rb
. Open up this file in your editor and you'll see the post-obit lawmaking:
app/controllers/puppies_controller.rb
class PuppiesController < ApplicationController def index @puppies = Puppy.with_attached_photo end # ... snipped other actions ... stop
Everything in the file is standard Rails code, apart from the with_attached_photo
telephone call. This call causes ActiveRecord to load all of the associated ActiveStorage::Hulk
associations when y'all fetch the list of Puppy
models. This is a scope that ActiveStorage provides to help y'all avoid an expensive N+i database query.
Finally, let's wait at the views, which generate the HTML your application will send to the user'southward browser. In that location are a few views in this app, but you'll want to focus on the view responsible for showing the uploaded puppy image. Y'all'll notice this file at app/views/puppies/_puppy.html.erb
. Open it in your editor, and you'll encounter code like this:
app/views/puppies/_puppy.html.erb
<div course = "puppy" > <%= image_tag puppy.photograph.variant( resize_to_fill : [ 250 , 250 ] ) %> </div >
ActiveStorage is designed to work with Rails, so yous can use the built-in image_tag
helper to generate a URL that points to an attached photo, wherever information technology happens to be stored. In this case, the app is using the variant support for images. When the user first requests this variant, ActiveStorage will automatically apply ImageMagick via the image_processing gem, to generate a modified paradigm fitting our requirements. In this case, it will create a puppy photo filling a 250x250 pixel box. The variant volition be stored for yous in the same place as your original photo, which means you'll only need to generate each variant once. Runway will serve the generated version on subsequent requests.
Note: Generating prototype variants can be slow, and you potentially don't want your users waiting. If you know you're going to need a particular variant, you tin can eagerly generate it using the .processed
method:
puppy.photograph.variant( resize_to_fill : [ 250 , 250 ] ) .processed
It's a proficient idea to do this kind of processing in a background chore when you deploy to production. Explore Active Task and create a task to call processed
to generate your images ahead of time.
At present your application is running locally, and y'all know how all the lawmaking pieces fit together. Side by side, it's time to set upwards a new DigitalOcean Space so you can move your uploads to the cloud.
Step 2 — Setting upwards your DigitalOcean Space
At the moment, your Infinite Puppies awarding stores images locally, which is fine for development or testing, but y'all almost certainly don't desire to use this mode in production. In order to scale the application horizontally by adding more awarding server instances, you'd need copies of each image on every server.
In this step, you lot'll create a DigitalOcean Space to employ for your app'south images.
Sign in to your DigitalOcean management panel, click Create in the superlative right, and choose Spaces.
Pick any data centre and leave the CDN disabled for now; you'll come back to this later. Ensure the file listing is set to Restrict File List.
Choose a name for your Space. Remember that this will have to be unique across all Spaces users, so pick a unique name, like yourname-infinite-puppies
. Click Create a Infinite:
Alarm: Be conscientious about admission to the files y'all store on behalf of your customers. At that place have been many examples of data leaks and hacks due to misconfigured file storage. Past default, ActiveStorage files are just attainable if you generate an authenticated URL, simply it's worth being vigilant if you lot're dealing with client data.
You'll so encounter your brand new Infinite.
Click the Settings tab and take a note of your Space's endpoint. Y'all'll need that when you configure your Rails application.
Next, you lot'll configure the Rails awarding to store ActiveStorage files in this Space. To do that securely, yous demand to create a new Spaces Access Key and Underground.
Click API in the left navigation, so click Generate New Primal in the bottom correct. Give your new key a descriptive name similar "Evolution Car". Your underground will only appear once, and then exist sure to copy it somewhere safe for a moment.
In your Runway app, y'all'll need a secure mode to shop that access token, so you lot'll use Rails' secure credential management characteristic. To edit your credentials, execute the post-obit command in your terminal:
- EDITOR = "nano -west" rails credentials:edit
This generates a main key and launches the nano
editor so you lot can edit the values.
In nano
, add the post-obit to your credentials.yml
file, using your API cardinal and secret from DigitalOcean:
config/credentials.yml
digitalocean : access_key : YOUR_API_ACCESS_KEY hush-hush : YOUR_API_ACCESS_SCRET
Save and close the file (Ctrl+10
, then Y
, and then Enter
), and Rails will store an encrypted version that'southward safety to commit to source control in config/credentials.yml.enc
.
Y'all volition see output like the following:
Output
Calculation config/master.key to shop the encryption central: RANDOM_HASH_HERE Save this in a password manager your squad tin can access. If you lose the fundamental, no one, including you lot, can access annihilation encrypted with information technology. create config/principal.cardinal File encrypted and saved.
Now that you've configured your credentials, you're ready to signal your app to your new Spaces bucket.
Open the file config/storage.yml
in your editor and add together the following definition to the lesser of that file:
config/storage.yml
digitalocean : service : S3 endpoint : https://your-spaces-endpoint-hither access_key_id : <%= Rails.awarding.credentials.dig(:digitalocean, :access_key) %> secret_access_key : <%= Track.application.credentials.dig(:digitalocean, :secret) %> bucket : your-infinite-name-hither region : unused
Note that the service says S3
rather than Spaces
. Spaces has an S3-compatible API, and Rails supports S3 natively. Your endpoint is https://
followed past your Space's endpoint, which y'all copied previously, and the bucket
name is the name of your Space, which you entered when creating it. The bucket proper noun is besides displayed as the championship in your Control Panel when you view your Infinite.
This configuration file will exist stored unencrypted, and then instead of entering your access key and clandestine, yous're referencing the ones yous just entered securely in credentials.yml.enc
.
Notation: DigitalOcean uses the endpoint to specify the region. Withal, you need to provide the region, or ActiveStorage will complain. Since DigitalOcean volition ignore it, you can gear up it to whatever value yous'd like. The value unused
in the example code makes it clear that you lot're not using it.
Relieve the configuration file.
Now, you lot demand to tell Rails to employ Spaces for your file storage backend instead of the local file system. Open config/environments/development.rb
in your editor and modify the config.active_storage.service
entry from :local:
to :digitalocean
:
config/environments/development.rb
# ... # Shop uploaded files on the local file organisation (run into config/storage.yml for options). config.active_storage.service = :digitalocean # ...
Save the file and go out your editor. Now first your server again:
- rail due south -b 0.0.0.0
Visit http://localhost:3000
or http://your server ip:3000
in a browser once over again.
Upload some images, and the app will store them in your DigitalOcean Space. You tin can encounter this past visiting your Space in the DigitalOcean console. You will run across the uploaded files and variants listed:
ActiveStorage uses random filenames past default, which is helpful when protecting uploaded customer data. Metadata, including the original filename, is stored in your database instead.
Note: If you lot are getting an Aws::S3::Errors::SignatureDoesNotMatch
, that might mean your credentials are incorrect. Run track credentials:edit
again and double-check them.
Rails stores the names and some metadata about your files as ActiveStorage::Blob
records. You tin access the ActiveStorage::Blob
for any of your records by calling an accessor method named after your attachment. In this case, the attachment is called photo
.
Try it out. Start a Rails console in your terminal:
- rail c
Grab the blob from the last puppy photograph you uploaded:
> Puppy.last.photo.hulk #=> => #<ActiveStorage::Blob ...>
You lot now accept a Rails Application storing uploads in a scalable, reliable, and affordable object store.
In the adjacent ii steps, you'll explore 2 optional additions you can make to the app that will help ameliorate this solution's performance and speed for your users.
Stride 3 — Configuring the Spaces CDN (Optional)
Note: For this stride, yous will demand a doman with name servers pointing to DigitalOcean. Yous can follow the How to Add together Domains guide to do that.
Using a Content Delivery Network (CDN) volition allow you to provide faster downloads of files for your users by locating copies of the files closer to them.
Y'all tin can investigate CDN performance using a tool like Uptrends CDN Performance Check. If you add the URL for one of the photos y'all uploaded in the previous step, you'll encounter things are fast if you happen to exist nearby, only things get a trivial slower as y'all move away geographically. You can get the URL using the Developer Tools in your browser, or by starting a Rails console (runway c
) and calling service_url
on an attachment.
> Puppy.last.photo.service_url
Here'southward an case Uptrends written report with a file located in the San Francisco data eye. Notice that the times decrease depending on the altitude from San Francisco. San Diego has a brusk fourth dimension, while Paris has a much longer fourth dimension:
You can improve speeds by enabling Spaces' built-in CDN. Become to Spaces in your DigitalOcean Control Panel and click the name of the Infinite you created in Step 2. Next, choose the Settings tab and click Edit next to CDN (Content Delivery Network), then click Enable CDN.
Now y'all need to choose a domain to use for your CDN and create an SSL Document for the domain. You tin do this automatically using Let's Encrypt. Click the Utilise a custom subdomain dropdown and then Add a new subdomain certificate.
Find the domain you'd like to apply, then cull the option to create a subdomain. Something like cdn.yourdomain.com
is a standard naming convention. You can so requite the certificate a name and click the "Generate Certificate and Use Subdomain" button.
Printing the Salve push button under CDN (Content Commitment Network).
Your CDN is now enabled, but you lot need to tell your Track Application to apply it. This isn't built into ActiveStorage in this version of Rails, so you lot'll override some built-in Runway framework methods to make information technology piece of work.
Create a new Rails initializer called config/initializers/active_storage_cdn.rb
and add the following code which will rewrite the URLs:
config/initializers/active_storage_cdn.rb
Rails.application.config.after_initialize practise require "active_storage/service/s3_service" module SimpleCDNUrlReplacement CDN_HOST = "cdn.yourdomain.com" def url ( ... ) url = super original_host = " #{ bucket.proper name } . #{ client.client.config.endpoint.host } " url.gsub(original_host, CDN_HOST ) stop end ActiveStorage::Service::S3Service. prepend (SimpleCDNUrlReplacement) end
This initializer runs each fourth dimension your awarding asks for a URL from an ActiveStorage::Service::S3Service
provider. It then replaces the original, non-CDN host with your CDN host, defined as the CDN_HOST
constant.
You can now restart your server, and you'll observe that each of your photos comes from the CDN. You won't need to re-upload them, as DigitalOcean will have care of forwarding the content from the data center where you gear up your Infinite out to the edge nodes.
Y'all might like to compare the speed of accessing one of your photos on Uptrends' Operation Check site now to the pre-CDN speed. Hither's an example of using the CDN on a San Francisco-based Infinite. You lot tin encounter a significant global speed improvement.
Next yous'll configure the application to receive files directly from the browser.
Step 4 — Setting up Direct Uploads (Optional)
One last feature of ActiveStorage that you might like to consider is chosen a Direct Upload. Now, when your users upload a file, the data is sent to your server, processed by Rail, and so forwarded to your Infinite. This can cause issues if you take many simultaneous users, or if your users are uploading large files, equally each file will (in near cases) employ a single app server thread for the unabridged duration of an upload.
By contrast, a Direct Upload will go straight to your DigitalOcean Space with no Rail server hop in between. To do this, you'll enable some congenital-in JavaScript that ships with Rails and configure Cross-Origin Resources Sharing([CORS]((https://developer.mozilla.org/en-US/docs/Spider web/HTTP/CORS) on your Space so that you can securely transport requests directly to the Space despite them originating in a different place.
Starting time, you'll configure CORS for your Space. You will use s3cmd
to do this, and you can follow Setting Upwardly s3cmd 2.x with DigitalOcean Spaces if you haven't configured this to piece of work with Spaces notwithstanding.
Create a new file chosen cors.xml
and add the following code to the file, replacing your_domain
with the domain you're using for development. If you are developing on your local machine, y'all'll utilize http://localhost:3000
. If you're developing on a Droplet, this will exist your Droplet IP address:
cors.xml
<CORSConfiguration > <CORSRule > <AllowedOrigin > your_domain </AllowedOrigin > <AllowedMethod > PUT </AllowedMethod > <AllowedHeader > * </AllowedHeader > <ExposeHeader > Origin </ExposeHeader > <ExposeHeader > Content-Type </ExposeHeader > <ExposeHeader > Content-MD5 </ExposeHeader > <ExposeHeader > Content-Disposition </ExposeHeader > <MaxAgeSeconds > 3600 </MaxAgeSeconds > </CORSRule > </CORSConfiguration >
You can so use s3cmd
to fix this as the CORS configuration for your Space:
- s3cmd setcors cors.xml s3://your-space-name-hither
There'due south no output when this control runs successfully, but you can check that it worked by looking at your Space in the DigitalOcean Control Console. Cull Spaces, then select the name of your Infinite, then select the Settings tab. You'll meet your configuration under the CORS Configurations heading:
Note: At the moment you lot need to use s3cmd
rather than the Control Panel to configure CORS for "localhost" domains because the Control Console treats these as invalid domains. If you lot're using a non-localhost domain (like a Droplet IP) it's condom to do it here.
At present you need to tell Rails to use straight uploads, which you do past passing the direct_upload
option to the file_field
helper. Open app/views/puppies/new.html.erb
in your editor and change the file_field
helper:
app/views/puppies/new.html.erb
<h2 > New Puppy </h2 > <%= form_with(model: @puppy) do |f| %> <div class = "form-particular" > <%= f.label :photograph %> <%= f.file_field :photograph, accept: "image/*", direct_upload: true %> </div > <div class = "grade-item" > <%= f.submit "Create puppy", class: "btn", data: { disable_with: "Creating..." } %> </div > <% end %>
Salvage the file and start your server once again:
- rails s -b 0.0.0.0
When yous upload a new photograph, your photo is uploaded directly to DigitalOcean Spaces. You can verify this by looking at the PUT
request that's made when y'all click the Create puppy button. You can find the requests past looking in your browser's spider web console, or by reading the Track server logs. You'll discover that the image upload is significantly faster, specially for larger images.
Conclusion
In this commodity you lot modified a basic Rails application using ActiveStorage to store files that are secure, fast, and scalable on DigitalOcean Spaces. Yous configured a CDN for fast downloads no matter where your users are located, and you implemented directly uploads then that your app servers volition not exist overwhelmed.
You can now take this code and configuration and adapt it to fit your own Rails application.
Source: https://www.digitalocean.com/community/tutorials/how-to-use-activestorage-in-rails-6-with-digitalocean-spaces
0 Response to "Upload an Image to Active Storage From a Url"
Post a Comment