Skip to content

Release 1.7

Compare
Choose a tag to compare
@the-teacher the-teacher released this 19 Feb 11:10
· 130 commits to master since this release

Rails 7. Start Kit and ActiveStorage

Screenshot 2023-02-19 at 13 26 46

Rails 7 Start Kit — Dockerized Rails App with the most popular preinstalled tools.

Install and Run Ruby on Rails now!

Copy & Paste in your terminal

git clone https://github.com/the-teacher/rails7-startkit.git && \
  cd rails7-startkit && \
  bin/setup

I've done a mistake when I've chosen ActiveStorage

I didn't write any rails code last 5 or 6 years. I've heard that now Rails has a default solution to upload files. Moreover I saw the following message on the paperclip page

Screenshot 2023-02-19 at 13 40 45

Since that moment it was clear to me that I have to use ActiveStorage for image uploading. Probably all other uploading solutions just died for rails.

Intuitively I expected that ActiveStorage will help me to do all the same things that I did for paperclip or carrierwave. Storage is just storage. What can be wrong with it?

When I have installed ActiveStorage I found that it is impossible to setup a human-friendly path for files' location. All file paths and files look like that.

Screenshot 2023-02-19 at 09 48 04

I was confused, and looks like I'm not the first person who was. Link 1 Link 2

I looked for solutions or workarounds. I asked people in different RoR communities, and I found that not so much people understand why it is so.

Some people just switch to other solutions like carrierwave or shrine.

After a small investigation I should say the following:

ActiveStorage is a cloud solution

  • If you read a definition: Active Storage makes it simple to upload and reference files in cloud services like Amazon S3, Google Cloud Storage, or Microsoft Azure Storage, and attach those files to Active Records.
  • You should accept that Active Storage will use not a human-friendly paths for your files
  • You should accept that Active Storage will use not a human-friendly file names
  • You should accept that Active Storage will stream your files via the Application

If you want to to have human-friendly locations and file names to navigate on it sometimes and feel a full control over the situation -- Active Storage is a mistake. You probably should use something else. I wanted just a "typical" file uploading solution with a friendly file structure like paperclip or carrierwave, but AS is a bit different. I needed time to accept it.

⚠️ I've done a mistake when I've chosen Active Storage as a typical file upload solution. It is not so simple as I expected.

I relied on my intuition, but not on the description of the library. I think Active Storage should be renamed to Active Cloud Storage to avoid confusing people like me, who like to do something but not to read a boring definitions on a Readme page 😄

Conclusion

Now when I see all aspects and boundaries of this solution I can choose exactly what I need for my specific cases in future. I think sometimes I can even use multiple solutions in the same app for different models and goals.

Using ActiveStorage for User Avatar in 10 steps

Step 1

Like it was described in the official documentation I did

For generation of database migrations and migrating

$ bin/rails active_storage:install

$ bin/rails db:migrate

No need modify config/storage.yml. We will go with default settings

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

Step 2

Now it is time to improve app/models/user.rb and add an attachment

class User < ApplicationRecord
  # img_path = Rails.root.join('public/Rails7.StartKit.jpg')
  # u = User.first
  # u.avatar.attach(io: File.open(img_path), filename: 'avatar.jpg')
  #
  # u.avatar.purge
  has_one_attached :avatar

   ...
end

As you see I left some commands in the comments to check the functionality from Rails Console

Step 3

Checking that everything works from Rails Console

From the root of the application

$ rails c

Now when you are in Rails Console:

img_path = Rails.root.join('public/Rails7.StartKit.jpg')

u = User.first

u.avatar.attach(io: File.open(img_path), filename: 'avatar.jpg')

Screenshot 2023-02-19 at 14 22 16

In the project in storage folder you will see something like that:

Screenshot 2023-02-12 at 15 07 48

Everything works!

Step 4

Now we ned to modify the app. We use Devise and we need to integrate our uploading with it. No problems!

We create a controller for User model.

I created the file app/controllers/users_controller.rb manually. It is simple and I do not need any code generator.

# frozen_string_literal: true

class UsersController < ApplicationController
  before_action :authenticate_user!, except: :profile

  def profile; end

  def update
    current_user.update(user_params)
    redirect_to profile_user_path, notice: 'Avatar is uploaded'
  end

  private

  def user_params
    params.require(:user).permit(:avatar)
  end
end

Step 5

To have an access to this controller I need routing. config/routes.rb

Rails.application.routes.draw do
  devise_for :users
  root 'demo#index'
   
   ...

  resource :user, only: [:update] do
    get :profile
  end
end

Step 6

I need a link to my profile page

<div class="examples">
   ...
  <%= link_to "Upload Avatar", profile_user_path, class: 'example-item' %>
</div>

Step 7

When I'm not a logged in user I should not have an access to uploading, because I do not know a user to which I'm going to attach my avatar.

Screenshot 2023-02-19 at 14 33 38

In the file app/views/users/profile.html.erb

  <% unless user_signed_in? %>
    <p>
      You can not upload an avatar until user is logged in:
      <%= link_to "Sign In", new_user_session_path %>
    </p>

    <p>
      email: <b>admin@rails-start.com</b>
    </p>

    <p>
      password: <b>qwerty123</b>
    </p>
  <% end %>

If you have passed logging process you will see

Screenshot 2023-02-19 at 14 35 22

  <% if user_signed_in? %>
    <p>
      Current user: <%= current_user.email %> |
      <%= link_to "Sign Out", destroy_user_session_path%>
    </p>

    <% if current_user.avatar.attached? %>
      <%= image_tag current_user.avatar, class: :user_avatar %>
    <% end %>

    <%= form_for current_user, method: :patch do |form| %>
      <p><%= form.file_field :avatar %></p>
      <p><%= form.submit "Upload" %></p>
    <% end %>
  <% end %>

Step 8

Now you can upload a new file and check how it works.

Screenshot 2023-02-19 at 14 37 15

Step 9

You can visit web inspector and check how actually you image was received. Definitely Rails controller was involved into this process.

Screenshot 2023-02-19 at 09 49 03

Step 10

Company JetBrains found my project interesting and I was given a licence to use RubyMine for my project. They didn't ask about anything in return, but I would like to acknowledge them. I found their IDE really helpful.

Screenshot 2023-02-19 at 14 40 50

That is it!

👉 Subscribe to the project to know about most recent updates.

Happy coding with Rails 7. Start Kit