Creating The Application
Start with generating a new project with the canonical padrino
command. In contrast to our "Hello World!" application (app) before, we are using new options:
$ mkdir ~/padrino-projects
$ cd ~/padrino_projects
$ padrino-gen project job-vacancy -d activerecord \
-t rspec \
-s jquery \
-e erb \
-a sqlite
Explanation of the fields commands:
- -d activerecord: We are using Active Record as the orm library (Object Relational Mapper).
- -t rspec: We are using the RSpec testing framework.
- -s jquery: Defining the JavaScript library we are using - for this app will be using the ubiquitous jQuery library.
- -e erb: We are using ERB (embedded ruby) markup for writing HTML templates. An alternative is Haml or Slim, but to keep the project as simple as possible, we stick with ERB. Feel free to use them if you like to.
- -a sqlite: Our ORMorm database adapter is SQLite. The whole database is saved in a text file.
Since we are using RSpec for testing, we will use its' built-in mock extensions rspec-mocks for writing tests later. In case you want to use another mocking library like rr or mocha, feel free to add it with the -m option.
You can use a vast array of other options when generating your new Padrino app, this table shows the currently available options:
orm
: Available options are: activerecord, couchrest, datamapper, minirecord, mongoid, mongomatic, ohm, ripple, and sequel. The command line alias is-d
.test
: Available options are: bacon, cucumber, minitest, riot, rspec, shoulda, and steak. The command line alias is-t
.script
: Available options are: dojo, extcore, jquery, mootools, prototype, and rightjs. The command line alias is-s
.renderer
: Available options are: erb, haml, liquid, and slim. The command line alias is-e
.stylesheet
: Available options are: compass, less, sass/scss. The command line alias is-c
.mock
: Available options are: mocha and rr.
The default value of each option is none. In order to use them you have to specify the option you want to use.
Besides the project
option for generating new Padrino apps, the following table illustrates the other generators available:
admin
: A very nice built-in admin dashboard.admin_page
: Creates for an existing model the CRUD operation for the admin interface.app
: You can define other apps to be mounted in your main app.controller
: A controller takes date from the models and puts them into view that are rendered.mailer
: Creating new mailers within your app.migration
: Migrations simplify changing the database schema.model
: Models describe data objects of your application.project
: Generates a completely new app from the scratch.plugin
: Creating new Padrino projects based on a template file - it's like a list of commands.
Later, when the time comes, we will add extra gems, for now though we'll grab the current gems using bundle
by running at the command line:
$ bundle install
Basic Layout
Lets design our first version of the index.html page which is the starter page our app. An early design question is: Where to put the index.html page? Because we are not working with controllers, the easiest thing is to put the index.html directly under the public folder in the project.
We are using HTML5 for the page, and add the following code into public/index.html
:
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>Start Page</title>
</head>
<body>
<p>Hello, Padrino!</p>
</body>
</html>
Plain static content - this used to be the way websites were created in the beginning of the web. Today, apps provide dynamic layout. During this chapter, we will how to add more and more dynamic parts to our app.
We can take a look at our new page by executing the following command:
$ cd job-vacancy
$ bundle exec padrino start
You should see a message telling you that Padrino has taken the stage, and you should be able to view our created index page by visiting http://localhost:3000/index.html in your browser.
You might ask "Why do we use the bundle exec
command - isn't padrino start
enough?" The reason for this is that we use bundler to load exactly those Ruby gems that we specified in the Gemfile. I recommend that you use bundle exec
for all following commands, but to focus on Padrino, I will skip this command on the following parts of the book.
You may have thought it a little odd that we had to manually requests the index.html in the URL when viewing our start page. This is because our app currently has no idea about routing. Routing is the process to recognize request URLs and to forward these requests to actions of controllers. With other words: A router is like a like vending machine where you put in money to get a coke. In this case, the machine is the router which routes your input "Want a coke" to the action "Drop a Coke in the tray".
First Controller And Routing
Lets add some basic routes for displaying our home, about, and contact-page. How can we do this? With the help of a routing controller. A controller makes data from you app (in our case job offers) available to the view (seeing the details of a job offer). Now let's create a controller in Padrino names page:
$ padrino-gen controller page
create app/controllers/page.rb
create app/helpers/page_helper.rb
create app/views/page
apply tests/rspec
create spec/app/controllers/page_controller_spec.rb
Lets take a closer look at our page-controller:
# app/controller/page.rb
JobVacancy::App.controllers :page do
# get :index, :map => '/foo/bar' do
# session[:foo] = 'bar'
# render 'index'
# end
# get :sample, :map => '/sample/url', :provides => [:any, :js] do
# case content_type
# when :js then ...
# else ...
# end
# get :foo, :with => :id do
# 'Maps to url '/foo/#{params[:id]}''
# end
# get '/example' do
# 'Hello world!'
# end
end
The controller above defines for our JobVacancy
the :page
controller with no specified routes inside the app. Let's change this and define the about, contact, and home actions:
# app/controller/page.rb
JobVacancy:.App.controllers :page do
get :about, :map => '/about' do
render :erb, 'about'
end
get :contact , :map => '/contact' do
render :erb, 'contact'
end
get :home, :map => '/' do
render :erb, 'home'
end
end
We will go through each line:
JobVacancy::App.controller :page
- Define the namespace page for our JobVacancy app. Typically, the controller name will also be part of the route.do ... end
- This expression defines a block in Ruby. Think of it as a method without a name, also called anonymous functions, which is passed to another function as an argument.get :about, :map => '/about'
- The HTTP command get starts the declaration of the route followed by the about action (as a symbolsymbol), and is finally mapped to the explicit URL /about. When you start your server withbundle exec padrino s
and visit the URL http://localhost:3000/about, you can see the rendered output of this request.render :erb, 'about'
- This action tells us that we want to render the erb file about for the corresponding controller which ispage
in our case. This file is actually located atapp/views/page/about.erb
file. Normally the views are placed underapp/views/<controller-name>/<action-name>.<ending>
. Instead of using an ERB templates, you could also use:haml
, or another template engine. You can even completely drop the rendering option and leave the matching completely for Padrino.
symbol. Unlike strings, symbols of the same name are initialized and exist in memory only once during a session of ruby. This makes your programs more efficient. ↩
Call the following command to see all defined routes for your application:
$ padrino rake routes
=> Executing Rake routes ...
Application: JobVacancy
URL REQUEST PATH
(:page, :about) GET /about
(:page, :contact) GET /contact
(:page, :home) GET /
Application Template
Although we are now able to put content (albeit static) on our site, it would be nice to have some sort of basic styling on our web page. First we need to generate a basic template for all pages we want to create:
<%# app/views/layouts/application.erb %>
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>Job Vacancy - find the best jobs</title>
</head>
<body>
<%= yield %>
</body>
</html>
Let's see what is going on with the <%= yield %>
line. At first you may ask what does the <>
symbols mean. They are indicators that you want to execute Ruby code to fetch data that is put into the template. Here, the yield
command will put the content of the called page, like about.erb or contact.erb, into the template.
CSS Design Using Twitter Bootstrap
The guys at Twitter were kind enough to make their CSS framework Twitter Bootstrap available for everyone to use.
Padrino itself also provides built-in templates for common tasks done on web app. These padrino-recipes help you saving time by not reinventing the wheel. Thanks to @arthur_chiu, we use his bootstrap-plugin by executing:
$ padrino-gen plugin bootstrap
apply https://github.com/padrino/padrino-recipes/raw/master/plugins/...
create public/stylesheets/bootstrap.css
...
Next we need to include the style sheet in our app template for the whole app:
<%# app/views/layouts/application.erb %>
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>Job Vacancy - find the best jobs</title>
<%= stylesheet_link_tag 'bootstrap', 'bootstrap-theme' %>
<%= javascript_include_tag 'bootstrap.min', 'jquery', 'jquery-ujs' %>
</head>
<body>
<%= yield %>
</body>
</html>
The stylesheet_link_tag
points to the bootstrap.min.css in you app public/stylesheets directory and will automatically create a link to this stylesheet. The javascript_include_tag
does the same as stylesheet_link_tag
for your JavaScript files in the public/javascripts directory.
Using Sprockets to Manage the Asset Pipeline
Sprockets are a way to manage serving your assets like CSS, and JavaScript compiling all the different files in one summarized file for each type. To take advantage to use a preprocessor to write your assets with Sass, CoffeeScript, or less.
To implement Sprockets in Padrino there the following strategies:
- rake-pipeline: Define filters that transforms directory trees.
- grunt: Set a task to compile and manage assets in JavaScript.
- sinatra-assetpack: Let's you define you assets transparently in Sinatra.
- padrino-sprockets: Integrate sprockets with Padrino in the Rails way.
We are using the padrino-sprockets gem. Let's add it to our Gemfile (don't forget to run bundle install
):
# Gemfile
gem 'padrino-sprockets', :require => ['padrino/sprockets'],
:git => 'git://github.com/nightsailer/padrino-sprockets.git'
Next we need to move all our assets from the public folder in the assets folder:
$ cd job-vacancy
$ mkdir -p app/assets
$ cd public
$ mv -v fonts images javascripts stylesheets ../app/assets
Now we have to register Padrino-Sprockets in this application:
# app/app.rb
module JobVacancy
class App < Padrino::Application
...
register Padrino::Sprockets
sprockets
...
end
end
Next we need create an application.css file and add the following to determine the order of the loaded CSS files in app/assets/stylesheets/application.css
:
/* app/assets/stylesheets/application.css */
/*
* This is a manifest file that'll automatically include all the stylesheets ...
* ...
*
*= require_self
*= require bootstrap
*= require bootstrap-theme
*= require site
*/
First we are loading the bootstrap
default css, then bootstrap-theme
, and finally our customized site
CSS. The require_self
loads the file itself, to define the order that the files are loaded. This is helpful if you want to check the order of the loaded CSS as a comment above your application without ever have to look into the source of it.
Next let's have a look into our JavaScript file app/assets/javascript/application.js
:
/* app/assets/javascript/application.js */
// This is a manifest file that'll be compiled into including all the files ...
// ...
//
//= require_tree .
The interesting thing here is the require_tree .
option. This option tells Sprockets to include all JavaScript files in the assets folder with no specific order.
Now, we can clean up the include statements in our application template:
<%# app/views/layouts/application.erb %>
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>Job Vacancy - find the best jobs</title>
<%= stylesheet_link_tag '/assets/application' %>
<%= javascript_include_tag '/assets/application' %>
</head>
Now we want to enable compression for our CSS and JavaScript files. For CSS compression Padrino Sprockets is using YUI compressor and for JS compression the Uglifier. We need to add these these Gems in our Gemfiles
:
# Gemfile
...
gem 'padrino-sprockets', :require => 'padrino/sprockets',
:git => 'git://github.com/nightsailer/padrino-sprockets.git'
gem 'uglifier', '2.1.1'
gem 'yui-compressor', '0.9.6'
...
And finally we need to enable minifying in our production environment:
# app/app.rb
module JobVacancy
class App < Padrino::Application
...
register Padrino::Sprockets
sprockets :minify => (Padrino.env == :production)
...
end
end
Navigation
Next we want to create the top-navigation for our app. We already implemented the page controller with the relevant actions. All we need is to put links to them in a navigation header for our basic layout.
<%# app/views/layouts/application.erb %>
<!DOCTYPE html>
<html lang="en-US">
<head>
<title>Job Vacancy - find the best jobs</title>
<%= stylesheet_link_tag 'bootstrap', 'bootstrap-theme' %>
<%= javascript_include_tag 'bootstrap.min', 'jquery', 'jquery-ujs' %>
<%= stylesheet_link_tag '/stylesheets/site.css' %>
</head>
<body>
<div class="container">
<div class="row">
<div class="span12 offset3">
<span id="header">Job Vacancy Board</span>
</div>
<div class="row">
<nav id="navigation">
<div class="span2 offset4">
<%= link_to 'Home', url(:page, :home) %>
</div>
<div class="span2">
<%= link_to 'About', url(:page, :about) %>
</div>
<div class="span2">
<%= link_to 'Contact', url(:page, :contact) %>
</div>
</nav>
</div>
<div class="row">
<div class="span9 offset3 site">
<%= yield %>
</div>
</div>
</div>
</div>
</body>
Explanation of the new parts:
- link_to - Is a helper for creating links. The first argument is the name for the link and the second is for the URL (href) to which the link points to.
- url (or
url_for
) - This helper return the link which can be used as the second parameter for thelink_to
function. It specifies the <:controller>, <:action> which will be executed. You can use in your helper in your whole app to create clean and encapsulated URLs.
Now that the we provide links to other parts of the app, lets add some sugar-candy styling:
/* app/assets/stylesheets/site.css */
body {
font: 18.5px Palatino, 'Palatino Linotype', Helvetica, Arial, Verdana,
sans-serif;
text-align: justify;
}
#header {
font-family: Lato;
font-size: 40px;
font-weight: bold;
}
#navigation {
padding-top: 20px;
}
h1 {
font-family: Lato;
font-size: 30px;
margin-bottom: 20px;
}
.site {
padding: 20px;
line-height: 1.8em;
}
I will not explain anything at this point about CSS. If you still don't know how to use it, please go through w3c school css tutorial. Since we are using the asset pipeline, we don't need to register our new CSS file in views/application.erb
- now you will understand why we did this.
Writing Tests
Our site does not list static entries of job offers that you write, but other users will be allowed to post job offers from the Internet to our site. We need to add this behavior to our site. To be on the sure side, we will implement this behavior by writing tests first, then the code. We use the RSpec testing framework for this.
Remember when we created the page-controller with padrino-gen controller page
? Thereby, Padrino created a corresponding spec file spec/app/controller/page_controller_spec.rb
which has the following content:
# spec/app/controller/page_controller_spec.rb
require 'spec_helper'
RSpec.describe "/page" do
pending "add some examples to #{__FILE__}" do
before do
get "/page"
end
it "returns hello world" do
expect(last_response.body).to eq "Hello World"
end
end
end
Let's update that file and write some basic tests to make sure that everything is working as expected. Replace the specs in the file with the following code:
# spec/app/controller/page_controller_spec.rb
require 'spec_helper'
RSpec.describe "PageController" do
describe "GET #about" do
it "renders the :about view" do
get '/about'
expect(last_response).to be_ok
end
end
describe "GET #contact" do
it "renders the :contact view" do
get '/contact'
expect(last_response).to be_ok
end
end
describe "GET #home" do
it "renders :home view" do
get '/'
expect(last_response).to be_ok
end
end
end
Let's explain the interesting parts:
spec_helper
- Is a file to load commonly used functions to setup the tests.describe block
- This block describes the context for our tests. Think of it as way to group related tests.get ...
- This command executes a HTTP GET to the provided address.last_response
- The response object returns the header and body of the HTTP request.
Now let's run the tests with rspec spec/app/controllers/page_controller_spec.rb
and see what's going on:
...
Finished in 0.21769 seconds
3 examples, 0 failures
Cool, all tests passed! We didn't exactly use behavior-driven development until nownote.
note. Note: It's possible your tests did not pass due to a Padrino error in which a comma ( , ) was omitted during the initial app generation that looks something like 'NameError: undefined local variable' check yourspec_helper.rb
file and make sure the following matches:def app(app = nil, &blk)
, please note the comma right after nil. ↩
\begin{aside} \heading{Red-Green Cycle}
In behavior-driven development (BDD) it is important to write a failing test first and then the code that satisfies the test. The red-green cycle represents the colors that you will see when executing these test: Red first, and then beautiful green. But once your code passes the tests, take yet a little more time to refactor your code. This little mind shift helps you a lot to think more about the problem and how to solve it. The test suite is a nice by product too.
\end{aside}