I recently wanted to strip the Exif metadata from user-uploaded, ActiveStorage-backed images in Rails. The reason is that images taken with mobile phones are usually geotagged and contain the user’s location, which is sensitive personal data.
Initially, I thought of implementing it in the model by calling the relevant
code with a
before_save callback, having it process the attached image.
Sadly, I wasn’t able to find a publicly-documented, usable API in ActiveStorage that allows this kind of manipulation. After some digging, I did manage to make it work, but the implementation was based on an internal, undocumented API, so I quickly abandoned it.
Given this limitation, I quickly realized that the most viable solution would be to take care of things before the image file ever reaches ActiveStorage. This way, I wouldn’t have to mess with its quirky API.
sudo apt install imagemagick # Debian/Ubuntu
Install the mini_magick Gem:
bundle add mini_magick
Add the following code in your controller (or a separate library file):
private def strip_exif_from_attachment_params(record, record_params) record.attachment_reflections.each_key do |attachment_attribute| Array(record_params[attachment_attribute]).each do |file_param| next if file_param.content_type != 'image/jpeg' uploaded_file = file_param.tempfile image = MiniMagick::Image.read(uploaded_file) image.auto_orient # Before stripping image.strip # Strip Exif uploaded_file.rewind image.write(uploaded_file) end end end
Things to note:
- Line 1: Accept the record as a parameter
- Line 2: Use reflection on the record to figure out its ActiveStorage attachments (so it operates on all of them)
- Line 4: Skip attachments that are not JPEG images
- Line 6: Operate on the uploaded file’s Tempfile
- Line 8: Use Exif metadata, before stripping, to orient the image (e.g. fix it if it’s rotated)
- Line 9: Strip Exif metadata
- Line 11: Persist changes to the file
Assuming you have a user model with an attached avatar:
class User < ApplicationRecord has_one_attached :avatar end
Here’s an example on how you’d use it:
class UsersController < ApplicationController before_action :set_user, only: %i[update] def update strip_exif_from_attachment_params(update_params) if !@user.update(update_params) flash.now[:alert] = 'Failed to update user' render :edit return end redirect_to user_path(@user), notice: 'User updated' end private def set_user @user = User.find(params.require(:id)) end private def update_params params.require(:user).permit(:avatar) end end