Activestorage Uploads Successfully but Link to Image Doesnt Work

The writer selected the Diversity in Tech fund to receive a donation as function of the Write for DOnations programme.

Introduction

When you're edifice web applications that let users upload and store files, you'll want to utilise a scalable file storage solution. This way you lot're not in danger of running out of space if your application gets wildly popular. Later on all, these uploads can be annihilation from profile pictures to house photos to PDF reports. You likewise want your file storage solution to be reliable and so you 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 exist affordable too.

DigitalOcean Spaces tin can address all of these needs. Considering it's uniform with Amazon'due south S3 service, y'all can apace integrate it into a Ruby on Rail application using the new ActiveStorage library that ships with Rails 6.

In this guide, you lot'll configure a Rails application, so information technology uses ActiveStorage with DigitalOcean Spaces. You'll then run through the configuration necessary to get uploads and downloads blazing fast using direct uploads and Spaces' built-in CDN (Content Delivery Network).

When yous're finished, you'll be set up to integrate file storage with DigitalOcean spaces into your own Rail application.

Prerequisites

Before yous begin this guide, yous'll need the post-obit:

  • A DigitalOcean account.
  • A development environment for Ruby on Rails. Follow How to Install Ruby on Rails with Rbenv on macOS or How to Install Ruby on Rails with Rbenv on Ubuntu depending on which operating system you're using.
  • Some initial Ruby on Rails knowledge. Tutorials like How to build a Reddish on Rails Application tin can help with this, or y'all can follow the Official Getting Started Guide.
  • Git installed on your development machine. You can follow the tutorial Contributing to Open up Source: Getting Started with Git to install and set up Git on your computer.
  • Node.js installed, which yous tin can practise by following How to Install Node.js and Create a Local Development Environment. This tutorial uses Node.js version 14.
  • The Yarn package manager installed, which yous tin install with npm install -g yarn.
  • SQLite installed, which you can do by following Step one of How to Build a Ruby on Rails Application.
  • Imagemagick installed, which you can install by following the tutorial How To Resize Images with ImageMagick.
  • (Optional) If you'd similar to experiment with the Spaces CDN, yous'll need a domain name and the ability to change DNS records for that domain. Y'all can follow the How to Add Domains tutorial to manage your domain with DigitalOcean.
  • (Optional) If you'd like to use Direct Uploads, y'all'll need to configure s3cmd to piece of work with DigitalOcean Spaces. Follow Setting Upwards s3cmd two.x with DigitalOcean Spaces to go that prepare.

Footstep 1 — Getting the Sample App Running

Rather than build a complete Rails awarding from scratch, you'll clone an existing Rails half dozen application that uses ActiveStorage and modify information technology to use DigitalOcean Spaces as its image storage backend. The app you'll piece of work with is Space Puppies, an image gallery that will let people upload and view photographs of their favorite puppies. The application looks similar the post-obit figure:

The Space Puppies application running in a web browser

Open your last and clone the awarding from GitHub with the following command:

                      
  1. git clone https://github.com/do-community/space-puppies

You'll see 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), done. remote: Total 122 (delta 3), reused 122 (delta 3), pack-reused 0 Receiving objects: 100% (122/122), 163.17 KiB | 1018.00 KiB/s, done. Resolving deltas: 100% (3/3), done.

Next, check your Ruby version. Space Puppies uses Ruby ii.7.i, so run rbenv versions to check which version you lot have installed:

                      
  1. rbenv versions

If you've followed the prerequisite tutorials, you'll only have Scarlet 2.5.1 in that list, and your output volition await like this:

                      

Output

* system 2.v.ane

If you don't take Ruby ii.7.1 in that list, install it using ruby-red-build:

                      
  1. rbenv install 2.7.one

Depending on your car's speed and operating organization, this might take a while. You'll see output that looks like this:

                      

Output

Downloading ruby-two.7.i.tar.bz2... -> https://enshroud.ruby-lang.org/pub/ruby/2.7/cherry-red-two.7.1.tar.bz2 Installing ruby-2.7.ane... Installed crimson-2.vii.1 to /root/.rbenv/versions/two.7.1

Change to the space-puppies directory:

                      
  1. cd space-puppies

rbenv will automatically change your Red version when you enter the directory. Verify the version:

                      
  1. ruby-red --version

You'll see output similar to the following:

                      

Output

ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]

Adjacent, you volition install the Ruddy gems and JavaScript packages that the app needs to run. Then yous'll the database migrations needed for the Space Puppies app to run.

Install all the necessary gems using the parcel command:

                      
  1. bundle install

And so, to tell rbenv virtually whatsoever new binaries installed by Bundler, use the rehash command:

                      
  1. rbenv rehash

Adjacent, tell yarn to install the necessary JavaScript dependencies:

                      
  1. yarn install

Now create the database schema with Rails' congenital-in migration tool:

                      
  1. rails db:migrate

With all the libraries installed and the database created, start the congenital-in web server with the following command:

                      
  1. rails s

Note: By default, rails s only binds to the local loopback address, meaning y'all can only access the server from the same computer that runs the command. If yous're running on a Droplet and yous'd like to admission your server from a browser running on your local auto, you'll need to tell the Track server to respond to remote requests by binding to 0.0.0.0. You lot can do that with this command:

                          
  1. track s -b 0.0.0.0

Your server starts, and yous'll receive output like this:

                      

Output

=> Booting Puma => Rails six.0.3.2 application starting in development => Run `runway server --help` for more startup options Puma starting in single way... * Version 4.3.five (ruby 2.7.i-p83), codename: Mysterious Traveller * Min threads: 5, max threads: 5 * Surroundings: development * Listening on tcp://127.0.0.1:3000 * Listening on tcp://[::1]:3000 Utilise Ctrl-C to stop

Now you can access your application in a web browser. If yous're running the application on your local auto, navigate to http://localhost:3000. If you're running on a Droplet or other remote server, then navigate to http://your_server_ip:3000.

Yous'll come across the app's interface, only this time without any puppies. Endeavor adding a couple of images by clicking the New Puppy button.

The Space Puppies application running in a web browser

If you need puppy photos to use for testing, Unsplash has an extensive list you can use for testing. Review the Unsplash license if you programme to use these images in your projects.

Before moving on, allow'southward walk through each layer of the application and look at how ActiveStorage works with each function so you can make the necessary changes for DigitalOcean Spaces. For a more detailed look at ActiveStorage, read the Active Storage Overview page in the official Runway documentation.

Offset, look at the model, which represents an object in your application that you're storing in the database. You'll discover the Puppy model in app/models/puppy.rb. Open this file in your text editor and you'll run into this code:

app/models/puppy.rb

                      class            Puppy            <            ApplicationRecord    has_one_attached            :photo            end                  

You'll find the has_one_attached macro in the model, which indicates at that place'due south a photo attached to each Puppy model instance. These photos will be stored as ActiveStorage::Blob instances via an ActiveStorage::Fastened::One proxy.

Close this file.

The adjacent layer up the stack is the controller. In a Rail application, the controller is responsible for controlling access to database models and responding to requests from the user. The corresponding controller for the Puppy model is the PuppiesController which y'all volition find in app/controllers/puppies_controller.rb. Open this file in your editor and you'll see the following code:

app/controllers/puppies_controller.rb

                      class            PuppiesController            <            ApplicationController            def                          index                        @puppies            =            Puppy.with_attached_photo            end            # ... snipped other actions ...            finish                  

Everything in the file is standard Rails code, apart from the with_attached_photo phone call. This call causes ActiveRecord to load all of the associated ActiveStorage::Hulk associations when yous fetch the listing of Puppy models. This is a scope that ActiveStorage provides to help you avoid an expensive N+1 database query.

Finally, let's look at the views, which generate the HTML your application will transport to the user'south browser. There are a few views in this app, but y'all'll want to focus on the view responsible for showing the uploaded puppy image. You lot'll find this file at app/views/puppies/_puppy.html.erb. Open it in your editor, and you'll see code similar this:

app/views/puppies/_puppy.html.erb

                                                    <div              grade                              =                "puppy"                            >                                                      <%=                                  image_tag puppy.photograph.variant(                  resize_to_fill                  :                  [                  250                  ,                  250                  ]                  )                                %>                                                                    </div              >                              

ActiveStorage is designed to work with Rails, so you tin 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 beginning requests this variant, ActiveStorage will automatically use ImageMagick via the image_processing gem, to generate a modified epitome fitting our requirements. In this case, it will create a puppy photo filling a 250x250 pixel box. The variant will be stored for yous in the same place every bit your original photo, which means you'll merely need to generate each variant once. Rails will serve the generated version on subsequent requests.

Note: Generating image variants can exist deadening, and you potentially don't desire your users waiting. If you know you're going to demand a particular variant, yous can eagerly generate it using the .candy method:

            puppy.photograph.variant(              resize_to_fill              :              [              250              ,              250              ]              )              .candy                      

It's a proficient idea to practice this kind of processing in a groundwork job when y'all deploy to production. Explore Agile Job and create a chore to telephone call candy to generate your images alee of fourth dimension.

Now your application is running locally, and you know how all the code pieces fit together. Next, it'southward time to set up a new DigitalOcean Space so you can move your uploads to the cloud.

Pace 2 — Setting upwards your DigitalOcean Space

At the moment, your Space Puppies awarding stores images locally, which is fine for development or testing, but you lot almost certainly don't want to use this manner in product. In society to calibration the application horizontally by adding more application server instances, you'd need copies of each prototype on every server.

In this stride, you'll create a DigitalOcean Space to use for your app's images.

Sign in to your DigitalOcean management console, click Create in the height right, and cull Spaces.

Pick whatever data centre and go out the CDN disabled for now; you'll come back to this subsequently. Ensure the file listing is gear up to Restrict File List.

Choose a proper name for your Infinite. Remember that this will have to be unique across all Spaces users, so pick a unique proper noun, like yourname-space-puppies. Click Create a Space:

A screenshot of the DigitalOcean create space form with a name filled  in

Alert: Be careful about access to the files you store on behalf of your customers. There have been many examples of data leaks and hacks due to misconfigured file storage. By default, ActiveStorage files are merely accessible if you generate an authenticated URL, but it's worth being vigilant if y'all're dealing with client information.

Yous'll then come across your make new Space.

Click the Settings tab and take a notation of your Space'south endpoint. Yous'll need that when you configure your Rails application.

Next, you'll configure the Rails application to store ActiveStorage files in this Space. To do that securely, you need to create a new Spaces Admission Key and Secret.

Click API in the left navigation, so click Generate New Key in the bottom correct. Give your new key a descriptive name like "Development Machine". Your secret will just announced once, so be certain to copy it somewhere safety for a moment.

A screenshot showing a Spaces access key

In your Runway app, you'll need a secure way to shop that access token, so y'all'll utilize Track' secure credential management feature. To edit your credentials, execute the following control in your terminal:

                      
  1. EDITOR = "nano -west" rails credentials:edit

This generates a main key and launches the nano editor so you tin can edit the values.

In nano, add the following to your credentials.yml file, using your API key and clandestine from DigitalOcean:

config/credentials.yml

                      digitalocean            :            access_key            :            YOUR_API_ACCESS_KEY            secret            :            YOUR_API_ACCESS_SCRET                  

Save and close the file (Ctrl+Ten, then Y, then Enter), and Rails will store an encrypted version that's safety to commit to source control in config/credentials.yml.enc.

You will come across output like the following:

                      

Output

Adding config/main.key to shop the encryption key: RANDOM_HASH_HERE Save this in a password manager your team can access. If you lose the key, no 1, including yous, can access annihilation encrypted with it. create config/master.key File encrypted and saved.

At present that yous've configured your credentials, you're ready to indicate your app to your new Spaces bucket.

Open the file config/storage.yml in your editor and add the following definition to the bottom of that file:

config/storage.yml

                      digitalocean            :            service            :            S3            endpoint            :            https://your-spaces-endpoint-here            access_key_id            :            <%= Rails.application.credentials.dig(:digitalocean,            :access_key) %>            secret_access_key            :            <%= Runway.application.credentials.dig(:digitalocean,            :underground) %>            bucket            :            your-infinite-name-here            region            :            unused                  

Note that the service says S3 rather than Spaces. Spaces has an S3-compatible API, and Track supports S3 natively. Your endpoint is https:// followed past your Space's endpoint, which you lot copied previously, and the bucket name is the name of your Infinite, which you entered when creating it. The saucepan proper name is also displayed as the title in your Command Panel when you view your Space.

This configuration file volition be stored unencrypted, so instead of entering your access primal and secret, yous're referencing the ones you just entered securely in credentials.yml.enc.

Annotation: DigitalOcean uses the endpoint to specify the region. Even so, you lot need to provide the region, or ActiveStorage will complain. Since DigitalOcean volition ignore it, yous can fix information technology to any value you lot'd like. The value unused in the example code makes it clear that y'all're non using information technology.

Salvage the configuration file.

At present, you need to tell Track to use Spaces for your file storage backend instead of the local file system. Open config/environments/development.rb in your editor and change the config.active_storage.service entry from :local: to :digitalocean:

config/environments/development.rb

                      # ...            # Store uploaded files on the local file organisation (meet config/storage.yml for options).            config.active_storage.service            =                          :digitalocean                        # ...                              

Salve the file and get out your editor. At present start your server again:

                      
  1. rails south -b 0.0.0.0

Visit http://localhost:3000 or http://your server ip:3000 in a browser once again.

Upload some images, and the app will store them in your DigitalOcean Space. You tin can come across this by visiting your Space in the DigitalOcean console. You will run into the uploaded files and variants listed:

files uploaded to a Space

ActiveStorage uses random filenames past default, which is helpful when protecting uploaded client data. Metadata, including the original filename, is stored in your database instead.

Note: If you are getting an Aws::S3::Errors::SignatureDoesNotMatch, that might mean your credentials are incorrect. Run rail credentials:edit again and double-check them.

Rail stores the names and some metadata about your files as ActiveStorage::Blob records. You can admission the ActiveStorage::Blob for any of your records by calling an accessor method named after your zipper. In this case, the attachment is called photo.

Try information technology out. Start a Rail console in your final:

                      
  1. rails c

Grab the hulk from the final puppy photo you uploaded:

                      >            Puppy.last.photo.hulk            #=> => #<ActiveStorage::Blob ...>                  

Yous now have a Rails Application storing uploads in a scalable, reliable, and affordable object store.

In the next two steps, you'll explore two optional additions y'all can brand to the app that will help amend this solution'south functioning and speed for your users.

Footstep 3 — Configuring the Spaces CDN (Optional)

Note: For this footstep, you volition need a doman with proper noun servers pointing to DigitalOcean. You can follow the How to Add together Domains guide to exercise that.

Using a Content Commitment Network (CDN) will allow you to provide faster downloads of files for your users by locating copies of the files closer to them.

You can investigate CDN performance using a tool like Uptrends CDN Performance Check. If you add together the URL for one of the photos y'all uploaded in the previous stride, you lot'll see things are fast if you happen to exist nearby, merely things go a little slower as you move away geographically. Y'all tin go the URL using the Developer Tools in your browser, or by starting a Track panel (rails c) and calling service_url on an zipper.

                      >            Puppy.concluding.photograph.service_url                  

Hither's an case Uptrends study with a file located in the San Francisco data center. Find that the times subtract depending on the altitude from San Francisco. San Diego has a brusk time, while Paris has a much longer fourth dimension:

An example Uptrends CDN Performance Report

You can improve speeds by enabling Spaces' congenital-in CDN. Go to Spaces in your DigitalOcean Control Panel and click the name of the Space you created in Pace ii. Next, cull the Settings tab and click Edit next to CDN (Content Commitment Network), then click Enable CDN.

Now you need to choose a domain to use for your CDN and create an SSL Certificate for the domain. Y'all can do this automatically using Allow's Encrypt. Click the Use a custom subdomain dropdown and then Add a new subdomain certificate.

Find the domain you'd like to use, then choose the option to create a subdomain. Something like cdn.yourdomain.com is a standard naming convention. You can and then give the certificate a proper noun and click the "Generate Document and Employ Subdomain" button.

The filled-in Add Custom Subdomain form

Printing the Salve button under CDN (Content Commitment Network).

Your CDN is now enabled, merely you need to tell your Rails Awarding to use it. This isn't congenital into ActiveStorage in this version of Runway, so you'll override some congenital-in Rails framework methods to make information technology work.

Create a new Rails initializer called config/initializers/active_storage_cdn.rb and add the following lawmaking which will rewrite the URLs:

config/initializers/active_storage_cdn.rb

          Runway.awarding.config.after_initialize            do            require                          "active_storage/service/s3_service"                        module            SimpleCDNUrlReplacement            CDN_HOST            =                          "cdn.yourdomain.com"                        def                          url                        (            ...            )            url            =            super            original_host            =                          "                              #{                bucket.proper noun                }                            .                              #{                customer.client.config.endpoint.host                }                            "                        url.gsub(original_host,            CDN_HOST            )            end            end            ActiveStorage::Service::S3Service.            prepend            (SimpleCDNUrlReplacement)            terminate                  

This initializer runs each time your application asks for a URL from an ActiveStorage::Service::S3Service provider. Information technology and then replaces the original, non-CDN host with your CDN host, divers as the CDN_HOST constant.

Y'all can now restart your server, and you'll find that each of your photos comes from the CDN. You lot won't demand to re-upload them, as DigitalOcean will accept intendance of forwarding the content from the information center where you fix your Space out to the edge nodes.

Y'all might like to compare the speed of accessing one of your photos on Uptrends' Performance Cheque site now to the pre-CDN speed. Hither's an example of using the CDN on a San Francisco-based Space. You can see a significant global speed comeback.

The Uptrends CDN Performance Report after enabling the CDN

Next you'll configure the awarding to receive files direct from the browser.

Stride 4 — Setting up Direct Uploads (Optional)

One concluding characteristic of ActiveStorage that yous might like to consider is called a Direct Upload. At present, when your users upload a file, the data is sent to your server, processed by Rails, and so forwarded to your Space. This can cause problems 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 entire duration of an upload.

Past contrast, a Directly Upload will go straight to your DigitalOcean Space with no Rail server hop in between. To do this, you'll enable some built-in JavaScript that ships with Track and configure Cross-Origin Resources Sharing([CORS]((https://developer.mozilla.org/en-U.s./docs/Spider web/HTTP/CORS) on your Space so that you can securely send requests directly to the Space despite them originating in a different place.

First, you'll configure CORS for your Infinite. Yous will use s3cmd to do this, and you can follow Setting Upwards s3cmd 2.x with DigitalOcean Spaces if you haven't configured this to work with Spaces withal.

Create a new file called cors.xml and add the following lawmaking to the file, replacing your_domain with the domain you're using for development. If you are developing on your local machine, you'll utilise http://localhost:3000 . If you're developing on a Droplet, this volition be your Droplet IP address:

cors.xml

                                                    <CORSConfiguration              >                                                      <CORSRule              >                                                      <AllowedOrigin              >                        your_domain                                          </AllowedOrigin              >                                                      <AllowedMethod              >            PUT                              </AllowedMethod              >                                                      <AllowedHeader              >            *                              </AllowedHeader              >                                                      <ExposeHeader              >            Origin                              </ExposeHeader              >                                                      <ExposeHeader              >            Content-Blazon                              </ExposeHeader              >                                                      <ExposeHeader              >            Content-MD5                              </ExposeHeader              >                                                      <ExposeHeader              >            Content-Disposition                              </ExposeHeader              >                                                      <MaxAgeSeconds              >            3600                              </MaxAgeSeconds              >                                                      </CORSRule              >                                                      </CORSConfiguration              >                              

You can then use s3cmd to set this as the CORS configuration for your Space:

                      
  1. s3cmd setcors cors.xml s3://your-space-name-here

At that place'south no output when this command runs successfully, merely you tin check that it worked by looking at your Space in the DigitalOcean Control Panel. Cull Spaces, and so select the proper name of your Infinite, then select the Settings tab. You lot'll see your configuration under the CORS Configurations heading:

A successful CORS configuration for direct uploads

Note: At the moment y'all need to utilize s3cmd rather than the Control Panel to configure CORS for "localhost" domains because the Control Panel treats these as invalid domains. If you're using a not-localhost domain (like a Droplet IP) it's safe to do it hither.

At present you demand to tell Rails to utilise straight uploads, which you practice past passing the direct_upload option to the file_field helper. Open app/views/puppies/new.html.erb in your editor and modify the file_field helper:

app/views/puppies/new.html.erb

                                                    <h2              >            New Puppy                              </h2              >                        <%= form_with(model: @puppy) do |f| %>                                          <div              class                              =                "form-item"                            >                        <%= f.label :photo %>     <%= f.file_field :photo, accept: "epitome/*",            direct_upload: true            %>                                          </div              >                                                      <div              class                              =                "form-item"                            >                        <%= f.submit "Create puppy", class: "btn", information: { disable_with: "Creating..." } %>                                          </div              >                        <% cease %>                  

Salve the file and starting time your server once more:

                      
  1. rails s -b 0.0.0.0

When y'all upload a new photo, your photo is uploaded directly to DigitalOcean Spaces. You tin verify this past looking at the PUT request that's fabricated when you click the Create puppy button. You lot can find the requests by looking in your browser's web console, or by reading the Rails server logs. You lot'll notice that the paradigm upload is significantly faster, especially for larger images.

Conclusion

In this article you modified a basic Rail application using ActiveStorage to shop files that are secure, fast, and scalable on DigitalOcean Spaces. You configured a CDN for fast downloads no affair where your users are located, and you implemented direct uploads so that your app servers will non be overwhelmed.

You tin can at present accept this code and configuration and accommodate it to fit your own Rails application.

landapuppere37.blogspot.com

Source: https://www.digitalocean.com/community/tutorials/how-to-use-activestorage-in-rails-6-with-digitalocean-spaces

0 Response to "Activestorage Uploads Successfully but Link to Image Doesnt Work"

Enviar um comentário

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel