Build a photo sharing app with Ruby on Rails and Cloudinary
You will need Ruby and Rails installed on your machine.
Some say moments are best when captured so we can relive them and quite frankly, they kind of have a point. This is why applications like Instagram have a lot of users. In this article, we are going to look at how to build a simple photo sharing application with Pusher, Cloudinary and Ruby.
Prerequisites
For you to successfully follow through this article, you need to have the following set up:
- Ruby installed on your machine
- Rails installed on your machine
To confirm your installation, run the following :
ruby -v // minimum version 2.4
rails --version // minimum version 4.2
If you get version numbers as result then it means you’re good to go!
Getting started
Creating a new Ruby project
Since you already have Ruby installed on your machine, go ahead to create a new Rails application by running the command:
rails new pusher-photofeeds
This creates a sample rails project with the following structure:
You can take the sample application for a spin by running the command:
rails server --binding=127.0.0.1
Configuring the database
To store posts in a database, a database driver is needed. With Ruby on Rails, SQLite is used as the default database driver, and for simplicity, we are going to continue with that.
To create your database, run the command:
rake db:create
This creates the database for the application in the db/
directory of your project.
Creating the post model
To represent a sample post in our application, we need to create a model. Our simple post with have the following properties:
- Link
- Caption
Create the model by running the command:
rails generate model Post link:text caption:text
This creates a migration file for your new model that can be found in db/migrate/{timestamp}_create_posts
. The file will look like this:
# db/migrate/{timestamp}_create_posts
class CreatePosts < ActiveRecord::Migration[5.2]
def change
create_table :posts do |t|
t.text :link
t.text :caption
t.timestamps
end
end
end
Now, to store the data in the database, run the command:
rails db:migrate
When you run that, you get the following output:
➜ rails db:migrate
== 20180520070732 CreatePosts: migrating ======================================
-- create_table(:posts)
-> 0.0016s
== 20180520070732 CreatePosts: migrated (0.0022s) =============================
At this point, we have our posts table created with the link and caption as fields we wanted.
Accepting posts
Rails is an MVC Framework and we need to create a controller that handles our route. Let’s create a Photo
controller that perform the following actions:
- Displays the existing photos in the feed
- Stores a new photo
To do this, run the following command:
rails generate controller Photo index store
This creates the controller and the following files. Now, let’s edit the index
view created by this command. Update your photo/index.html.erb
file to look like this:
<!-- app/views/photo/index.html.erb -->
<h1 style="text-align: center">Realtime PhotoFeeds!</h1>
<div class="wrapper">
<form class="uploads" method="POST" action="/photo/store" enctype="multipart/form-data">
<%= token_tag %>
<div class="form-group">
<label for="caption">Caption</label>
<input type="text" name="caption">
</div>
<div style="margin-left: 5px;">
<input type="file" name="image">
</div>
<div>
<input type="submit" value="Upload">
</div>
</form>
<div class="images">
<div class="post">
<div>
<img class="post-image" src="https://pbs.twimg.com/media/DOXI0IEXkAAkokm.jpg">
</div>
<p class="post-caption">Living my best life</p>
</div>
</div>
</div>
The view’s styling is controlled by photo.scss
. Update the file to look like this:
// app/assets/stylesheets/photo.scss
.wrapper{
display: flex;
flex-direction: column;
align-items: center;
}
.uploads{
margin-top: 20px;
margin-bottom: 20px;
align-items: center;
}
.images{
.post{
width: 500px;
border-radius: 10px;
border: 1px solid rgb(214, 210, 210);
margin-bottom: 20px;
}
}
.post-image{
width: 100%;
}
.post-caption{
padding-left: 20px;
}
.uploads{
display: flex;
justify-content: space-between;
}
input[type="submit"]{
background-color: #333333;
border: none;
color: white;
padding: 10px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
The top bar allows the user to upload pictures to the feed and that is fixed to the top to make it easy to perform the action. The other part part is responsible for displaying the images on the screen to the user.
Now let’s take a look at what happens when a submission is made for a new post to be created. When the Post button is clicked, a POST request is made to the ‘photo/store’ route.
In the config/routes.rb
that houses the available routes for our application, we have the following:
/
- To display the existing photos and upload form/photo/store
- To create a new post
# config/routes.rb
Rails.application.routes.draw do
post 'photo/store'
root 'photo#index'
end
Let’s take a look at the controller logic to save the post uploaded by the user. Edit your app/controllers/photo_controller.rb
to look like this:
# app/controllers/photo_controller.rb
class PhotoController < ApplicationController
def store
render plain: params[:caption].inspect
end
def index
@posts = Post.all
end
end
The
index
method in the controller is responsible for fetching all the existing posts in the database.
At the moment, the controller only renders the caption on a new page. This is not what we want. We are going to further edit this controller to do the following:
- Upload the image to Cloudinary and save the post in the database
- Use Pusher to display posts in realtime
Uploading posts with Cloudinary
Cloudinary is your one stop shop for all your image and video management needs when building applications. With Cloudinary, you no longer have to worry about writing the core code behind image and video manipulations, uploading images. You can read more about all Cloudinary features here.
Cloudinary has a Ruby gem and that is what will be used to handle the file uploads.
Add the Cloudinary as a dependency in your Gemfile
:
# adding cloudinary to gemfile
gem 'cloudinary'
Install it by running the command:
bundle install
Configuring Cloudinary
To use the Cloudinary gem, you need to have a Cloudinary developer account to use it in your applications. Head over here to create one if you don’t have one already.
Cloudinary automatically checks for a cloudinary.yml
in the config
directory that contains the configuration file for your Cloudinary account. You can download your own customized file for your account here. Once it’s downloaded, place the file in the config
directory.
Using the module
We are going to use the module in the Photo controller. Update the app/controllers/photo_controller.rb
file:
# app/controllers/photo_controller.rb
class PhotoController < ApplicationController
def index
@posts = Post.all.order("created_at DESC")
end
def store
# upload image to cloudinary
@value = Cloudinary::Uploader.upload(params[:image])
# create a new post object and save to db
@post = Post.new({:link => @value['secure_url'], :caption => params[:caption]})
@post.save
# trigger an event with pusher
[...]
redirect_to('/')
end
end
Now, at this point, we’ve been able to upload a new image using Cloudinary and save the post in the database.
Adding realtime functionality with Pusher
Now that the post has been created, you need to let all the users online know that there’s a new post without needing to refresh their browsers. To do this, let’s use Pusher. Pusher allows you to seamlessly add realtime features to your applications without worrying about infrastructure.
To get started, create a developer account. Once that is done, create your application and obtain your application keys.
To use Pusher with your Rails application, you need to do a couple of things.
Install Pusher via Ruby gems
Add pusher to your Gemfile
# Gemfile
[...]
gem 'pusher'
[...]
And then install the new gem:
bundle install
To confirm your installation run:
bundle info pusher
You should get a result like this:
$ bundle info pusher
* pusher (1.3.1)
Summary: Pusher API client
Homepage: http://github.com/pusher/pusher-http-ruby
Path: /home/captog/.rvm/gems/ruby-2.4.1/gems/pusher-1.3.1
Import the Pusher package
Create a config/initializers/pusher.rb
file to look like this:
# config/initializers/pusher.rb
require 'pusher'
Pusher.app_id = 'PUSHER_APP_ID'
Pusher.key = 'PUSHER_APP_KEY'
Pusher.secret = 'PUSHER_APP_SECRET'
Pusher.cluster = 'PUSHER_APP_CLUSTER'
Pusher.logger = Rails.logger
Pusher.encrypted = true
The `PUSHER_APP_ID`, `PUSHER_APP_KEY`, `PUSHER_APP_SECRET` and `PUSHER_APP_CLUSTER` can all be obtained from the [dashboard](https://dashboard.pusher.com) of your application.
Triggering events from the controller
To trigger a new event, update the app/controlllers/photo_controller.rb
file:
# app/controllers/photo_controller.rb
class PhotoController < ApplicationController
def store
# upload image to cloudinary
@value = Cloudinary::Uploader.upload(params[:image])
# render plain: @value['secure_url']
# create a new post object and save to db
@post = Post.new({:link => @value['secure_url'], :caption => params[:caption]})
if @post.save
# broadcasting posts using pusher
Pusher.trigger('posts-channel','new-post', {
link: @post.link,
caption: @post.caption
})
end
redirect_to('/')
end
end
After the post is saved in the database, a new-post
event is triggered in the posts-channel
. The Pusher dashboard allows you to debug your application by seeing when events have been triggered.
Displaying posts on the client
To integrate Pusher to the frontend of your application, you’ll need to do the following:
First, include the Pusher script in the header of the application in the app/views/layouts/application.html.erb
# app/views/layouts/application.html.erb
[...]
<head>
[...]
<script src="https://js.pusher.com/4.1/pusher.min.js"></script>
[...]
</head>
[...]
Next thing to do is to create a Pusher client in the application header:
# app/views/layouts/application.html.erb
[...]
<head>
[...]
// add pusher script
<script>
var pusher = new Pusher('PUSHER_APP_KEY', {
cluster: 'eu',
encrypted: true
});
</script>
[...]
</head>
[...]
Finally, subscribe to the posts-channel
and listen for new-post
events. You can do this in the photo#index
view of the application. Update the app/views/photo/index.html.erb
to look like this:
# app/views/photo/index.html.erb
<h1 style="text-align: center">Realtime PhotoFeeds!</h1>
<div class="wrapper">
[...]
<div class="images" id="post_section">
<% @posts.each do |post| %>
<div class="post">
<div>
<img class="post-image" src="<%= post.link %>">
</div>
<p class="post-caption"><%= post.caption %></p>
</div>
<% end %>
</div>
</div>
<script>
var channel = pusher.subscribe('posts-channel');
channel.bind('new-post', function(data) {
let post_link = data.link;
let post_caption = data.caption;
let div = document.getElementById('post_section');
let new_content = document.createElement("div");
new_content.innerHTML = `
<div class="post">
<div>
<img class="post-image" src="${post_link}">
</div>
<p class="post-caption"> ${post_caption}</p>
</div>`;
div.insertBefore(new_content, div.firstChild);
});
</script>
Here, on the client, we subscribe to the posts-channel
and listen for a new-post
event. When an event is broadcasted we then update the UI to show the new post.
To see the application at work, start the rails server using the command:
rails server --binding=127.0.0.1
This starts up a rails server with the port shown to you - in this case 127.0.0.1:3000
as specified with the --binding
flag. When you visit the application, this is what you see:
Conclusion
In this article, we’ve seen how to use Ruby and Pusher to make a simple realtime photosharing application. Feel free to apply these concepts and explore the Pusher documentation for more exciting features.Lastly, the complete source code of this demo application is on GitHub.
27 May 2018
by Christian Nwamba