How To Upload and Display Images Trough IBM Cloud with Rails 6

Written by redacuve | Published 2020/07/22
Tech Story Tags: ibm-cloud | ruby-on-rails | rails | cloud | s3 | aws-s3 | ibm-cloud-storage | ibm

TLDR This is a tutorial about how we can implement IBM Cloud Storage in our Ruby on Ruby on Rails 6 projects. In this tutorial we are going to build a simple app that can upload a kitty photo and vote for that photo. This tutorial only includes the part of creating a new Ruby app, configuring it to save our photo on the cloud, and showing it on an image tag. For a free account you don’t need a credit card. IBM Cloud is compatible with protocol S3 like Amazon Web Services.via the TL;DR App

This is a tutorial about how we can implement IBM Cloud Storage in our Ruby on Rails 6 projects. We can upload images and manage these assets with Rails. In this tutorial we are going to build a simple app. We can upload a kitty photo and vote for that photo (this tutorial only includes the part of creating a new Rails app, configuring it to save our photo on the cloud, and showing it on an image tag; the design and the part for the photo will be included in the app but is not shown in this tutorial).
You can find the app on this repository https://github.com/redacuve/my-cat-photos, and the live version here: https://my-cat-photos.herokuapp.com/.
Before we start, here is a little bit of background. I needed to use file storage for my project on Rails 6. At first Amazon S3 seemed fair to me. There is a lot of documentation about how to use AWS, but there was a problem. For a free account you need a valid credit card. I was looking for other options but they all needed credit cards (Azure, Google Cloud). Then I found IBM Cloud. For a free account you don’t need a credit card.
IBM also upgraded their cloud. Now it is compatible with protocol S3 (Simple Storage Service) like Amazon Web Services. Thanks to this protocol you can save objects (they can be files, or in this case images) and every object is saved in a bucket (a container of objects). This is great because the previous cloud of IBM was tricky to use.

Creating an account and making our Bucket

The first part is, of course, going to the registration URL and registering. We can go to https://cloud.ibm.com/registration and create our account.
  1. We need to provide our email and password. Then click on Next.
  2. We need to look for our confirmation email. IBM will send us a code and we need to insert that code in the given field and click on Next.
  3. We need to add our personal information and click on Create the account.
When we create our account, we can read some documentation and we can click on Accept. Then we are going to see the dashboard.
I’m not going to explain all of the services that IBM includes, but feel free to check them out. There are some very good services. We can create our resource clicking on “Create resource”.
We are going to search “Object Storage” and click on it. There we can see the features of our plan (lite and free). We can add the name of the resource (I will call it my-cat-photos; you can call it whatever you want). And then select “Create”.
Now we are on the Cloud Object Storage section. We need to select “Create Bucket”.
Here we can customize our bucket clicking on Custom Bucket. But a standard predefined bucket is fine, so we can select that one.
it’s important to have a unique bucket name. We can see the location of the bucket (and we can change it) but the default location is OK. We can also see the service credentials, where we can add another one later. We are ready to click on “View Bucket Configuration”.
IMPORTANT.
Here there is information that we will need later. We need to look at it and save it for later. Specifically, we need:
  • The region
  • Bucket name
  • Endpoint.
We can find the first two on bucket details and the last one scrolling a little bit to endpoints and copy the public one. For me, these are the data:
Region: us-south
Bucket name: my-cat-photos-cos-standard-cpm
Endpoint: s3.us-south.cloud-object-storage.appdomain.cloud
Now we can obtain the credentials. For that we need to click on the “Service credentials” menu on the left.
Here we are going to click on the “New Credential” button.
We can name the credential as we want. But we need to click on advanced options. Here we need to toggle on the switch to include the HMAC credential to our credential and click on add.
Then our credential will be created. We can expand the credential and copy the values: access_key_id and secret_access_key
At this point, we need to have the following:
  • Region
  • Bucket Name
  • Endpoint
  • access_key_id
  • secret_access_key.
With this information we can go on to the second part of this tutorial.

Creating our app with Rails 6

This is simple. We are going to create the app. It’s straightforward. We need a model for the photo, a controller with 3 methods (index, new, and create), and two views. One for creating the photo and another for displaying all photos (index and new), and of course our model. In this model we need a field of the string type, because here is where we are going to save our image name. We also need to update the routes.
So, let’s start. We can open a terminal in Linux (we need rails 6, bundler, and yarn). And hit this command to create our app:
$ rails new my-cat-photos
Inside the new folder that Rails creates for us, we can create the photos controller.
$ rails generate controller photos
Then, we can create the model.
$ rails generate model Photo title:text description:text votes:float image:string
We can start with the model ‘app/models/photo.rb’ working only with the validation of the image. In this case we need to have the title, the description, and the image to be present so we can save the record.
class Photo < ApplicationRecord
  validates_presence_of :title, :description, :image
end
Then move to the controller and the methods ‘app/controllers/photos_controller.rb’, it’s simple:
class PhotosController < ApplicationController
  def index
    @photos = Photo.all
  end

  def new
    @photo = Photo.new
  end

  def create
    @photo = Photo.new(photo_params)
    if @photo.save
      flash[:success] = 'Photo Created successfully'
      redirect_to root_path
    else
      render :new
    end
  end

  private
  def photo_params
    params.require(:photo).permit(:title, :description, :votes, :image, :image_cache)
  end
end
With the controller done, we can update our routes ‘config/routes.rb’. It only has three routes. The index route is the main route of our project.
Rails.application.routes.draw do
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
  root 'photos#index'
  get '/upload', to: 'photos#new'
  post '/photos', to: 'photos#create'
end
Now we can work with the View. First the new view ‘app/views/photos/new.html.erb’ to create the photo. It's important to create the hidden field called _cache at the end. This is so if validation fails, we don't need to search the photo again. That's why this field is included in the “photo_params” function of the controller. We also need to put in the form multipart: true as an option of the HTML because we need to have the file uploaded in our form.
<%= form_with(model: @photo, local: true, html: { multipart: true }) do |form| %>
    <% if @photo.errors.any? %>
        <div>
            <h2><%= pluralize(@photo.errors.count, "error") %></h2>
            <ul>
                <% @photo.errors.full_messages.each do |message| %>
                    <li><%= message %></li>
                <% end %>
            </ul>
        </div>
    <% end %>
    <div class="field">
        <%= form.label :title %>
        <%= form.text_field :title %>
    </div>
    <div class="field">
        <%= form.label :description %>
        <%=form.text_field :description %>
    </div>

    <div class="field">
        <%= form.label :votes %>
        <%= form.number_field :votes%>
    </div>

    <div class="field">
        <%= form.label :image, 'Upload the foto' %>
        <%= form.file_field :image %>
    </div>

    <%= form.hidden_field :image_cache %>

    <div class="actions">
        <%= form.submit "Upload Your photo" %>
    </div>

<% end %>
And the index view ‘app/views/photos/index.html.erb’ is simple. It only shows all the fields for every photo. At the end we have our link to the upload route (where there’s the form to upload our image).
<% @photos.each do |photo| %>
  <ul>
    <li><%= photo.title %></li>
    <li><%= photo.description %></li>
    <li><%= photo.votes %></li>
    <li><%= photo.image %></li>
  </ul>
<% end%>

<%= link_to "Create new", upload_path %>
Here is a simple app (don’t forget to run the migrations). We can create a photo and see all of the photos created, but if we tried to see something similar to an image it’s not going to work. We cannot see any photo, photo name, or URL. Instead in the photo.image field, we can see something similar to this #<ActionDispatch::Http::UploadedFile:0x00007fb0651ff3c0> We are going to solve this with the IBM Cloud. For this, we need to use three gems:
We can put these gems in our gemfile.
# My Gems
# Uploader
gem 'carrierwave', '~> 2.0'
# Help with Ibm Cloud (aws configuration)
gem 'fog-aws'
# image resizing
gem 'mini_magick'
We proceed to install these gems with this command:
$ bundle install

Configuring Carrierwave and Fow-aws

This is the best part of this tutorial. For the configuration of carrierwave, fog-aws, and minimagick, to work with IBM Cloud, we need to have this:
  • Region.
  • Bucket Name.
  • Endpoint.
  • access_key_id.
  • secret_access_key.
The first step to do is generate the uploader for the image, the name of the uploader is up to you but I’m going to call it photo.
$ rails generate uploader photo
This command will generate a file called photo_uploader.rb inside the folder ‘app/uploaders’, this is a configuration file. Here we are going to change some things.
Comment the storage :file to storage :fog (to use with our fow-aws gem). Uncomment the extension_whitelist function to only allow uploading images. Uncomment the version :thumb. This will use mini_magick to create a thumb version of our file. To use this gem we need to uncomment this line: ‘include CarrierWave::MiniMagick’.
So our file ‘app/uploaders/photo_uploader.rb’ needs to look like this:
class PhotoUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick
  storage :fog
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  version :thumb do
    process resize_to_fit: [50, 50]
  end

  def extension_whitelist
    %w(jpg jpeg gif png)
  end
end
After we configure our uploader we need to add it to the model. This is simple. We need to mount the uploader to one field on our model. That field needs to be a field of the string type (that's why when we generate our model we made a field called image of the string type) and attach the uploader; in this case, it’s the PhotoUploader.
class Photo < ApplicationRecord
  mount_uploader :image, PhotoUploader
  validates_presence_of :title, :description, :image
end
Our next step is to configure fog (that line that we uncomment storage :fog). For that we need to create an initializer for fog. This file needs to be called carrierwave.rb and needs to be inside the folder ‘config/initializers’. So we are going to paste this placeholder code in our file ‘config/initializers/carrierwave.rb’.
CarrierWave.configure do |config|
  config.fog_credentials = {
    provider:              'AWS',                        # required
    aws_access_key_id:     'xxx',                        # required unless using use_iam_profile
    aws_secret_access_key: 'yyy',                        # required unless using use_iam_profile
    use_iam_profile:       true,                         # optional, defaults to false
    region:                'eu-west-1',                  # optional, defaults to 'us-east-1'
    host:                  's3.example.com',             # optional, defaults to nil
    endpoint:              'https://s3.example.com:8080' # optional, defaults to nil
  }
  config.fog_directory  = 'name_of_bucket'                                      # required
  config.fog_public     = false                                                 # optional, defaults to true
  config.fog_attributes = { cache_control: "public, max-age=#{365.days.to_i}" } # optional, defaults to {}
end
Before configuring this code, we need to store our credentials in a safe place (if we upload the code with the credentials it can be very dangerous because in a public repo everyone can steal the credentials and that is a bad thing). So we are going to use Credentials.

Credentials on Rails 6

Using credentials with Rails 6 is so simple. We have a file called credentials.yml.enc inside the folder ‘config’. This file is encrypted with our ‘master.key’ located in the same folder. That's why our master.key file always needs to be ignored for git. One use of the master.key (between many others) is to decrypt our credentials.yml.enc and use the information that is inside (generally used to store credentials of APIs).
To access, decrypt, and edit credentials.yml.enc first we need our favorite editor (for example: gedit, emacs, geany, kate, kwrite, vim, mousepad, etc.) and use the name of the editor to edit the information. I’m going to use nano because I love it.
We need to run this command in our terminal:
$ EDITOR=’nano’ rails credentials:edit
Now our favorite editor will display the information of credentials.yml.enc. Here we can simply uncomment these lines:
# aws:
#   access_key_id: 123
#   secret_access_key: 345
And add our keys without quotes, so its something like this
aws:
  access_key_id: e5e4f3363d2dbd1aebf13630a26078d5
  secret_access_key: b083b1387037b87454298fc1067404b71feec88b5d945d25
We can save our file, and it will be encrypted. Now we can access that data in our Rails app. We just need to call:
NameOfTheApp::Application.credentials.nameOfTheCredential[:keyToFetch]
If we forget the name of the application we can find it here: ‘config/application.rb’. If I want to access key id for the name of the module (in my case is MyCatPhotos) I need this lines.
MyCatPhotos::Application.credentials.aws[:access_key_id]

MyCatPhotos::Application.credentials.aws[:secret_access_key]

Finish configuration

Now it’s time to configure the initiliazer ‘config/initializers/carrierwave.rb’. We need to put the aws_secret_key_id and aws_secret_access_key from our encrypted credentials.
  • use_iam_profile needs to be false (it’s the default).
  • region is the region of our bucket.
  • host will be nil (it’s the default).
  • endpoint will be our endpoints.
These are the parts of fog credentials. The last thing is the config.fog_directory. Here we need to put our bucket name and leave everything with its defaults. So our code needs to look like this:
CarrierWave.configure do |config|
    config.fog_credentials = {
      provider:              'AWS',                        
      aws_access_key_id:     MyCatPhotos::Application.credentials.aws[:access_key_id],                        
      aws_secret_access_key: MyCatPhotos::Application.credentials.aws[:secret_access_key],                        
      use_iam_profile:       false,                         
      region:                'us-south',                  
      host:                  nil,             
      endpoint:              'https://s3.us-south.cloud-object-storage.appdomain.cloud' 
    }
    config.fog_directory  = 'my-cat-photos-cos-standard-cpm'                                      
    config.fog_public     = false                                                 
    config.fog_attributes = { cache_control: "public, max-age=#{365.days.to_i}" } 
  end
and we are almost done!.
Every time we upload an image it’s going to be saved in our cloud. So to retrieve the image and show it on our page we need to access the property URL, and this will be the source of an image tag. In Rails, we can achieve that with <%= image_tag photo.image.url %>
So we modify our index view a little bit, ‘app/views/photos/index.html.erb’, to show the image, if it exists.
<% @photos.each do |photo| %>
  <ul>
    <li><%= photo.title %></li>
    <li><%= photo.description %></li>
    <li><%= photo.votes %></li>
    <%= image_tag photo.image.url if !photo.image.url.nil? %>
  </ul>
<% end %>
Now our project can show the images that we upload to the cloud. We can also inspect the url of our images and they are created by default with carrierwave and fow. Looks something like this:
https://my-cat-photos-cos-standard-cpm.s3.us-south.cloud-object-storage.appdomain.cloud/uploads/photo/image/1/bebudito.jpg?X-Amz-Expires=600&X-Amz-Date=20200623T205500Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=dbd1aebf108e4f33b3630a2e5d5363d2%2F20200623%2Fus-south%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=723299b8ee0e077a7c70265673825873002ed71838ea2ed28e8a9dc2066ad49f
If we can retrieve the thumb version it’s with photo.image.thumb.url.
This is the end of this tutorial. We left out a little bit of design and functionalities (like the voting). If you like I can make another tutorial about design and functionalities. I didn’t do it here because this tutorial is too long, but you can see the full app on my repo.

Written by redacuve | Web Developer | Rider | Traveler | Cookie eater
Published by HackerNoon on 2020/07/22