Rails Girls Sinatra App Guide

*Created by Sorcha Abel

What is Sinatra?

Getting Started

1. Creating the application with Cloud9

Follow the steps outlined in the Rails Girls Cloud9 Sinatra Setup Guide Cloud9 Setup

2. Write a description for your application

Cloud9 automatically creates a file called REAMDME.md. The .md extension means that this is a MarkDown file. A readme file is useful for documenting what your application does, and how a developer can setup their environment to develop or run your application. There is no clearly defined Markdown standard, however, GitHub has their own flavor(sic).

eg.

# My awesome Sinatra app!
This app is like the RailsGirls Ideas app, but using Sinatra instead of Rails!

We will add more documentation as the project evolves.

3. Git

While you are developing your app, you should be committing your changes and pushing them to a remote repository, such as GitHub.

We’ll start by initialising git within our app and adding the current files.

git init
git add .
git commit -m "Initial commit"

You can modify the Initial commit message to anything you like.

The next step is to create a remote repository on GitHub and add it to your app.

They will be similar to below. DO NOT copy the lines below. You must push to your own newly created repo

git remote add origin https://github.com/YourName/project_name.git
git push -u origin master

Whenever you wish to commit your changes to GitHub, enter the following(change the message!):

git add .
git commit -m "Change this message to describe your changes!"
git push origin master

Start Coding

4. Create a Gemfile

There are a few gems we will need to install before we can start coding. The easiest way to install gems is by using the bundler gem. Simply run gem install bundler before proceeding.

Bundler uses a Gemfile to record which gems our app needs. We need to manually create this Gemfile for our app. This can be done from the terminal window or the Cloud9 file explorer window.

To use the Cloud9 explorer window, right mouse-click and select New file. Rename this file from Untitled to Gemfile.

To use the terminal:

touch Gemfile

A Gemfile will now appear in the Cloud9 file explorer window. Double click to open it and type in the following code which specifies which gems we want included in our application:

source 'https://rubygems.org'

gem "sinatra"
gem "rake"
gem "sqlite3"
gem "activerecord"
gem "sinatra-activerecord"

group :development do
  gem "sinatra-contrib"
  gem "tux"
end

Save the Gemfile (command ⌘ + s(mac), control + s(windows and linux) or use the menu option File -> Save).

Commit your Gemfile to git:

git add .
git commit -m "Created Gemfile"
git push origin master

Any time you edit a Gemfile you must install the new gems. To do so, run the following command in the terminal window:

bundle install

To update your gems to their latest version, run:

bundle update

Commit the Gemfile.lock file:

git add .
git commit -m "bundle install"
git push origin master

Quick Overview of the gems

sinatra allows us to use the Sinatra DSL (Domain Specific Language) in our app.

rake is used to run automated tasks, like creating and migrating the database.

sqlite3 is the database we will be using to store data.

activerecord is the interface the app uses to communicate with the database.

sinatra-activerecord is a bridge that allows us to use Active Record in a Sinatra application.

tux allows us to interact with the database through the command line.

sinatra-contrib will reload our app, so we don’t have to stop and start the server whenever a minor change is made.

5. Project Structure

Sinatra doesn’t impose any structure on your project (which can be both a blessing and a curse). The entire structure of the project is in your hands and this flexibility can cause headaches. The structure outlined in this app is a pattern that can work well for many of your future Sinatra apps.

In the next section we will focus on file and folder creation. Pay particular attention to where you create files/folders. Ask an instructor if you are unsure.

5a. File/Folder Creation

The instructions given will show you how to create files and folders through the terminal window. You can however create all files and folders by right clicking in the Cloud9 explorer window as we did above. If you feel comfortable with file and folder creation follow the structure in the image below and skip to the next section.

From the terminal create a folder called app by executing the following command:

mkdir app

Change to the app folder and create three subfolders:

cd app
mkdir models views controllers

You have just created a major component of our application’s structure:

  1. models (database communication)
  2. views (what the end user sees)
  3. controllers (lots of routes for our app)

From the terminal window, move up one level of the project structure and into the root of the project:

cd ..

First we will create a folder called files. This folder will contain the files that we upload from our app. Next we will create a folder called config. Then we will create a file called application.rb inside the newly created config folder. application.rb is the file that will load all the files needed by our application. Finally we will create a file called database.yml that will manage our database connections.

mkdir files
mkdir config
cd config
touch application.rb
touch database.yml

From the terminal window, move up one level of the project structure and into the root of the project. From here create another folder called db. This will store information about our sqlite3 database including our database migrations.

cd ..
mkdir db

We are almost there. The final step in our skeleton structure for our application is to create a config.ru file in the root of the project. The config.ru file is a convention that is required by certain deployment procedures and tools (like shotgun, tux, and Heroku). We also create a Rakefile to describe any automated tasks.

touch config.ru
touch Rakefile

Congratulations! You may not realise it yet but you have just created a solid basic structure for a Sinatra app that you can reuse time and time again.

By the time our application is complete, it should look similar to the folder structure below:

5b. Commit the skeleton to git

If you wish to commit this skeleton folder structure to git, you may notice that the empty folders we created aren’t being committed; git will only remember files, not folders. To make git remember our structure, you must create a file inside each empty folder. The convention is to create a file called .keep. The dot at the start of the name means that your computers operating system should ignore them.

touch app/.keep
touch app/controllers/.keep
touch app/models/.keep
touch app/views/.keep
touch db/.keep
touch files/.keep

git add .
git commit -m "folder structure"
git push origin master

6. Configure the App

The first step is to set up the application.rb file. Double click on the file to open it (you will find it under the config folder). Take some time to understand what we are about to put into this file. Note you must save all files in cloud9 (command ⌘ + s(mac), control + s(windows and linux) or menu options File -> Save)

# require gems and classes
require 'bundler'
Bundler.require
$: << File.expand_path('../', __FILE__)
Dir['./app/**/*.rb'].sort.each { |file| require file }

# configure sinatra
set :root, Dir['./app']
set :public_folder, Proc.new { File.join(root, 'assets') }
set :erb, :layout => :'layouts/application'

Lets examine the first four lines in a bit more detail:

require 'bundler' enables our application to automatically discover the Gemfile.

Bundler.require loads into the project all the gems that are specified in the Gemfile.

$: << File.expand_path('../', __FILE__) adds the entire project to $LOAD_PATH. This allows Sinatra to find all the files you’ve added to the project. For more on $LOAD_PATH read the Rails Girls Brisbane blog post

Dir[’./app/**/*.rb’].sort.each { |file| require file } This line explicitly requires each file found in our model, view and controller folders.

Even though we haven’t set them up yet, we know the project is going to need these files. The last three lines of application.rb sets the root of the project and tells the application where the erb (embedded Ruby) files and CSS files are located.

6a. Rake

We need to set up our Rake file so we can run our helper tasks. Open the file Rakefile and add the following code:

require './config/application'
require 'sinatra/activerecord/rake'

6b. ActiveRecord

The final step in our setup is the database.yml file. This file will contain our sqlite database configuration and is required for connecting to the database. Open the config folder and double click to open the file. Copy and paste the lines below.

sqlite: &sqlite
  host: 127.0.0.1
  adapter: sqlite3

development:
  <<: *sqlite
  database: db/development.sqlite3

production:
  <<: *sqlite
  database: db/production.sqlite3

test:
  <<: *sqlite
  database: db/test.sqlite3

6c. Commit your setup

git add .
git commit -m "Setup and configuration"
git push origin master

7. Migrations

Just like we did in the original Rails Girls, we need to create an ideas table so we can save our ideas. From the terminal window run:

rake db:create_migration NAME=create_ideas

A migration file will have been created in the db/migrate folder. The name of the file is of the form YYYYMMDDHHMMSS_create_ideas.rb. The timestamp determines which migration should be run and in what order. Double-click to open it and enter the following. This is similar to the scaffold command you ran in Rails Girls.

class CreateIdeas < ActiveRecord::Migration
  def change
    create_table :ideas do |t|
      t.string :name
      t.text :description
      t.string :picture

      t.timestamps null: false
    end
  end
end

Each time we create a migration, it needs to be run so that the changes get applied to the database. Open the terminal window and type:

bundle exec rake db:migrate

This will create the ideas table inside the db/development.sqlite3 database. You can see it in the schema file, db/schema.rb.

7a. Commit your migration and schema, Ignore the database

Because our database might contain sensitive or private data, we do not want to commit it to git. git will ignore any files listed in a .gitignore file.

Create the .gitignore file using the Cloud9 file explorer or through terminal:

touch .gitignore

Cloud9 will sometimes hide files starting with a dot, but we need to see it. Click the “cog” icon in the upper right corner of the file explorer to open the Cloud9 preferences tab. Under User Settings select Tree & Navigate and removed the .* from the hidden file patterns options. If unsure ask an instructor.

Open the .gitignore file and enter the following, to ignore all sqlite3 databases.

*.sqlite3

Now commit everything to git

git add .
git commit -m "database migration and .gitignore"
git push origin master

8. Project Code

We have just created the ideas table by running the migration, so let’s now create the corresponding model. Navigate to the app/models folder and create the idea.rb file (right click in cloud9 file explorer window or use the terminal window). We will use the terminal window.

touch app/models/idea.rb

Double click to open the file and enter the following code:

class Idea < ActiveRecord::Base

end

This is just an empty file at this stage, we will add code to it later on.

9. Routes

Finally it is time to create routes. Routes are simply the rules that tell your application which code to run for a given URL. From the controllers folder, create a file called ideas_controller.rb (you can use the terminal window or cloud9 file explorer)

%w(/ /ideas).each do |path|
  get path do
    @ideas = Idea.all
    erb :'ideas/index'
  end
end

This route will map the URL ideas/index to a listing of all ideas. We have just told Sinatra to look in the ideas folder for an html file called index.erb (it has a .erb suffix rather than .html because it contains embedded Ruby as well as html).

Now that we have created our route we now need to create a corresponding index page to display it. Create a folder for ideas in the app/views/ directory (from terminal window mkdir app/views/ideas or use the cloud9 file explorer window). Then create a file called index.erb.

10. Views

Type or paste the following into the index.erb file you just created

<h1>Listing Ideas</h1>

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Description</th>
      <th>Picture</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
  <% @ideas.each do |idea| %>
    <tr>
      <td><%= idea.name %></td>
      <td><%= idea.description %></td>
      <td><%= idea.picture.present? %></td>
      <td><a href="/ideas/<%= idea.id %>/edit">Edit</a></td>
      <td><a href="/ideas/<%= idea.id %>">Show</a></td>
      <td><% if idea.picture %><a href="/files/<%= idea.picture %>">View Picture</a><% end %></td>
      <td><% if idea.picture %><a href="/files/<%= idea.picture %>/download">Download Picture</a><% end %></td>
    </tr>
  <% end %>
  </tbody>
</table>
<br>

10a. Commit your ideas model, controller and view

git add .
git commit -m "Ideas model, controller and view"
git push origin master

11. Improving our design

Although our application currently has only one page, it will eventually have many pages. To give it a consistent look and feel, we will want to have the same header and footer on each page. Instead of copying and pasting the header and footer onto each page, and having to edit each page if we want to make a change, we will create one file that will contain all the common page content. This is an example of adhering to the DRY (Don’t Repeat Yourself) principle of Ruby programming.

Create a subfolder of views folder and call it layouts. Then create a file in the layouts folder and call it application.erb.

Enter the following html/erb code:

<!DOCTYPE html>
<html>
  <head>
    <title>Workspace</title>
    <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
    <!-- Optional theme -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
    <!-- Latest compiled and minified JavaScript -->
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
    <!-- Application CSS -->
    <link rel="stylesheet" type="text/css" href="css/application.css">
    <!-- Application JS -->
    <script src="js/application.js"></script>
  </head>
  <body>
    <nav class="navbar navbar-default navbar-fixed-top" role="navigation">
      <div class="container">
        <div class="navbar-header">
          <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
            <span class="sr-only">Toggle navigation</span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
            <span class="icon-bar"></span>
          </button>
          <a class="navbar-brand" href="/ideas">The Idea app</a>
        </div>
        <div class="collapse navbar-collapse">
          <ul class="nav navbar-nav">
            <li class="active"><a href="/ideas">Home</a></li>
            <li class="active"><a href="/new">New Idea</a></li>
            <li class="active"><a href="/about">About Me</a></li>
          </ul>
          <p class="navbar-text pull-right">
        </div>
      </div>
    </nav>
    <div class="container">
      <% @flash.each do |type, content| %>
        <% if content.respond_to?('each') %>
          <% content.each do |item| %>
            <%= render 'alert',
                       type:    type,
                       content: item %>
          <% end %>
        <% end %>
      <% end if @flash %>

      <%= yield %>
    </div>
    <footer>
      <div class="container">
        Rails Girls <%= Time.now.year %>
      </div>
    </footer>
    <script src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.0.3/js/bootstrap.js"></script>
  </body>
</html>

11a. Commit your layout

git add .
git commit -m "Application layout view"
git push origin master

12. Preview in a browser

In order to start the server, you need to be in the root directory of your project. Use cd .. repeatedly from your terminal window until you return to your workspace. Don’t worry if you go back too far, simply cd workspace will bring you back to where you should be. Ask an instructor if unsure.

From the terminal window run the following to start the server:

ruby config/application.rb -p $PORT -o $IP

From the cloud 9 IDE click the share button on the right hand upper window. From the second option (Application:), click on the link and select the copy option. Then navigate to your browser and paste the url into the browser window.

You should now see the ideas app running in the browser! ctrl+c will terminate the server. At this stage there is a small style issue with your app, it doesn’t display as well as it should. We will cover this in a following section.

13. New Route and View

Generally speaking each route will have a corresponding view. Let’s create a route and a view for when we are ready to create a new idea.

Open the ideas_controller.rb file and add the following code after the last line:

%w(/new /ideas/new).each do |path|
  get path do
    @title = 'New Idea'
    @idea  = Idea.new
    erb :'ideas/new'
  end
end

Now we need to create the view to allow our users to add a new idea.

Navigate to the views/ideas folder and create a new file called new.erb. Add the following code:

<h1><%=@title %></h1>
<form action="/ideas" method="post" enctype="multipart/form-data">
  <label for="idea_name">Name:</label>
  <br />
  <input id="idea_name" name="idea[name]" type="text" value="<%= @idea.name %>" />
  <br />

  <label for="idea_description">Description:</label>
  <br />
  <textarea id="idea_description" name="idea[description]" rows="5"><%= @idea.description %></textarea>
  <br />

  <label for="idea_picture">Picture:</label>
  <br />
  <input type="file" id="idea_picture" name="idea[picture]" rows="5"><%= @idea.picture %>
  <br />

  <input type="submit" value="Create Idea" />
</form>

Let’s pause for a moment to review what we have just done. Our first step was to create a route for the new action. We did that in the ideas_controller.rb file.

We set up two variables that will be used by the new view, @title and @idea. @title is just the text that appears at the top of the web page. @idea is a variable that will hold all the data we will enter into our form and eventually insert into the database.

Finally, with the line erb :'ideas/new' we defined a route that will link the URL ideas/new to the “New Idea” view.

When the “Create Idea” button is pressed on the form, it will POST the form data back to our server. We need a route to consume that data and persist it to the database. Add the following route to the ideas_controller.rb file.

post '/ideas' do
  if params[:idea]
    @idea           = Idea.new(params[:idea])
    if params[:idea][:picture] && params[:idea][:picture][:filename] && params[:idea][:picture][:tempfile]
      filename      = params[:idea][:picture][:filename]
      @idea.picture = filename
      file          = params[:idea][:picture][:tempfile]
      FileUtils.copy_file(file.path,"files/#{@idea.picture}")
    end
    if @idea.save
      redirect '/ideas'
    else
      erb :'ideas/new'
    end
  else
    erb :'ideas/new'
  end
end

13a. Preview in a browser

From the terminal window run the following to start the server:

ruby config/application.rb -p $PORT -o $IP

From the Cloud 9 IDE click the share button on the right hand upper window. From the second option (Application:), click on the link and select the copy option. Then navigate to your browser and paste the url into the browser window.

You should now see the ideas app running in the browser. ctrl + c will terminate the server.

14. More routes

So now we can add an idea in our app and save it to the database. But what about retrieving an idea from the database? The next route in our application will allow us to retrieve just one idea from our database.

Navigate to ideas_controller.rb and enter the following route:

get '/ideas/:id' do
  @idea = Idea.find(params[:id])
  erb :'ideas/show'
end

Similar to before this route is calling erb :'ideas/show' so let’s create that file now. Navigate to the views/ideas folder and create a file called show.erb. Add the following code:

<p>
  <strong>Name:</strong>
  <%= @idea.name %>
</p>

<p>
  <strong>Description:</strong>
  <%= @idea.description %>
</p>

<p>
  <strong>Picture:</strong>
  <%= @idea.picture if @idea.picture.present? %>
</p>
<p>
  <%= delete_idea_button(@idea.id) %>
</p>

15. Delete routes

So now we can view a list of all our ideas, we can create a new idea, and we can view a single idea. But what if we want to delete an idea that we have previously added?

Navigate to ideas_controller.rb and enter the following code:

helpers do
  def delete_idea_button(idea_id)
    erb :'ideas/_delete_idea_button', locals: { idea_id: idea_id }
  end
end

This is called a helper route. It is referring to a view called ideas/_delete_idea_button so let’s create that.

Navigate to the views/ideas folder and create a new file called _delete_idea_button.erb. Be sure to include the underscore.

Open the newly created file and enter the following code:

<form action="/ideas/<%= idea_id %>" method="post">
  <input type="hidden" name="_method" value="delete" />
  <input type="submit" value="Delete Idea" />
</form>

Finally we need to create the route that will do the actual deleting. Return to the ideas_controller.rb file and enter the following

delete '/ideas/:id' do
  Idea.find(params[:id]).destroy
  redirect '/ideas'
end

Lets preview our changes in the browser. From the terminal window run the following to start the server:

ruby config/application.rb -p $PORT -o $IP

Note that to delete an idea, you must first open the idea (click on Show). From there you should see the Delete button.

16. Updating an Idea

We are almost done! The final piece of database functionality it to retrieve a single idea and update it. As we did in the previous steps, let’s start by creating a new route. Add the following code to ideas_controller.rb:

get '/ideas/:id/edit' do
  @idea = Idea.find(params[:id])
  erb :'ideas/edit'
end

Again we see the route is calling a view erb :'ideas/edit'. Let’s create it by navigating to views/ideas and creating a file called edit.erb with the following code:

<h1>Edit Idea</h1>
<form action="/ideas/<%= @idea.id %>" method="post" enctype="multipart/form-data">
  <input type="hidden" name="_method" value="put" />

  <label for="idea_name">Name:</label>
  <br />
  <input id="idea_description" name="idea[name]" type="text" value="<%= @idea.name %>" />
  <br />

  <label for="idea_description">Description:</label>
  <br />
  <textarea id="idea_description" name="idea[description]" rows="5"><%= @idea.description %></textarea>
  <br />

  <label for="idea_picture">Picture:</label>
  <br />
  <input type="file" id="idea_picture" name="idea[picture]" rows="5"><%= @idea.picture %>
  <br />

  <input type="submit" value="Update Idea" />
</form>

As with the new action above, we know we will need to create a second route to update the database with the new data. Navigate back into ideas_controller.rb and add the following code:

put '/ideas/:id' do
  if params[:idea].try(:[], :picture)
    file      = params[:idea][:picture][:tempfile]
    @filename = params[:idea][:picture][:filename]
  end

  @idea = Idea.find(params[:id])

  if @idea.update_attributes(params[:idea])
    if @filename
      @idea.picture = @filename
      @idea.save
      File.open("./files/#{@filename}", 'wb') do |f|
        f.write(file.read)
      end
    end
    redirect "/ideas/#{@idea.id}"
  else
    erb :'ideas/edit'
  end
end

Lets preview our changes in the browser. From the terminal window run the following to start the server:

ruby config/application.rb -p $PORT -o $IP

We have left out an important route for our app: managing the files we uploaded. To keep things simple let’s create a new controller that will deal with the files in our app.

Navigate to the app/controllers folder and create a new controller called files_controller.rb. Add the following code to create the required routes:

get '/files/:filename/download' do |filename|
  send_file "./files/#{filename}", :filename => filename, :type => 'Application/octet-stream'
end

get '/files/:filename' do |filename|
  send_file "./files/#{filename}"
end

Let’s try to download or view our files in the browser. From the terminal window run the following to start the server:

ruby config/application.rb -p $PORT -o $IP

17. CSS

Lets add some css to our app.

Using the terminal window cd into the app folder and then do the following (again you can use the Cloud9 file explorer to create these folders)

mkdir app/assets
mkdir app/assets/css
touch app/assets/css/application.css

Lets open our newly created application.css file and add the following code:

body { padding-top: 100px; }
footer { margin-top: 100px; }
table, td, th { vertical-align: middle; border: none; color: brown; }
th { border-bottom: 1px solid #DDD; }

Let’s preview our changes in the browser. From the terminal window run the following to start the server:

ruby config/application.rb -p $PORT -o $IP

17.a Final commit to github

git add .
git commit -m "My sinatra app is finished"
git push origin master

18. The End

Congratulations you have just completed your first Sinatra app!

Next Steps

Keep in touch!! We love to hear from you.

Follow us on twitter

Sorcha

Rachelle

Rails Girls Brisbane

Read the Rails Girls Brisbane blog http://railsgirlsbne.com/

Email us: Rails Girls Brisbane

Join our Facebook Group: Rails Girls BNE Group