Migrating from Refile to ActiveStorage
Tags: rails, refile, activestorage
Published: 2021-03-04 16:03:24

Historically I've been using the [Refile gem](https://github.com/refile/refile) for attaching files to my Rails models. But the gem hasn't been maintained for a long time and since Rails 5.2 ActiveStorage has been my preferred way to go.

These days I've been working on upgrading some of our older Rails 5.1 applications to Rails 6.0 and now I started seeing deprecation warnings on Refile, so It's time to say goodbye to Refile and hello to ActiveStorage.

Here I'll show you how I migrated my users avatar images from Refile to ActiveStorage.

### The basics
On my User model I've replaced Refile's `attachment :avatar` with ActiveStorage's `has_one_attached :avatar`

In my `Users#form` view I've replaced the Refile field:
```erb
<%= f.attachment_field :avatar %>
```

with a regular `file_field` for ActiveStorage:
```erb
<%= f.file_field :image %>
```

Finally in my views when I wanna show the avatar image I moved from Refile's:
```erb
<%= image_tag attachment_url(user, :avatar, :fill, 75, 75) %>
```

to ActiveStorage:
```erb
<%= image_tag user.avatar.variant(resize_to_limit: [75, 75]) %>
```

You should check out the [Rails guides](https://edgeguides.rubyonrails.org/active_storage_overview.html) for more options on ActiveStorage.


### The migration
Great, with my frontend ready to handle ActiveStorage let's move on to the actual file migrations.
I've been using AWS S3 for hosting my Refile files.

Refile relies on the AWS SDK v2. Before I ran this migration I've removed the Refile gems including the AWS SDK v2 with the following gems for ActiveStorage in my Gemfile:
```
gem "aws-sdk-s3", require: false
gem 'image_processing'
```

Historically my User model just had a single Refile database field: `avatar_id` (string).
I wish I had used Refile's content_type detection as well, but I didn't, so in my migration I decided to use ImageMagick to detect the file type of my users images.

I created the following migration file in `db/migrate/moving_from_refile_to_active_storage.rb`:
```ruby
require 'mini_magick' # included by the image_processing gem
require 'aws-sdk-s3' # included by the aws-sdk-s3 gem

class User < ActiveRecord::Base
  has_one_attached :avatar
end

class MovingFromRefileToActiveStorage < ActiveRecord::Migration[6.0]
  def up
    puts 'Connecting to AWS S3'
    s3_client = Aws::S3::Client.new(
      access_key_id: ENV['AWS_S3_ACCESS_KEY'],
      secret_access_key: ENV['AWS_S3_SECRET'],
      region: ENV['AWS_S3_REGION']
    )

    puts 'Migrating user avatar images from Refile to ActiveStorage'
    User.where.not(avatar_id: nil).find_each do |user|
      tmp_file = Tempfile.new

      # Read S3 object to our tmp_file
      s3_client.get_object(
        response_target: tmp_file.path,
        bucket: ENV['AWS_S3_BUCKET'],
        key: "store/#{user.avatar_id}"
      )

      # Find content_type of S3 file using ImageMagick
      # If you've been smart enough to save :avatar_content_type with Refile, you can use this value instead
      content_type = MiniMagick::Image.new(tmp_file.path).mime_type

      # Attach tmp file to our User as an ActiveStorage attachment
      user.avatar.attach(
        io: tmp_file,
        filename: "avatar.#{content_type.split('/').last}",
        content_type: content_type
      )

      if user.avatar.attached?
        user.save # Save our changes to the user
        puts "- migrated #{user.try(:name)}'s avatar image."
      else
        puts "- \e[31mFailed to migrate the avatar image for user ##{user.id} with Refile id #{user.avatar_id}\e[0m"
      end

      tmp_file.close
    end

    # Now remove the actual Refile column
    remove_column :users, :avatar_id, :string
    # If you've created other Refile fields like *_content_type, you can safely remove those as well
    # remove_column :users, :avatar_content_type, :string
  end

  def down
    raise ActiveRecord::IrreversibleMigration
  end
end
```

Just to be safe I chose not to delete Refile files from my S3 bucket in this migration file. I could of course have done so, but I choose to delete the `storage` and `cache` folders in my bucket manually instead.

That's it. After having run this migration all my users avatar images are now handled through ActiveStorage.

Good bye Refile.