How to setup active storage w/ AWS S3
Published: 2023-09-06 20:10:45

For my final project at Flatiron School we had to add some new technology to our project that we had never used or learned about before and I decided on using Active Storage with AWS S3 to allow a user to upload an avatar. Upon researching, reading blogs, documentation, and watching online tutorials I found that I was having to use many different resources just to figure out how to get started, and therefor have decided to write a step by step tutorial all in one place for whoever needs to get started with Active Storage. 

**<u>Active Storage setup</u>**

First things first, I recommend reading the active storage documentation in rails or at least referencing it if you find that you are having any issues: https://guides.rubyonrails.org/active_storage_overview.html 

but I will lay out the necessary steps for you here as well. 

- install "libvips" or "ImageMagick" for image analysis and transformations

- both of these require the image_processing gem so uncomment it in your Gemfile or add it if you don't see it there already: 
`gem "image_processing", ">= 1.2"`

- Active storage uses three tables in your database: 
• active_storage_blobs
• active_storage_variant_records
• active_storage_attachments

- to create these tables run "bin/rails active_storage:install" and after migrate them by running "bin/rails db:migrate" 

Here is a photo of my schema file for reference for how yours should look (note please ignore the "photo" column in my users table I had this here as a temporary column for when I was working on the project before I had implemented Active Storage) 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u97b45a2p5tt1h1ejdyo.png)

- Declare active storage services in "config/storage.yml"

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hn4u4y5gh577wvzmnl8y.png)


In "config/environments/development" tell Active Storage which service to use
`config.active_storage.service = :local`

To use the S3 service in production add this line of code to "config/environments/production"
`config.active_storage.service = :amazon`

To use the test service add this line of code to "config/environments/test"
`config.active_storage.service = :test` 

**<u>Setting up your Models, Controllers, and Serializers</u>**

Since I was adding the ability for a user to upload an avatar when creating an account I added the following line of code to my user model: 
`has_one_attached :image`

this line of code is very important and establishes the association, do not be confused that this will not be reflected as a column in your schema file 

Add the "with_attached_image" method to you Users Controller:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ev2jc6gkmfbmrttlcgn7.png)

and make sure to include ":image" into your user params
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6kzqt9bet69gba6clewy.png)

Modify your User Serializer like so:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4av3cwpp5hl9slvxds7b.png)
(adding ":image" to attributes and the "image" custom method)

**<u>Setting up AWS</u>**

- make sure your service in config/storage.yml set to "S3"

- add the "aws-sdk-s3" gem to your gemfile
`gem 'aws-sdk-s3', require: false`

Create an AWS account and create a bucket in S3 (you can change your bucket name in "config/storage.yml" to your specific bucket name once we create the bucket, and the region to whatever your specific region once we select it in AWS S3

Once you're logged in click on "S3"
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/80gkgo7qb6g14rxgnf95.png)

Click "Create Bucket"
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnhgo69k77e0kkcswl5a.png)

You will be taken to this page:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2jsqdh0z0qhz45uxejgh.png)

Fill in the form as so: 

**Bucket name** - yourbucketname
**AWS Region** - your region
**Object Ownership** - ACLS disables
**Block Public Access settings for this bucket** - Block all public access
**Bucket Versioning** - Disable
**Tags** - leave empty
**Default encryption** - Server-side encryption w/ Amazon S3 managed keys(SSE-S3)
**Bucket Key** - Enable

* for more information and understanding on why you should set "Object Ownership" and Public access settings a certain way I recommend watching this video: https://www.youtube.com/watch?v=UOLpv2f8mz8&t=779s

Once you are done click "create bucket" 

Click on the name of your new bucket where you will now be taken to a page where you can edit your settings

Click on permissions and scroll to the Bucket Policy section and click on the "edit" button and then the "Policy Generator" button. 

Fill out the form as so: 

**Select Type of Policy** - S3 Bucket Policy
**Effect** - Allow
**Principal** - arn:aws:iam::ACCOUNT_ID:root
**AWS Service** - Amazon S3
**Actions** - DeleteObject, GetObject, PutObject
**Amazon Resource Name (ARN)** - arn:aws:s3:::BUCKET_NAME/*

* To find your account ID click your username in the top right corner and your account id will appear

* you can find your ARN in the properties section of the settings

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qsf0sjb713cfaymaa1ih.png)

Once you have filled out the form click "Add Statement" and "Generate Policy" and copy the Policy JSON Document code that pops up and paste it into the field under Edit Bucket Policy and click "Save Changes" here's a photo of mine for reference: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/er1je2hzcnxib1lqzvim.png)

Next, go back to your bucket and go to the permissions section where you can scroll down to CORS(Cross-origin resource sharing) and click the "edit" button. Here's mine for reference: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qzmx64xrcha6esxmfu1v.png)

You can add more methods if you need depending on your app and if you plan to deploy your application you will need to do something like this: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kt0063vbv9l7l82zf2db.png)

Now that all of this is saved and complete, click on your username in the top right corner and then click on "security credentials" and then "users" on the left side of the page and click "create user" in the top right corner

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cm0f95pdpjivab6c1pef.png)

* The name of the user does not matter or need to be connected to an existing user in your application so I would just name it something generic

Once you enter your user's name click "next" and fill out the form like so: 

**Permission options** - Attach policies directly 
**Permission Policies** - AmazonS3FullAccess

Click "Create User" to save your new user and then click on the name of your new user and then "Security Credentials" where you can scroll down to the Access Keys section and click "Create Access Key" 

Select "Application running outside AWS" and add a create description tag called something like "Connect AWS S3 to my_app_name". You will now be able to see your access keys, keep this window open and take a screen shot or download them to a CSV file because once you close this page you wont be able to access them again and you will need them to make connections to your app

In your terminal run: "EDITOR=vim bin/rails credentials:edit"
and edit/update "aws", "access_key_id", and "secret_access_key" with the information in the CSV file you just downloaded after we created the access keys above, once you are done close the file and it will automatically save and you can leave the terminal and you are good to go! 

**<u>Setting up your Frontend</u>**
All of my research led me to use Javascript' formData api as a way to allow the user to upload a photo on the frontend through a form which would then connect to the backend  to Active Storage. Here is my signup function and handleSubmit function which is attached to the onSubmit on my form

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bundljdg9q9gwn23l9e3.png)

* When working on this project and troubleshooting I made the mistake of adding a header to my POST method so make sure not to do so 

Instead of using "e.target.value" like you are most likely using in the rest of your form you will instead need to use "e.target.files" on the image (I am using React Bootstrap if you are confused as to why my form looks like like this) 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/210lmzyb1nfkt6eps3in.png)

If you are using validations in your project I found that the way params was sent back for the image using formData was creating some issues. To resolve this I had to create frontend validations for the image in the form as a workaround to prevent my app from breaking. I am sure there are alternative ways to do this, this is just the option that worked for me. In order to do this I created a new state object: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8vcogjfn46c67a3zaquz.png)

and then a conditional statement: "isImage ?" in my form like so: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uhz6hau4zuu0otfms2ud.png)

**<u>Conclusion</u>**
I hope this was helpful, of course aspects of this tutorial are specific to my project but I think they can easily be adjusted and manipulated to the specifics of yours. I tried to  get into the obstacles and troubleshooting I had to figure out along the way incase you find yourself in the same position!