DEV Community

Tyler Smith
Tyler Smith

Posted on • Edited on

Resize images before saving them with Ruby on Rails Active Storage

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 to resize images, so either uncomment image_processingfrom you Gemfile, or add it if it is not already present.

# Gemfile

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

After that, install your Gemfile dependencies:

bundle install
Enter fullscreen mode Exit fullscreen mode

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

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
Enter fullscreen mode Exit fullscreen mode

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:

before_action lambda {
  resize_before_save(user_params[:profile_picture], 100, 100)
}, only: [:update]
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode

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 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 and Donapieppo 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!

Top comments (2)

Collapse
 
tundeiness profile image
Tunde Oretade

shouldn't resize_before_save be in the model instead of the controller?

Collapse
 
tylerlwsmith profile image
Tyler Smith • Edited

It could be if you wanted it to be 🤷

For me, it feels weird having the user model knowing about image resizing, though it's possible this might work in the before_save and before_update hooks. However, then you have to do something clever to make sure that you aren't resizing the image on every save.

My app is pretty simple and the user controller is the only place that a user profile picture can be set, so I'm comfortable with this on the controller.