Building a Rails app for images using Amazon S3 and Paperclip

In the steps outlined below, I walk through creating a CRUD app in Rails that saves images to, and serves images from, Amazon S3, using the paperclip gem. The images are displayed in a grid using Flexbox with some Ruby fun to change the grid layout each time the page is refreshed.

S3 image app

I experimented with Flexbox and Sass on the front end to learn more about these techniques and give this app a responsive design. I’m using my Instagram images as test images for this app. If I shrink my viewport below the breakpoint I’ve defined (600px wide), the images revert to displaying in a column, with the sidebar underneath, as shown in the following screenshot:

Small index screen for Rails app with Amazon S3
Small index screen for Rails app with Amazon S3

This has turned into quite a long article, so here’s a Contents section:

1. Building the basic Rails app with Paperclip and Amazon S3
2. Adding a recursive helper for the image display grid
3. Building the Front-End
4. Further resources

I was motivated to create this test app since I want to implement these technologies in my main project, UpLearn, as a faster and more robust way of storing and serving the screenshots.

S3 from Amazon Web Services

The first step, before creating the app, is to set up an S3 account and have a bucket ready in which to store your images.

The full AWS suite, of which S3 is but a part, is extensive and rather complex, so can be somewhat overwhelming at first. The documentation from Amazon is pretty good, and I’d recommend starting here: Getting Started with Amazon S3

You’ll need to register for an Amazon account if you don’t already have one, and then set up an S3 account. Once you’ve done this, create a bucket within S3 and make a note of your Access Key and your Secret Key which you’ll need for your Rails app.

Part 1: Creating the basic Rails app

Step 1: Setting up the Rails app

Open up Terminal (or equivalent command line tool) and inside your development folder create a new rails app with the following command:

Type $ cd image_test_app to move into the image_test_app folder (for me dev/web_apps/image_test_app), and then create the database as follows:

You’ll need “imagemagick” installed for paperclip to work. If you’re using homebrew on a Mac then this may already be installed and if not, this command will do that for you:

There’s more information in the paperclip readme file.

Now, time to move to our code editor (I’m using Sublime Text 2, and think it’s great). Let’s do this in baby steps, testing our app at each stage.

Step 2: Modifying the Gemfile

First, in the Gemfile, add the following:

Here, I’m installing three gems that I’m using for this app. The first two gems are at the heart of this application.

I’ve installed Amazon’s AWS gem, ‘aws-sdk’, but I’ve had to specify version 1.5.7, as it appears that the latest version 2 gem is NOT compatible with Paperclip yet. I tried, went round and round in circles but could not get it working without forcing rails to revert to this older version of the Amazon AWS gem. This open ticket on Paperclip’s GitHub page would appear to confirm this as an issue. Hopefully this will be resolved soon.

The second gem is the paperclip gem, which is used for handling files through forms in our Rails app; in our case, uploading images to S3 through our Rails app.

The final gem, dotenv-rails, is used to keep credentials (e.g. my Amazon Access key and Secret key) secure, as we’ll see below.

Don’t forget to return to the command line and run bundle install again to install these gems into your Rails app:

When building Rails apps, I start by setting up the basic Controller and first View to get something up and running quickly. After that I hook up the Model and start adding the complexity.

Step 3: Setting up the Controller

I like to start the server running at this stage, then check each step to see what my display is showing me in the browser window. So, in the command line:

This gives me the following default welcome screen for Rails:

Default Rails welcome screen
Default Rails welcome screen

Next job is to define the routes, so open up the config/routes.rb file and add the following code (2 yellow lines):

This sets up the necessary routes for our app. If you now refresh the browser where you’re running the app, you’ll see a different message:

Missing controller error message
Missing controller error message

Aha! This is an error message from Rails, informing us that we don’t have a controller set up yet. The controller sits in between the model and the view layers, sending commands to the model to update it, and sending commands to the view for displaying data.

So let’s set that up by creating a file called posts_controller.rb inside the app/controllers folder. Add the following code to get started:

Refreshing the browser, you’ll now see the following error message, telling us that we’re missing the template (the index view):

Missing Template error message
Missing Template error message

Let’s remedy this!

Step 4: Creating the first View

Start by creating a folder within app/views called posts. The name is very important as Rails favours convention over configuration, and will throw an error if you try to use a different folder name like “bens_views_folder” say. It must be the plural of the model name, i.e. Post model & posts controller & posts view folder.

So, inside app/views/posts folder, create a file called index.html.erb (an embedded Ruby file) and add the following code:

Refresh and voila! You should now see a static webpage, the first piece of your app!

Screenshot of static index page
Screenshot of static index page

Step 5: Linking the Post model to our app

There are a few steps here: creating the posts model, connecting our app to Amazon S3 and then creating a form so that images can be uploaded.

Creating the Post model: Let’s go back to the command line and go ahead and create our Post model with title and image attributes:

In the command line, you’ll then need to run a rake migration command to run the Posts migration and actually create the table, as follows:

In the code editor, you can see there’s now a file for the post model, at image_test_app/app/models/post.rb. Let’s add the following code, in yellow:

Here, we’ve created a standard model in Rails and added a Paperclip attribute for the file attachments (images) and a validation to ensure that only images are uploaded (line 10). The has_attached_file creates a small image (size 64px by 64px), a medium image (or med, size 200px by 200px) and a large image (size 400px by 400px) when creating entries in the Post model.

Linking our app to Amazon S3: This is surprisingly easy, since Paperclip handles a lot of the heavy lifting for us. We set the default Paperclip storage to “S3”, setup our credentials and bingo, the app will save to S3! So, inside of the config/environments/development.rb file, add the following code, just above the final end, as follows:

So, we set the default storage to be S3, and then we specify our S3 credentials by telling Paperclip to look in the .env file for our credentials (we’re about to create this file).

So, create a new file .env in the root directory (image_test_app/.env), and add your S3 credentials, like this:

When using the dotenv gem and the ENV notation above, the final piece is to add this new .env file to the .gitignore file, to keep our keys safe and not visible publicly on GitHub. So, in the .gitignore file, add the following two lines of code to the bottom of the file:

Creating the upload form: Back to views, let’s create the upload form in a new file called new.html.erb in the app/views/posts folder, and add the following code to create a form:

So, this is our basic input form, with an input field for the image title and a button to add a file for upload. The form_for helper automatically sets the form’s encoding to “multipart/form-data” to allow file uploading to happen through the form.

We need to return to our controller now, and add some logic before we can use this view. We’ll also update the index action and add the create action whilst we’re at it, so that our uploads can be persisted to the database/Amazon S3.

So, in app/controllers/posts_controller.rb, add the following code in yellow:

Now, we can upload images and they’ll be persisted to our PostgreSQL database, with the image residing in our Amazon S3 bucket. Pretty cool!

Now we need to display them.

Step 6: Serving the images in our Index view

Let’s do a very simple first step, simply adding some code to our app/views/posts/index.html.erb file so display all of the images we’ve uploaded, using the @posts instance variable we instantiated in our controller earlier. Let’s also add a link to the upload form (line 3) so we can access that and upload images.

The index file should look like this:

Go ahead and test it out by uploading an image through the form. Hopefully you’ll get a screen looking like this:

Screenshot of first image upload
Screenshot of first image upload

There we go! Cool right? We’ve created a bare bones image app linked to an S3 bucket.

I also added the Read, Update and Delete parts of the CRUD functionality, but won’t go through that explicitly here. Check out the full posts_controller code on GitHub and the views folder for further details.

Part 2: Taking the app to the next level

If you’re following this tutorial and have got this far then great job! Get up and stretch, take 5, grab a caffeinated drink and let’s keep going.

The app is functional but very basic at present. I want to add some front-end styling and also display the images in a grid on the index page using Flex boxes. There’s work to do on the back-end first though, before we get deep into flex and sass.

I want to randomly shuffle the image order, and randomly choose how many images to show on each row, between 1 and 4 when I reload the index page, so it’ll look different each time. For this, I created a recursive method to split my images array (i.e. Post.all) into smaller row arrays, e.g. something like this:

[ [image 1, image 2], [image 3, image 4, image 5,image 6], [image 7, image 8, image 9] ...]

To do this, I created a helper file, app/helpers/row_helper.rb, to keep the logic out of the controller file, as follows:

Essentially, what I’m doing here is taking the posts array (i.e. the full array of images in Post.all) and breaking that into smaller arrays that I push into a new array at instance variable @array.

I pass in the posts_array as an argument to the row_split method on line 2. The notation rand(2..4) selects randomly from 2, 3 or 4. Then on line 9 I create an array called new_row that consists of the first r items of the posts array, where r is the random number Ruby generated for us. I push this array into the main array, @array.

On line 11, the leftover items in the posts array get put into an array called remaining, which I then pass into the row_split method as an argument again (the recursive piece). It’ll keep going through this process until there are less than 5 items remaining in the argument array, at which time it just pushes these into the @array and is done.

I need to include this row helper in my posts controller with the following code:

which means I have access to the row_split method inside my posts controller.

I can write the index action in the posts controller in a very succinct fashion, as follows:

So when the index action is called, an empty @array, is created. Then a call to the Post model is made and all the posts are returned, shuffled and stored in a variable called “posts”. On the final line of the index action, row_split is called and we pass in an argument posts, i.e. the variable we just created that points to the array of all our posts.

The method row_split then splits the posts array into smaller arrays and returns them inside of the @array instance variable, as discussed above. This is then available to the index view.

Just before we take a look at that, there’s one piece of housekeeping to do whilst we’re still in the posts controller. We can dry up our code somewhat, by creating a method for all those times we make a call to the database to find the specific post referenced in our params. There are two parts to this: firstly, creating the private method, then second, using the before_action filter to call the method where it’s needed.

The code to achieve this is as follows (highlighted in yellow in the posts controller):

Ok, so that’s the posts controller wrapped up. Onto the views.

As I mentioned earlier, we now have an @array instance variable that is an array containing sub-arrays with each containing 1, 2, 3 or 4 image posts, e.g. something like this:

[ [image 1, image 2], [image 3, image 4, image 5], [image 6], ...]

So I need to loop through the outer @array first, which is line 1 in the code snippet below. Then for each sub-array (which I’ve called “row”, as all the images within will be displayed on the same row), I loop through each item within that array (line 2) and use the image_tag helper to display the image (line 3), as follows:

The final code (here on GitHub) has some “div” tags for the presentation of the images, which I discuss below in the front-end section.

At this stage, we now have a working app, with all of the back-end coding done. It should be saving images to Amazon S3 (you should see them in your S3 bucket if you log in to S3) and serving them from S3 too.


Part 3: Creating a front-end for the app using Flex box and Sass

At this stage, in the absence of any front-end styling, the app looks something like this:

S3 app before styling
S3 app before any styling

It’s fully functioning on the back-end, but all that work we did to create a recursive helper to randomly shuffle images into rows is for naught, unless we fix the front-end. I won’t go through the front-end code build line-by-line, but rather by looking at the overall concepts involved (the full front-end code is available here on GitHub).

High-level stylesheet structure

I split my stylesheets up, along the lines of the SMACSS architecture, namely having a base file for any CSS resets or global properties, a layout file for, well, layout, then a modules file to handle the presentation of things in the page and, finally, a states file for styling items that change (e.g. link hover overs etc.).

In this MVP scenario, I’m really only concerned with the layout of my page (since my aim is to test out flex box), so there is minimal styling in the modules file and no states file in this case.

The Base file

I’ve placed my minimal CSS reset in this file, just to remove some of the default margins and padding around my elements. This allows me to then control that myself. In addition, I’ve specified box-sizing to be border-box (meaning the border width and padding are inside the box size, not in addition to it. Read more here).

Lastly, you’ll notice I also set the font-family globally for my app in line 18. I’ve set it to “Lato” which is a free Google font that is quite popular. To get this to work you need to add one more line to the head section of the app/views/layouts/application.html.erb file, as follows:

Layout with Flex box and Sass

There are three levels of items in my index page:

  • Top level: header, a 2 column layout (article and aside), and footer
  • 2nd level: within the article section, split into rows
  • 3rd level: within each row, split into image elements

At the top level, the article and aside are flex items. Then within the article section, the rows are defined as a column of flex items and then within each row, the image items are flex items. The stripped down HTML/CSS to do this is as follows:


And the corresponding CSS:

I’ve implemented a @media breakpoint at 600px, to switch to column view for the flex boxes below that size. The code to do this is:

More on choosing breakpoints from Google here.

As I’m building my stylesheets as SCSS files, then I can specify my colors as variables, use @mixins and nest styles within each other. For example, I’ve created a mixin for border-radius as follows, with a variable for the radius:

This allows me to easily change the look of different parts of the site by specifying the border-radius as an “argument” when using the mixin, for example on the img element:

Notice, the border color is also defined by a variable, $secondaryColor.

I haven’t gone through every detail of the front-end build here, so I’d encourage you to dive into the code for the application layout view, the index view and the stylesheets.

Putting it all together, the final app looks like this:

Paperclip and S3 app
Paperclip and S3 app


Part 4: Resources

Paperclip readme

AWS Getting Started Guide

Paperclip with Amazon S3 documentation

This is a comprehensive guide to flexbox from Codrops.

Guide to Flex boxes from the Mozilla Developer Network

2 column layout from David Walsh blog

Good primer article on the difference between Sass and SCSS

Sass basics from the official documentation


Comments, questions, typos? Leave your thoughts below.

Leave a Reply

Your email address will not be published. Required fields are marked *