Strip Exif metadata from images in Rails
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.
The solution
Install ImageMagick:
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(@user, 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
Have fun!