Rails Girls MY App Guide (Extended)

Extended for Rails Girls KL by Faezrah, @fzrhrs
Credits to Ben Walker for the CSS styling.

We are going to build our very own Instagram clone.

Alright, let's build it.

1.Creating a New Rails Project

Let's call our new project instaclone.

To create a new project in Rails, run this command in the terminal.

$ rails new instaclone

This will create a new Rails application called instaclone folder, and auto-generate files and folders that make up the structure of a Rails application. Most of the work in this tutorial will happen in the app sub-folder.

Now switch to your new Rails folder.

$ cd instaclone

To see what other files Rails has generated for you, type this command in the terminal:

$ ls

Now, we need to make sure we've got Rails working properly.

To do this, we need to run the server by typing this command:

$ bin/rails server -b 0.0.0.0

To see your application in action, open a browser window and navigate to: http://localhost:3000/

You should now see this friendly Rails default page.

To stop the server, hit CTRL + C.

Mentor: Explain what each command does. What was generated? What does the server do?

2.Getting Up and Running

In your Instaclone application, you will now create a new resource. A resource is a term used to call a collection of similar objects so users will be able to access via URI and perform CRUD operations.

For example, we want to have a collection of photos in our application.

Go ahead and run this in the terminal:

$ bin/rails generate resource photo file:string caption:string

This command will generate files we need for our photos collection, as well as two columns in our database.

But to get it to work properly we need to run a couple of other commands to update our database and restart the server.

$ bin/rake db:migrate
$ bin/rails server -b 0.0.0.0

Mentor: Explain the command, the model name and related database table, naming conventions, attributes and types, etc. What are migrations and why do you need them?

3.The First Form

Now that you have everything you need to build an Instagram clone, let's get coding.

Ideally, you need a place within the application to submit a new photo. To do this, we will need a form for our users to fill in.

Open app/controllers/photos_controller.rb, and you will see something like this:

class PhotosController < ApplicationController
end

To start with, we will need two actions in the controller. A new action and a create action.

Below the line:

class PhotosController < ApplicationController

Add the code like so:

def new
  @photo = Photo.new
end

def create
  @photo = Photo.new(photo_params)
  if @photo.save
  else
    render :new
  end
end

private
  def photo_params
    params.require(:photo).permit(:file, :caption)
  end

Now, go to app/views/photos/ and create a new file called new.html.erb.

Type the following code:

<%= form_for @photo do |f| %>
  <p>
    <%= f.label :file %><br>
    <%= f.text_field :file %>
  </p>

  <p>
    <%= f.label :caption %><br>
    <%= f.text_field :caption %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

Save the file and go to http://localhost:3000/photos/new to see the form live in your browser after you refresh it.

Checkpoint:

Your page should now look something like this:

It looks a little basic, right?

That's okay, let's focus on building our main feature before we move on to styling the application.

Mentor: Explain what is MVC in Rails.

4.Showing Photos

Before we can test our new photo form, let's add another action in our app/controllers/photos_controller.rb.

Below the create action, add a show action, like so:

def show
  @photo = Photo.find(params[:id])
end

Next, create a show file in app/views/photos and call it show.html.erb.

In your show.html.erb, type the following code:

<%= link_to @photo.file %>
<h3><%= @photo.caption %></h3>

We also need to modify our create action a bit. Add a redirect when your photo is saved, like so:

def create
  @photo = Photo.new(photo_params)
  if @photo.save
    redirect_to photo_path(@photo)
  else
    render :new
  end
end

Now, every time when we upload a new photo, if the photo is saved succefully, we will get redirected to the photo show page automatically.

Give it a try. Test the form, and it should redirect you to a page with content you've just submitted.

Checkpoint:

Your photo show page should look similar to this:

Mentor: Talk a little about CRUD operations and RESTful resources.

5.Uploading Photos

Now you can actually create a new caption for a photo, but how to actually upload an image?

We need to install a piece of software to let us upload files in Rails.

Open Gemfile in the project directory using your text editor and under the line:

 # gem 'capistrano-rails', group: :development 

Add this line:

 gem 'carrierwave' 

Every time you add a new gem, you need to run a command to install it to your application.

So, in the terminal, run:

$ bundle install 

At this point you might need to restart the Rails server process in the terminal.

Note: Some people might be using a second terminal to run the rails server continuously. If so you need to restart the Rails server process now. This is needed for the app to load the added library.

Go to the terminal tab which runs the server and then hit CTRL + C to quit the server. Once it has stopped, you can press the up arrow to get to the last command entered, then hit enter to start the server again.

Mentor: Explain what libraries are and why they are useful. Describe what open source software is.

Now that we have installed the gem, we can generate the code for handling uploads.

In the terminal run:

$ bin/rails generate uploader Photo 

This command will generate a new file called photo_uploader.rb. You need this file to configure your upload settings.

Next, open app/models/photo.rb and under the line:

 class Photo < ApplicationRecord 

Add:

 mount_uploader :file, PhotoUploader 

Then, open app/views/photos/new.html.erb and change:

 <%= f.text_field :file %> 

To:

 <%= f.file_field :file %> 

This will open your local window and let you select a photo to upload.

Sometimes, you might get an TypeError: can’t cast ActionDispatch::Http::UploadedFile to string.

If this happens, in file app/views/photos/new.html.erb change the line:

 <%= form_for @photo do |f| %> 

To:

 <%= form_for @photo, html: { multipart: true } do |f| %> 

If you refresh your browser now, your form should now look something like this:

This time we can actually add a new photo. When you upload an image it doesn’t look nice because it only shows a path to the file, so let’s fix that.

Open app/views/photos/show.html.erb and change:

 <%= link_to @photo.file %> 

To:

 <%= image_tag(@photo.file_url, width: 600) if @photo.file.present? %> 

Now refresh your browser to see what changed.

6.Showing All Photos

Now that we can upload our photos, we need to find a way to show all of our photos.

To do this, create an index action in our app/controllers/photos_controller.rb.

Below this line:

class PhotosController < ApplicationController

And before the new action, type the code:

  def index
    @photos = Photo.order(created_at: :desc)
  end

Next, create a new file called index.html.erb inside apps/views/photos and type the following:

  <h1>All Photos</h1>
  <% if @photos.any? %>
    <% @photos.each do |photo| %>
      <p>
        <%= image_tag(photo.file_url, width: 300) if photo.file.present? %>
        <%= photo.caption %>
      </p>
    <% end  %>
  <% else  %>
    <p>No photos found.</p>
  <% end %>
  <p><%= link_to "New Photo", new_photo_path %></p>

Now, go to http://localhost:3000/photos to see the changes.

Checkpoint:

Your index page should look something like this:

Mentor: Talk a little the relationship between HTML and Rails. What part of views is HTML and what is Embedded Ruby (ERB)?

7.Updating Photos

The first step we'll take is adding an edit and update actions to the PhotosController after the show action, as follows:

def edit
  @photo = Photo.find(params[:id])
end

def update
  @photo = Photo.find(params[:id])

  if @photo.update(photo_params)
    redirect_to @photo
  else
    render :edit
  end
end

Now, create a file called edit.html.erb in app/views/articles/ and add a form similar to the one we used when creating new photos, like this:

<%= form_for @photo do |f| %>
  <p>
    <%= f.label :file %><br>
    <%= f.file_field :file %>
  </p>

  <p>
    <%= f.label :caption %><br>
    <%= f.text_field :caption %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

Finally, we want to show a link to the edit action in the photo show page, like so:

<%= link_to 'Edit', edit_photo_path(@photo) %>

Checkpoint: And here's how our app looks so far:

7.Deleting Photos

Just like Instagram, we would like to be able to delete our photos.

Let's first create a destroy action in the controller that deletes the specific record. We then need to provide a link to that action in our index view.

So, in our photos_controller.rb, just below the update action, type as follows:

  def destroy
    @photo = Photo.find(params[:id])
    @photo.destroy

    redirect_to photos_path
  end

And our simple link to that action with a warning on our show.html.erb view:

  <%= link_to "Delete", photo_path(@photo), method: :delete, data: { confirm: "Are you sure you want to delete this photo?" } %>

Save your file and refresh your browser, you should see a delete link available for the photo, similar to this:

When you click on the delete link, a confirmation dialog box will appear, like this:

And it’s done! Try deleting one of your photos now and see what happens.

8.Styling

Now, ehem... It's the moment of truth. Styling!

To start, open Gemfile, and right below the carrierwave gem, add:

gem 'bootstrap-sass', '~> 3.3.6'
gem 'font-awesome-sass', '~> 4.6.2'

Remember, you have to run:

$ bundle install

Every time you add a new gem.

You might also need to restart your server so the newly added gem is loaded.

Now, in app/assets/stylesheets, rename application.css to application.scss.

Then, replace all of its content, with:

@import "bootstrap-sprockets";
@import "bootstrap";
@import "font-awesome-sprockets";
@import "font-awesome";

Checkpoint:

My application.scss looks like this:

Next, open application.js in app/assets/javascripts, and add the following:

//= require bootstrap-sprockets

Below this line of code:

//= require jquery_ujs

Mentor: Talk a little about CSS, CSS Framework and Bootstrap.

Okay so you have Bootstrap installed. Now, what?

Since this application is after all, a clone of Instagram, we're going to steal their styling, just a little bit.

In app/views/layouts/, open application.html.erb and add the following code, right below body:

<nav class="navbar navbar-default navbar-static-top">
  <div class="container">
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <%= link_to "InstaClone", photos_path, class: "navbar-brand" %>
    </div>
    <div id="navbar" class="navbar-collapse collapse">
      <ul class="nav navbar-nav">
        <li><%= link_to "New Photo", new_photo_path %></li>
      </ul>
    </div>
  </div>
</nav>

This is our basic header, and it's shown in all of our pages because we put the code in application.html.erb. This means we don't have to add the navbar to everything!

Still in application.html.erb, replace:

<%= yield %>

With the following:

<section class="container">
  <%= yield %>
</section>

Now, let's style the photos index page first. Add these code to your application.scss:

body {
  background-color: #fafafa;
}

.navbar-brand {
  a {
    color: #125688;
  }
}

.navbar-default {
  background-color: #fff;
  height: 54px;
  .navbar-nav li a {
    color: #125688;
  }
}

.navbar-container {
  width: 640px;
  margin: 0 auto;
}

.photos-wrapper {
  padding-top: 40px;
  margin: 0 auto;
  max-width: 642px;
  width: 100%;
}

.photo {
  background-color: #fff;
  border-color: #edeeee;
  border-style: solid;
  border-radius: 3px;
  border-width: 1px;
  margin-bottom: 60px;
}

.photo-head {
  height: 64px;
  padding: 14px 20px;
  color: #125688;
  font-size: 15px ;
  line-height: 18px;
  .thumbnail {}
  .name {
    display: block;
  }
}

.image {
  border-bottom: 1px solid #eeefef;
  border-top: 1px solid #eeefef;
}

.caption {
  padding: 24px 24px;
  font-size: 15px;
  line-height: 18px;
}

.form-wrapper {
  width: 60%;
  margin: 20px auto;
  background-color: #fff;
  padding: 40px;
  border: 1px solid #eeefef;
  border-radius: 3px;
}

.edit-links {
  margin-top: 20px;
  margin-bottom: 40px;
}

Then, in app/views/photos/index.html.erb, replace the content of the page as follows:

<% if @photos.any? %>
  <div class="row">
    <% @photos.in_groups_of(3) do |group| %>
      <% group.compact.each do |photo| %>
      <div class="col-md-4">
        <div class="photo">
          <div class="photo-head">
            <h4 class="name">
              Rails Girls MY
            </h4>
          </div>
          <div class="image center-block">
            <%= link_to photo_path(photo) do %>
              <%= image_tag(photo.file_url, class: "img-responsive center-block") if photo.file.present? %>
            <% end %>
          </div>
          <p class="caption"><%= photo.caption %></p>
        </div>
      </div>
      <% end %>
    <% end  %>
  </div>
<% else  %>
  <p>No photos found.</p>
<% end %>

Checkpoint: Now, if you refresh your browser, the page should look something like this:

Much better than before, right?

Okay, next let's quickly tidy up our photo form and photo show page.

Open the apps/views/photos/new.html.erb, and make sure your code look similar to this:

<div class="form-wrapper">
  <%= form_for @photo, html: { class: 'form-horizontal', multipart: true } do |f| %>
    <div class="form-group">
      <%= f.label :file %><br>
      <%= f.file_field :file, class: "form-control" %>
    </div>

    <div class="form-group text-center">
      <%= f.label :caption %><br>
      <%= f.text_field :caption, class: "form-control" %>
    </div>

    <div class="form-group text-center">
      <%= f.submit class: "btn btn-success btn-block" %>
    </div>
  <% end %>
</div>

Checkpoint: This is how my photo form looks like:

Lastly, open app/views/photos/show.html.erb and replace all of its content to:

<div class="row">
  <div class="col-md-6">
    <%= image_tag @photo.file_url, class: "img-responsive"  if @photo.file.present? %>
  </div>
  <div class="col-md-6">
    <h4>Rails Girls MY</h4>
    <div class="btn-group">
      <%= link_to edit_photo_path(@photo), class: "btn btn-warning btn-xs" do %>
        <i class="i fa fa-pencil"></i>
      <% end %>
      <%= link_to photo_path(@photo), method: :delete, data: { confirm: "Are you sure you want to delete this photo?" }, class: "btn btn-danger btn-xs" do %>
        <i class="i fa fa-trash"></i>
      <% end %>
    </div>
    <hr>
    <p><%= @photo.caption %></p>
  </div>
</div>

Checkpoint: And this is how my photo show page looks like:

9.Refactoring

You probably noticed we haven't updated the styling for our edit page. But there's a reason for this.

Because our edit.html.erb page looks very similar to the new.html.erb page - that they share the same code for displaying the form, we can actually remove this duplication by using a view partial.

To do that, create a new file _photo_form.html.erb in app/views/photos and move all of the content from our new.html.erb to this new file.

Next, replace new.html.erb with the code below so we can use the partial view:

<h3>New Photo</h3>
<%= render "photo_form" %>

We also need to update our edit.html.erb to have the code as follows, by rewriting it completely:

<h3>Edit "Photo #<%= @photo.id %>"</h3>
<%= render "photo_form" %>

Mentor: Talk about layouts and rendering in Rails.

10.Refine The Navigation

Now, one last thing. Let's fine tune the route a little bit.

If go to the index page of your app (that’s your app URL without /photos part) it still shows the "Yay! You're on Rails!" page. Let's make it go directly to the photos page.

Open config/routes.rb and after the first line, add:

  root "photos#index"

Test the change by refreshing the preview page in your browser.

Mentor: Talk about routes, and include details on the order of routes and their relation to static files.

Now, if you go to the main page of your app, you will see the list of your photos.

10. What's Next?

If you're feeling adventurous:

Check out our main page for additional guides.