Active Storage Ruby on Rails
Published: 2023-11-08 23:04:49

In today's guide, we'll explore how to seamlessly integrate Active Storage, a powerful feature of Ruby on Rails, into your web application. Active Storage allows you to manage file uploads, making it an essential tool for any project that requires handling images, documents, or any other form of file attachments. We will create a simple User model and configure image uploading for user avatars. We'll also use Active Storage Validations and Mini_Magick for image processing.
 
>For this guide an assumption is made that you already know how to setup and create a Ruby on Rails application that uses React JS for the frontend. 

---
## Prerequisites
Before we begin, ensure you have the following: 

- Ruby on Rails installed and setup for your project
- Node.js and Yarn installed (for React)
- Basic knowledge of Ruby on Rails and React

---
## Step 1: Setting Up a New Rails Application 
Let's start by creating a new Rails application. Open your terminal and run:
```bash 
rails new ActiveStorageExample --database=postgresql
cd ActiveStorageExample
```

This command will create a new Rails application named "ActiveStorageExample" using PostgreSQL as the database.

---
## Step 2: Configuring Active Storage

Now, let's configure Active Storage for our application. Install Active Storage in the terminal with: 

```bash
rails active_storage install 
rails db:migrate
```

 Add the following line to your `config/application.rb` file:

```ruby 
config.active_storage.variant_processor = :mini_magick
```

>This line configures Active Storage to use MiniMagick for image processing. If you don't need to process the images being uploaded you can skip this part

---
## Step 3: Creating the User Model

Next, let's generate a User model and all necessary files with a user name, first/last name and an avatar image. Run the following command:

```bash 
rails generate scaffold User username first_name last_name
rails db:migrate
```

Now, let's add Active Storage to our User model. In `app/models/user.rb`, make sure it looks like this: 


```ruby 
class User < ApplicationRecord
 has_one_attached :avatar
end
```

This associates the User model with Active Storage and enables us to attach avatar images.

---
## Step 4: Updating the User Controller

In your `app/controllers/users_controller.rb`, add the avatar attribute to the list of permitted parameters:

```ruby 
def user_params
 params.require(:user).permit(:name, :first_name, :last_name, :avatar)
end
```

---
## Step 5: Setting Up React as the Frontend

In this example, we'll use React as our frontend library. You can set up your React frontend as needed. Make sure you've installed the required dependencies and also set up your React components as you see fit. For reference I will use this form example below to handle the user data upload: 

```jsx
import React, { useState, useRef } from 'react';

const UserForm = () => {
  const [userData, setUserData] = useState({
    username: '',
    firstName: '',
    lastName: '',
  });
  const imageFile = useRef(null);

  const handleChange = (e) => {
    const { name, value } = e.target;
    setUserData({ ...userData, [name]: value });
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    
    const formData = new FormData();
    formData.append('user[username]', userData.username);
    formData.append('user[first_name]', userData.firstName);
    formData.append('user[last_name]', userData.lastName);
    formData.append('user[avatar]', imageFile.current.files[0]);

    try {
      const response = await fetch('/users', {
        method: 'POST',
        body: formData,
        // by using FormData() headers are not necessary
      });

      if (response.ok) {
        response.json().then((userData) => {
          console.log(userData)
        });
      } else {
        // Handle errors (e.g., show an error message)
        console.error('Error updating data.');
      }
    } catch (error) {
      console.error('An error occurred while submitting data:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit} method="post" >
      <div>
        <label htmlFor="username">Username:</label>
        <input
          type="text"
          id="username"
          name="username"
          value={userData.username}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="firstName">First Name:</label>
        <input
          type="text"
          id="firstName"
          name="firstName"
          value={userData.firstName}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="lastName">Last Name:</label>
        <input
          type="text"
          id="lastName"
          name="lastName"
          value={userData.lastName}
          onChange={handleChange}
        />
      </div>
      <div>
        // Insert Image file functionality here...
      </div>
      <div>
        <button type="submit">Submit</button>
      </div>
    </form>
  );
};

export default UserForm;
}

```

---
## Step 6: Adding Image Upload Functionality

Now, let's add image upload functionality to your React frontend. Create an input that allows users to select and upload their avatars. 

Here's a simplified example:

```jsx
    <div>
        <label htmlFor="avatar">Avatar:</label>
        <input
          type="file"
          id="avatar"
          name="avatar"
          ref={imageFile}
        />
    </div>
```

---
## Step 7: Adding Active Storage Validations

Active Storage Validations is a gem that provides easy-to-use validation rules for your attachments. Add it to your Gemfile:

```ruby
gem 'active_storage_validations'
``` 
Make sure to install the gem: 

```bash 
bundle install 
```

In your `User` model, add the following validations: 

```ruby 
class User < ApplicationRecord
  
  has_one_attached :avatar

  validates :avatar, content_type: ['image/png', 'image/jpg', 'image/jpeg']

end
```

---
## Step 8: Processing Images with MiniMagick

If you need to manipulate the uploaded images, you can use MiniMagick. It's already configured in your Rails application.

In your controller or a service, you can use MiniMagick to process the images. Here's an example of resizing the avatar:

```ruby 
user.avatar.variant(resize: '100x100').processed
```
By resizing the image down to 100x100-pixel square, we will allow for faster load times for our app.

---
## Step 9: Ensuring that the image data is being sent to the frontend

Much of the configuration and validations are handled at this point, however we need to send the image back to the front end to be rendered client-side. 

In your User model add a Rails helper and custom method:
 
```ruby 
class User < ApplicationRecord
  include Rails.application.routes.url_helpers

  has_one_attached :avatar

  validates :avatar, content_type: ['image/png', 'image/jpg', 'image/jpeg']

  def image_url 
    url_for(self.image)
  end
end
```

In your Serializer include this method to format the User image, make sure to pass the method name as an attribute: 

```ruby 
def image_format 
    return unless object.image.attached?
    object.image.blob.attributes
        .slice('filename','byte_size')
        .merge(url: object.image_url)
        .tap { |attrs| attrs['name'] = attrs.delete('filename')}
end
```

In your `development.rb` file located in config/environment folder include the default url options:

```ruby 
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000}
```
>The host and port values only apply to the dev environment, for a production environment make sure to make the necessary changes. 

In your `routes.rb` file include this line: 

```ruby 

default_url_options :host => "http://localhost:3000"

``` 
>Again this is only for the dev environment!

---
## Conclusion

**Congratulations! You've successfully integrated Active Storage into your Ruby on Rails application with a React frontend. You can now allow users to upload and display avatars while ensuring the files are in the correct format. Feel free to explore more features and customization options that Active Storage offers.**


**This is just the beginning. You can extend this functionality to handle more types of file uploads and integrate it into different parts of your application. Happy coding!**

--------
### More Resources:

[Active Record](https://guides.rubyonrails.org/active_record_basics.html)

[Setting up a Rails/React Project](https://www.digitalocean.com/community/tutorials/how-to-set-up-a-ruby-on-rails-v7-project-with-a-react-frontend-on-ubuntu-20-04)

[Setting up Amazon S3 for Active Storage](https://www.honeybadger.io/blog/rails-app-aws-s3/)