Well, today is the day that I write my first tech/coding blogpost. What I have decided to walk through is something that I have seen and used guides of, but it is something that seems like a good first post for me to try and write. Simple enough to easily write, but also something that could be beneficial. So here it goes. Let's look at Paperclip and AWS for storing your photos.

This tutorial will be a walk through on using paperclip for attachments, storing the photos using Amazon's S3 Storage, testing your model with RSpec, integration testing with Capybara, and pushing to Heroku. This guide will make the assumption that you already have an Amazon S3 and Heroku account setup, but will still be fairly detailed for more of a Rails beginner.

(Note: I normally prefer to create files by hand instead of using rails generators. They create a lot of files that aren't always necessary and in my opinion clutter up the code base. This tutorial will follow that practice.)

Installation

So let's start from the beginning of the process. First you will need to install the gems in your Gemfile. This tutorial will be using the aws-sdk version 1, along with paperclip version 4.3 or higher.

(Note: aws-sdk currently has version 2 available, but paperclip will not support until version 5.0, which is currently in beta. Later one I will write a post about upgrading to aws-sdk version 2 and paperclip 5.0.)

gem 'paperclip', '~> 4.3'
gem 'aws-sdk', '<2.0'

Now run your bundle install command and you are set to keep moving.

Models

Once you get the gems installed, go ahead and get ready to setup your model. In this tutorial, we will assume you are making a blog for all of your photos you love to take. Start by running rails g migration create_photos and open up your migration in your text editor. Add in your columns you would like, but for this tutorial we will just add the timestamps and the photo.

Class CreatePhotos < ActiveRecord::Migration
  def change
    create_table :photos do |t|
      t.attachment :picture
      t.timestamps
    end
  end
end

Go ahead and run bundle exec rake db:migrate to migrate your database. A friendly reminder, make sure after you run your migration, you run bundle exec rake db:test:prepare so that your test database receives the new migration.

Testing Your Photo Model

Now this tutorial assumes that we all love testing (which is something you should love). Which also means that this tutorial assumes that you already have the rspec-rails gem installed.

Luckily, Thoughtbot is a great group of people who love to make testing their gems easy. I personally use thoughtbots shoulda-matchers gem for a lot of my testing. So lets set that up.

group :test do
  gem 'shoulda-matchers'
end

Run your bundle install command. Shoulda Matchers also require some set up in your rails_helper.rb. You will need to add the following configuration down at the bottom.

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

Now you are ready to begin testing. Go into your spec folder, and create the file models/photo_spec.rb.

(Note: I like to add directories into my spec folder for models, features and whatever else you will be testing, but you are free to organize your specs however you like. The important thing is that you are writing them :D)

require 'rails_helper'

describe Photo do
  it {should have_attached_file :picture}
  it {should validate_attachment_presence :picture}
  it {should validate_attachment_content_type(:picture).
    allowing('image/png', 'image/jpeg', 'image/jpg').
    rejecting('text/plain', 'text/pdf', 'text/xml')
  }
end

Run bundle exec rspec in your terminal and see the nightmare of all those red dots. But don't worry, testing is worth it and we can make them all green by going into our model. Go ahead and create a photo.rb file in your app/models directory and make these tests pass. We can even look ahead and add a few styles to render different sized images in our views.

class Photo < ActiveRecord::Base
  validates_presence_of :picture

  has_attached_file :picture,
    styles: {
      large: '500x500>',
      medium: '300x300>',
      small: '100x100>'
    }

  validates_attachment_content_type :picture, content_type: /\Aimage\/.*\Z/
end

Back to your terminal, run bundle exec rspec and you should see three little green dots. You can breathe easy now. If yo don't see green dots, and you are getting undefined method errors, try running that bundle exec rake db:test:prepare in case you forgot about your test database!

Development Environment Set Up

Now before we go ahead and add the form to the views and create our photos, we need to set up our environments. There are multiple ways that you can set up the environments as far as where you store your access keys and what not, but I will go the route that I have found easiest. Lets starts with our development environment.

Find you way to your development config file located at config/environments/development.rb from the root of your project. Go ahead and add the following configurations:

Rails.application.configure do

  ...

  config.paperclip_defaults = {
    :storage => :s3,
    :s3_credentials => {
      :bucket => 'YOUR_S3_BUCKET_NAME',
      :s3_credentials => "#{Rails.root}/config/aws.yml"
    },
    url: ':s3_domain_url',
    path: '/:class/:attachment/:id_partition/:style/:filename'
  }

end

Make sure you enter in your bucket name in place for the credentials. Now, go ahead and in your config folder create a file called aws.yml to keep your keys in. Also, make sure you add your aws.yml file to your .gitignore so that you don't push your keys publicly. Add the following:

development:
  access_key_id: YOUR_S3_ACCESS_KEY
  secret_access_key: YOUR_S3_SECRET_ACCESS_KEY

After this you should be able to restart your server and be good to go. The next step? Get a form up in those views!

Views, Routes and Controllers

Now that we have the model set (and tested!) as well as the development environment to store your photos set, we can go ahead and work on getting the user side functional. Go into your config/routes.rb and add the following:

Rails.application.routes.draw do
  root to: 'photos#index'
  resources :photos, only: [:index, :create]
end

For this tutorial, we will only need those two routes. We will also make our app have the root_url set to the photos index action. Now go ahead and create your controller in app/controllers/photos_controller.rb. In there we can add the following:

class PhotosController < ApplicationController
  def index
    @photos = Photo.all
    @photo = Photo.new
  end

  def create
    photo = Photo.new(photo_params)
    if photo.save
      flash[:notice] = 'Your photo has been saved!'
      redirect_to root_url
    end
  end

private
  def photo_params
    params.require(:photo).permit(:picture)
  end
end

The index action will let us render all the photos that we have on the page as well as have a new photo to use in our form_for. We also have our photo_params method down at the bottom in our private methods for our Rails 4 strong params that will process the allowed input values, which is then used in our create action to create our photo and redirect back to our index path. We have the back end set-up, now lets move onto our views.

In this tutorial, we will be spending all of our time on our photos index view, so we can create that view at app/views/photos/index.html.haml.

(Note: This tutorial uses the HAML and Bootstrap-Sass gems.)

We can go ahead and add the following into our index view:

.container
  .flash-message
  - flash.each do |key, value|
    %div{:class => "alert alert-#{key}"}
    = value
    %button.close{"data-dismiss" => "alert"}
      %i.fa.fa-times{"aria-hidden" => "true"}

  %h1 My Photo Album

  - @photos.each do |photo|
    = image_tag(photo.picture.url(:large)

  = form_for @photo, multipart: true do |f|
    = f.label :picture
    = f.file_field :picture
    = f.submit 'Create Photo'

This view is pretty bare bones simple. We cycle through all of our photos which is collected in our controller in the @photos global variable, and then we have a form_for for our new photo designated with the @photo global variable from our controller. We also have a multipart: true argument in our form which will allow us to add an attachment to our model.

As of now, we should have a functioning form that will then save our photo into the AWS bucket that is stated in our development environment. Now there is only one more step to make sure we do this the good developer way.

Integration Testing

Why are we writing a spec to make sure our photo loads? Because specs make the next person who uses your codebase happier, and when you break something, you can run your suite and see how you broke it. Now in theory, you would want to write your specs before you write the logic/functionality so that you have something failing, then you make it work. In this tutorial we are writing the spec after, but it is always a good idea to comment something out to make the spec fail to make sure it is testing what you want it to be testing.

For integration testing, we will need to add a new gem to our test group in our Gemfile that we used earlier for the shoulda-matchers gem. Go into your Gemfile and add gem 'capybara' into that block. Now we need to create our spec file at spec/features/photo_pages_spec.rb.

Before we write this spec, we need a way for the test to add an actual attachment to make sure it is saved. The way I have gone about this is putting an image into the spec folder like this: spec/images/my_image.jpg. Now we will use that image for the testing purposes.

(Note: When you run your test suite, the test will add the image to your project in the follow directory: public/system/photos. I added /public/system/photos/* to my .gitignore so that I did not commit the photo being added after I run the test suite.)

After adding an image to use into our project, our spec should look something like this:

require 'rails_helper'

describe 'add photo' do
  it 'will allow you to add a photo' do
    visit root_url
    attach_file 'photo_picture', Rails.root.join('spec', 'images', 'my_image.jpg')
    click_on 'Create Photo'
    expect(page).to have_content 'Your photo has been saved!'
  end
end

We should be able to run our test suite and see all things passing. If you would like, you could have an else condition in your create action for a failed save of a photo and write another integration spec for that.

Heroku and Production Environment

We have almost made it. We have everything working in development, now all that is left is getting your production environment set. First I would recommend placing your variables into a .env file in the root of your project. Also make sure you add your .env into your .gitignore so that you do not publish your keys anywhere that you store your code! Your .env file should look something like this:

AWS_ACCESS_KEY_ID=YOUR_KEY
AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
S3_BUCKET_NAME=YOUR_BUCKET_NAME

You will want to put in your keys and bucket names in the appropriate places, and then head to your config/environments/production.rb and add the following:

Rails.application.configure do

  ...

  config.paperclip_defaults = {
    :storage => :s3,
    :s3_credentials => {
      :bucket => ENV['S3_BUCKET_NAME'],
      :access_key_id => ENV['AWS_ACCESS_KEY_ID'],
      :secret_access_key => ENV['AWS_SECRET_ACCESS_KEY']
    },
    url: ':s3_domain_url',
    path: '/:class/:attachment/:id_partition/:style/:filename'
  }
end

Now your production environment should be all set. The last thing you need to do is set those variables in your Heroku config. Luckily Heroku is awesome and makes this fairly simple.

To see your config variables for your Heroku app, just run heroku config in your terminal. All you need to do is then in your terminal run the following commands:

heroku config:set S3_BUCKET_NAME=YOUR_BUCKET_NAME

heroku config:set AWS_ACCESS_KEY_ID=YOUR_KEY

heroku config:set AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY

You can check to make sure those config variables are set correctly and matching your production.rb environment variables by running heroku config. This will print out all of your variables you have set!

And that is it! If all goes well you should have easy storage of your images using Paperclip and Amazon's S3 Storage! And there we have it, my first tech/coding blogpost!