Resize images before saving them with Ruby on Rails Active Storage
Tags: rails, activestorage, ruby
Published: 2023-04-30 06:56:25

---
title: Resize images before saving them with Ruby on Rails Active Storage
published: true
description: Leverage Rails' included image_processing gem to resize your images before you pay for them on S3.
tags: rubyonrails, activestorage, ruby
# cover_image: https://direct_url_to_image.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-03-03 09:25 +0000
---

By default, Ruby on Rails Active Storage saves images as-is, then allows them to be resized at a later time using the attachment's `variant` method.

Sometimes you may want to resize an image _before_ you save it. Rails doesn't provide a built-in way of accomplishing this, but it can be achieved by intercepting the image from `params`, then editing it in place before you call your model's `save` or `update` methods.

## Setup

This post assumes that you have already run the Active Storage database migrations and have a model that uses a `has_one_attached` field.

Ruby on Rails 7 uses the [`image_processing` gem](https://github.com/janko/image_processing) to resize images, so either uncomment `image_processing`from you Gemfile, or add it if it is not already present.

```rb
# Gemfile

# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
gem "image_processing", "~> 1.2"
```

After that, install your Gemfile dependencies:

```sh
bundle install
```

Next, add the following `private` method into a controller where you want to resize an image before persisting it to Active Storage.

```rb
def resize_before_save(image_param, width, height)
  return unless image_param

  begin
    ImageProcessing::MiniMagick
      .source(image_param)
      .resize_to_fit(width, height)
      .call(destination: image_param.tempfile.path)
  rescue StandardError => _e
    # Do nothing. If this is catching, it probably means the
    # file type is incorrect, which can be caught later by
    # model validations.
  end
end
```

This method accepts a `param` that contains an image and modifies its temporary file that is created on disk before it is uploaded to the selected Active Storage service.

Finally, add a `before_action` that runs the resize before the actions where you'd like to resize the image:

```rb
before_action lambda {
  resize_before_save(user_params[:profile_picture], 100, 100)
}, only: [:update]
```

## A complete example

You can now resize images before they are persisted with Active Storage by calling the `resize_before_save` method prior to calling the `update` action.

```rb
class UsersController < ApplicationController
  before_action :set_defaults
  before_action :authenticate_user!
  before_action lambda {
    resize_before_save(user_params[:profile_picture], 100, 100)
  }, only: [:update]

  def edit
  end

  def update
    respond_to do |format|
      if @user.update(user_params)
        format.html { redirect_to edit_user_url(@user) }
        format.json { render :show, status: :ok, location: @user }
      else
        format.html { render :edit, status: :unprocessable_entity }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  private

    def set_defaults
      @user = User.find(params[:id])
    end

    def user_params
      params.require(:user).permit(
        :display_name,
        :profile_picture,
      )
    end

    def resize_before_save(image_param, width, height)
      return unless image_param

      begin
        ImageProcessing::MiniMagick
          .source(image_param)
          .resize_to_fit(width, height)
          .call(destination: image_param.tempfile.path)
      rescue StandardError => _e
        # Do nothing. If this is catching, it probably means the
        # file type is incorrect, which can be caught later by
        # model validations.
      end
    end
end
```

## Troubleshooting

Occasionally when uploading files in development, I've received an `ActiveSupport::MessageVerifier::InvalidSignature` error. I don't know what causes this, but restarting my development server has typically fixed the issue.

## Parting thoughts

I recommend pairing this approach with the [`active_storage_validations` gem](https://github.com/igorkasyanchuk/active_storage_validations) and adding a validation to the attachment field to ensure that the attachment that you'd like to resize is an image. Ideally, an application should gracefully handle invalid input with useful error messages.

I also want to give credit to posts by [Elaine Osbourn](https://tosbourn.com/active-storage-image-compression/) and [Donapieppo](https://stackoverflow.com/a/56167444/7759523) that were instrumental in helping me figure this out.

If you liked this post, please leave it a like, or comment if you know a better way of doing this!