Rails: Coding a Photo Gallery App

Haseeb Chaudhary
5 min readMar 30, 2020

--

Photo by Soragrit Wongsa on Unsplash

This is the first part of a write up detailing how to code out a gallery application for all your favourite pictures. Following this you will have an application deployed in the cloud serving content with ability to add pics and delete. The next part will involve adding authentication and not using scaffolding.

Step 1 Base Build setup

Ensure you have a Ruby ruby 2.6.5 and Rails Rails 5.2.4.2 installed. they’re very simple to install so I’d say ensure you have this setup first.

Install Postgres DB

brew install postgresql
brew services start postgresql

Make sure Postgres DB is running

psql -d postgres

Build a new rails app

cd ~/code/$YOUR_GITHUB_USERNAME
rails new fotogallery --webpack --database=postgresql
cd blog

Create the database on PostgreSQL

git add .
git commit -m "Initial commit"
hub create
git push origin master

Modify your Gemfile:

# Gemfile
gem 'pg' # No more `gem 'sqlite'` thanks to `--database=postgresql`

Get yarn installed, then bootstrap, jquery and poppers.js

Yarn is a JavaScript package manager, and a faster alternative to NPM. It works almost exactly like NPM, but has one advantage — it creates a yarn.lock file which you can check into source control, and make sure you’re running the same version of the packages everywhere.

Rails 5.1 will support Yarn out of the box, but in the meanwhile, I wanted to try it out on a Rails 5.0 project. I found the process surprisingly easy and painless. Let’s see how you can add Bootstrap to an existing Rails project through Yarn.

Since rails uses the asset pipeline to minify and compress stylesheets, javascripts, and images, using a sass version of bootstrap is the preferred way of including bootstrap over simply including bootstrap in your application layout view. Rails will compile your bootstrap and assets on the fly and will allow you a lot more flexibility in the design of your apps.

Query is a lightweight, “write less, do more”, JavaScript library. The purpose of jQuery is to make it much easier to use JavaScript on your website. jQuery takes a lot of common tasks that require many lines of JavaScript code to accomplish, and wraps them into methods that you can call with a single line of code.

A library used to manage poppers in web applications. Popper. js is a positioning engine, its purpose is to calculate the position of an element to make it possible to position it near a given reference element. Popper. js has zero dependencies

yarn add bootstrap
yarn add jquery popper.js

Install Simple form: Simple Form was designed to be customized as you need to. Basically it’s a stack of components that are invoked to create a complete html input for you, which by default contains label, hints, errors and the input itself.

# Gemfile
gem 'simple_form'

The build:

bundle install
rails generate simple_form:install --bootstrap
rm app/assets/stylesheets/application.css
touch app/assets/stylesheets/application.scss
/* app/assets/stylesheets/application.scss */
@import "bootstrap/scss/bootstrap";

Scaffold a Post model

Open Gemfile, and comment out the jbuilder gem.

bundle install
rails generate scaffold article title body:text
rails db:migrate
# config/routes.rb
Rails.application.routes.draw do
root to: 'articles#index'
resources :articles, except: :index
end
git add .
git commit -m "Add Article scaffold"
rails s

Step 2 Decide upon a Hosting Environment for Production

Get Heroku installed, this is my simple go to hosting environment.

Install on OS X

# OS X
brew install heroku/brew/heroku

Login

heroku login

Create an Heroku app

heroku create $YOUR_APP_NAME --region eu

Let’s deploy!

Push your code to Heroku

git push heroku master

Run a command on Heroku

heroku run rails db:migrate

So we’re setting up an external service to use as a DB link for our images, super important when using a Production service such as Heroku. I’ve chosen Cloudordinary

Get a Cloudordinay Account Setup

Where do we put our secret keys?

We don’t want to share those secret keys on Github, we can use the dotenv gem for security.

# Gemfile
gem 'dotenv-rails', groups: [:development, :test]
bundle installtouch .env
echo '.env*' >> .gitignore
git status # .env should not be there, we don't want to push it to Github.
git add .
git commit -m "Add dotenv - Protect my secret data in .env file"

Step 3 How to Store and Serve Images

Cloudinary & Environment

# Gemfile
gem 'cloudinary', '~> 1.12.0'
bundle install# .env
CLOUDINARY_URL=cloudinary://298522699261255:Qa1ZfO4syfbOC-***********************8

Upload two pics

curl https://c1.staticflickr.com/3/2889/33773377295_3614b9db80_b.jpg > san_francisco.jpg
curl https://pbs.twimg.com/media/DC1Xyz3XoAAv7zB.jpg > boris_retreat_2017.jpg
# rails c
Cloudinary::Uploader.upload("san_francisco.jpg")
Cloudinary::Uploader.upload("boris_retreat_2017.jpg")
rm san_francisco.jpg boris_retreat_2017.jpg

And then go to cloudinary.com/console/media_library

<!-- app/views/articles/index.html.erb -->
<%= cl_image_tag("THE_IMAGE_ID_FROM_LIBRARY",
width: 400, height: 300, crop: :fill) %>

<!-- for face detection -->
<%= cl_image_tag("IMAGE_WITH_FACE_ID",
width: 150, height: 150, crop: :thumb, gravity: :face) %>

Crop modes:
scale, fit, fill, limit, pad, crop.

You even have thumb with Face detection

Transformation reference

This exhaustive documentation lists all you can do.

You can change the quality, put some effects (like Instagram), add
an overlay (watermark)

You can upload also other file formats (PDFs, etc.)

Setup

rails active_storage:install
rails db:migrate

This creates two tables in the database to handle the associations between our pictures uploaded on Cloudinary and any Model in our app.

Config

# config/storage.yml
cloudinary:
service: Cloudinary

Replace :local by :cloudinary in the config:

# config/environments/development.rb
config.active_storage.service = :cloudinary

Step 4 Code the Model, View, Controller

As its a photo gallery we’re using Multiple images

If you want to have many attached images, you can define a different relationship in your model:

class Article < ApplicationRecord
has_many_attached :photos
end

View & Controller

<!-- app/views/articles/_form.html.erb -->
<%= simple_form_for(article) do |f| %>
<!-- [...] -->
<%= f.input :photos, as: :file, input_html: { multiple: true } %>
<!-- [...] -->
<% end %>
# app/controllers/articles_controller.rb
def article_params
params.require(:article).permit(:title, :body, photos: [])
end
<!-- app/views/articles/show.html.erb -->
<% @article.photos.each do |photo| %>
<%= cl_image_tag photo.key, height: 300, width: 400, crop: :fill %>
<% end %>

Helpful Active Storage methods

@article.photo.attached? #=> true/false
@article.photo.purge #=> Destroy the photo

Behind the scenes

  1. active_storage_blobs -> Stores attachment metadata (filename, content-type, etc.)
  2. active_storage_attachments -> Join table between attachments and the relevant models

Step 5 Adding a Delete button to the pictures

ROUTES

Rails.application.routes.draw do   resources :articles do    resources :comments   end   get 'articles/index'   resources :articles   root 'articles#index'   resources :articles do     resources :photos do       match '/remove', to: 'articles#remove', via: 'delete'     end   endend

Articles CONTROLLER

def remove
@article = Article.find(params[:article_id])
@photo = @article.photos.find(params[:photo_id])
@photo.purge
redirect_to article_path(@article), notice: "Upload was successfully removed."
end

VIEW

<% @article.photos.each do |photo| %>
<%= link_to "X", article_photo_remove_path(@article, photo),
:method => :delete,
:data => { :confirm => t('.confirm', :default => 'Are you sure you want to delete this upload?') },
:id =>'delete-faq', :class => 'delete' %></td>
<% end %>

Step 6 Managing the Repo

Ensure you setup a new repo on github and taking care not to “Initialize this repository with a README”. Then on the CLI:

git remote add origin git@github.com:haseebc/[NameOfApp].git
git push -u origin master

So this gives a simple but awesome photo gallery application.

Part 2 will include setting this up without scaffold and authentication.

--

--