Store images with Rails ActiveStorage and Dropbox
I recently implemented cloud image hosting using Rails ActiveStorage and Dropbox. These are the resources that helped me to do it:
- Pragmatic Studio’s Using ActiveStorage in Rails
- Deleting attached images
- gem activestorage-dropbox
- gem dropbox_api
ActiveStorage has adapters for a few cloud storage systems (ex: Amazon S3), but it does not have one for Dropbox, so I used the activestorage-dropbox
gem to take this project over the finish line. My PR: https://github.com/lortza/yayme/pull/39/files
1. Set Up ActiveStorage
# install active_storage
rails active_storage:install
# run the associated migrations
rails db:migrate
Add the active storage association and associated logic in the model where you want the image to go. I’m adding one to my Post
model.
# app/models/post.rb
# add association
has_one_attached :image
# call a custom validation
validate :acceptable_image
# add ability to delete attached images
attr_accessor :remove_attached_image
after_save :purge_attached_image, if: :remove_attached_image?
# define the acceptable_image validation
def acceptable_image
return unless image.attached?
if image.byte_size > 1.megabyte
image_size = (image.byte_size / 1_000_000.0).round(2)
errors.add(:image, "size #{image_size} MB exceeds 1 MB limit")
end
acceptable_types = ['image/jpeg', 'image/jpg', 'image/png']
errors.add(:image, 'must be a JPEG or PNG') unless acceptable_types.include?(image.content_type)
end
# define the after_save action to remove an image
def remove_attached_image?
remove_attached_image == '1'
end
def purge_attached_image
image.purge_later
end
Add the new image field to the post
’s form. If there is an image attached, I want to see the image name, and a thumbnail variant of the image (requires ImageMagick, explained later). I also want to include a checkbox to delete the image. (The code below includes Twitter Bootstrap CSS classes. Skip them if you’re not using Bootstrap.)
<%= form.label :image %>
<% if post.image.attached? %>
<!-- display the image name and thumbnail -->
<p>
<%= image_tag(post.image.variant(resize_to_limit: [75, 75])) %><br>
<%= post.image.filename %>
</p>
<!-- provide a check box to delete the image -->
<div class='form-check'>
<%= form.check_box :remove_attached_image, class: 'form-check-input' %>
<%= form.label 'Check to remove attached image', class: 'form-check-label' %>
</div>
<% end %>
<%= form.file_field :image, class: 'file-field' %>
Display the image on the posts/show.html.erb
file:
<!-- app/views/posts/show.html.erb -->
<%= image_tag(@post.image, title: @post.image.filename) if @post.image.present? %>
Add the new image params to the posts_controller.rb
whitelist:
# app/controllers/posts_controller.rb
def post_params
params.require(:post).permit(:title, :body, :image, :remove_attached_image)
end
Add variant image processing to allow thumbnail rendering by adding the image_processing gem to the Gemfile and running bundle install
.
# Gemfile
gem 'image_processing', '~> 1.2'
On the command line, run bundle and then ensure you’ve installed ImageMagick:
bundle install
# for mac Homebrew users
brew install imagemagick
# for linux users
sudo apt-get install imagemagick
2. Create your app storage folder in Dropbox
Go to Dropbox’s developer page for setting up apps and select the options:
- Create App
- Choose an API: Dropbox API
- Choose the type of access you need: App Folder
- Enter the name of your app – or what you want your app’s folder name to be
This will create an app folder in your Dropbox account that is used for storage for your app. “Your app gets read and write access to this folder only and users can provide content to your app by moving files into this folder.” (from docs)
Get the credentials you need from your app page in Dropbox. You’ll want to get values for all of these fields and add them to your Rails credentials file. If using Rails credentials is new to you, carefully read that section of Using ActiveStorage in Rails. It’s important that you understand this and get it right.
I open my credentials file using the Atom text editor, so my command looks like:
EDITOR="atom --wait" bin/rails credentials:edit
Never try to open this file or your master key file directly. I’ve done this in the past and have ruined the encryption because my text editor added a newline at the end of the file. It’s a pickle you don’t want to be in.
Here is what you’ll need to add to your Rails credentials file. Replace all of this text with your own values.
# config/credentials.yml.enc
dropbox:
app_key: 111222333444
app_secret: 111222333444
access_token: 111222333444
user_id: your-dropbox-associated-email-address@email.com
When you save and close the file, Rails encrypts it for you. We’ll access those values in a bit by using Rails.application.credentials.some_value
.
If you haven’t done so already, add your RAILS_MASTER_KEY
to your production environment. I use Heroku, so I added it by running this on the command line:
heroku config:set RAILS_MASTER_KEY=yourmasterkeynumbershere
# ex
heroku config:set RAILS_MASTER_KEY=12345123451234512345
You can confirm that it worked by going to your Heroku settings https://dashboard.heroku.com/apps/your-app-name/settings
and looking in the Config Vars
section. Also, do yourself a favor and store your master key somewhere safe like a password manager. You don’t want to lose it.
3. Connect ActiveStorage and Dropbox
Add these adapter gems to your Gemfile. They hold the logic that connects ActiveStorage to your Dropbox app folder.
gem 'activestorage-dropbox' # adapter for dropbox
gem 'dropbox_api' # required for `activestorage-dropbox` gem
Run bundle
bundle install
Declare a Dropbox service in config/storage.yml
for the activestorage-dropbox
to read. This file is not encrypted, so we need to add these values by referencing the credentials file inside of erb
tags like this:
# config/storage.yml
dropbox:
service: Dropbox
app_key: <%= Rails.application.credentials.dig(:dropbox, :app_key) %>
app_secret: <%= Rails.application.credentials.dig(:dropbox, :app_secret) %>
access_token: <%= Rails.application.credentials.dig(:dropbox, :access_token) %>
user_id: <%= Rails.application.credentials.dig(:dropbox, :user_id) %>
access_type: app
To use the Dropbox service in development, you add the following to config/environments/development.rb
:
config.active_storage.service = :dropbox
# or you can keep your development storage "local", which is what I do:
config.active_storage.service = :local
To use the Dropbox service in production, you add the following to config/environments/production.rb
:
config.active_storage.service = :dropbox
That should do it. Now you’ll be able to upload images from a form, display them on a page, and delete them from a form.