Padrino is a web framework, closely related to its cousin, the Sinatra web framework. It tries to offer the flexibility of development of Sinatra and a wide array of helpers, tools and add-ons of Rails.
Padrino, Italian for “godfather”, is built on top of Sinatra, so you can use all your knowledge gained from the previous Sinatra series published on this site. However, it is somewhat different, as it provides some conventions that look very similar to the ones found in the previously mentioned articles, as you will see. By the way, I will use Ruby 1.9.2 for this tutorial.
To make it easier, I’ll manage it via RVM. The steps to set up RVM, if you haven’t already, are outlined in this article by Glenn Goodrich of Rubysource fame. Once you have RVM and Ruby 1.9.2 installed, make a gemset for this application. I am calling mine “rubysource”, as you can see below:
$ rvm use 1.9.2@rubysource --create
Install Padrino
To install Padrino:
$ gem install padrino
Once done, create a new project by running:
$ padrino g project rubytoday -t cucumber -b
g is short form for generate
-t is a switch used to specify a testing framework, in this case
I’ve specified Cucumber, this also includes RSpec
-b tells Padrino to use Bundler for managing the gems
This creates a project named rubytoday.
Once you change to the rubytoday directory, if you’ve used Rails, you’ll see a somewhat similar directory structure to a Rails project:
,-- Gemfile
|-- Gemfile.lock
|-- app
| |-- app.rb
| |-- controllers
| |-- helpers
| `-- views
| `-- layouts
|-- config
| |-- apps.rb
| `-- boot.rb
|-- config.ru
|-- cucumber.yml
|-- features
| |-- add.feature
| |-- step_definitions
| | `-- add_steps.rb
| `-- support
| |-- env.rb
| `-- url.rb
|-- public
| |-- favicon.ico
| |-- images
| |-- javascripts
| `-- stylesheets
|-- spec
| |-- spec.rake
| `-- spec_helper.rb
`-- tmp
Some notes:
- the main app located under app directory
- JavaScript and stylesheets content under public directory
- Cucumber features under features directory
- RSpec folder under spec directory
Now that we have the initial bare app, it’s a good time to check it into Git. That way, you can revert to this state again should the need arise. Before we do that, open the .gitignore file and add a regular expression pattern of the backup files that your editor creates. In my case that’s Emacs and the backup files have an ending of: *~.
Then add it to Git, like so:
$ git init
$ git add .
$ git commit -m "initial commit"
$ git status</p>
<h1>On branch master</h1>
<p>nothing to commit (working directory clean)
The last command git status states that I’m currently on the main master branch. To do further work, I will create a new branch and do my work within it. Once I’m happy with it, I’ll merge it back into the master branch.
$ git checkout -b hello_world
Switched to a new branch 'hello_world'
With the command git checkout -b helloworld I created a new branch helloworld and switched to it.
The output from git branch confirms it:
$ git branch
* hello_world
master
The initial ‘*’ in front of the branch name states the branch in which I’m currently working.
Hello world in BDD
If the branch name is anything to go by, you probably guessed what the next part of the tutorial will show. The creation of the “Hello world” page.
Since this tutorial tries to teach the usage of Cucumber and Behaviour Driven Development, I’ll start by writing a feature file explaining how I wish my application to behave.
Under the features directory I open a file hello.feature and add:
Feature: hello world
I want the application to greet the world every time I use it
Scenario: greet the world
Given I visit the app
Then it should greet the world
Feature and Scenario are keywords. Feature is the main title, as it were, that briefly outlines what is being described, followed by a description. Scenario is an example of how a particular example should manifest.
There is also a file called add.feature, which isn’t needed, so I’ll remove it:
$ git rm add.feature
Then I run Cucumber, to see the output:
$ bundle exec cucumber
Using the default profile...
Feature: hello world
I want the application to greet the world every time I use it
Scenario: greet the world # features/hello.feature:4
Given I visit the app # features/hello.feature:5
Undefined step: "I visit the app" (Cucumber::Undefined)
features/hello.feature:5:in 'Given I visit the app'
Then it should greet the world # features/hello.feature:6
Undefined step: "it should greet the world" (Cucumber::Undefined)
features/hello.feature:6:in 'Then it should greet the world'
1 scenario (1 undefined)
2 steps (2 undefined)
0m0.002s
You can implement step definitions for undefined steps with these snippets:
Given /^I visit the app$/ do
pending # express the regexp above with the code you wish you had
end
Then /^it should greet the world$/ do
pending # express the regexp above with the code you wish you had
end
The Cucumber complains that it does not recognise the undefined steps. So I copy them and add them to a new file:
features/stepdefinitions/hellosteps.rb:
Given /^I visit the app$/ do
pending # express the regexp above with the code you wish you had
end
Then /^it should greet the world$/ do
pending # express the regexp above with the code you wish you had
end
When I run the Cucumber again:
$ bundle exec cucumber
Using the default profile...
Feature: hello world
I want the application to greet the world every time I use it
Scenario: greet the world # features/hello.feature:4
Given I visit the app # features/step_definitions/hello_steps.rb:1
TODO (Cucumber::Pending)
./features/step_definitions/hello_steps.rb:2:in '/^I visit the app$/'
features/hello.feature:5:in 'Given I visit the app'
Then it should greet the world # features/step_definitions/hello_steps.rb:5
1 scenario (1 pending)
2 steps (1 skipped, 1 pending)
0m0.002s
It fails on the first step and skips the second one. Since the first step doesn’t really have anything defined, I add the following:
Given /^I visit the app$/ do
visit '/'
end
and run Cucumber again:
$ bundle exec cucumber
Using the default profile...
Feature: hello world
I want the application to greet the world every time I use it
Scenario: greet the world # features/hello.feature:4
Given I visit the app # features/step_definitions/hello_steps.rb:1
Then it should greet the world # features/step_definitions/hello_steps.rb:5
TODO (Cucumber::Pending)
./features/step_definitions/hello_steps.rb:6:in '/^it should greet the world$/'
features/hello.feature:6:in 'Then it should greet the world'
1 scenario (1 pending)
2 steps (1 pending, 1 passed)
0m0.367s
And the first step in the scenario passes, but the second step is still pending. So I modify it:
Then /^it should greet the world$/ do
page.should have_content("Hello world")
end
then I run Cucumber again:
$ bundle exec cucumber
Using the default profile...
Feature: hello world
I want the application to greet the world every time I use it
Scenario: greet the world # features/hello.feature:4
Given I visit the app # features/step_definitions/hello_steps.rb:1
Then it should greet the world # features/step_definitions/hello_steps.rb:5
undefined local variable or method 'response_body' for #<Object:0x9741db0> (NameError)
./features/step_definitions/hello_steps.rb:6:in '/^it should greet the world$/'
features/hello.feature:6:in `Then it should greet the world'
Failing Scenarios:
cucumber features/hello.feature:4 # Scenario: greet the world
1 scenario (1 failed)
2 steps (1 failed, 1 passed)
0m0.205s
Which is understandable, as I haven’t created anything that can render such greeting.
To rectify that I run:
$ padrino gen controller events index
which in turn creates events.rb under app/controllers and events_controller_spec.rb under spec/app/controllers/.
If you open spec/app/controllers/events_controller_spec.rb you’ll see a default example:
require 'spec_helper'
describe "EventsController" do
before do
get "/"
end
it "returns hello world" do
last_response.body.should == "Hello World"
end
end
which coincidentally is exactly what I’m trying to describe.
When I run the spec, like so:
$ padrino rake spec
it will fail. This is to be expected as I still don’t have anything written yet. To remedy that, I add an index route to the Event controller in app/controllers/events.rb:
Rubytoday.controllers :events do
get :index, :map => "/" do
"Hello world"
end
end
To verify the route I can do it via a Rake’s task:
$ padrino rake routes
=> Executing Rake routes ...
Application: Rubytoday
URL REQUEST PATH
(:events, :index) GET /
Which confirms that I have set up the default root path to Events controller’s index method.
Then to verify it via RSpec:
$ padrino rake spec
=> Executing Rake spec ...
EventsController
returns hello world (FAILED - 1)
Failures:
1) EventsController returns hello world
Failure/Error: last_response.body.should == "Hello World"
expected: "Hello World"
got: "Hello world" (using ==)
# ./spec/app/controllers/events_controller_spec.rb:9:in `block (2 levels) in <top (required)>'
Finished in 0.28037 seconds
1 example, 1 failure
Failed examples:
rspec ./spec/app/controllers/events<em>controller</em>spec.rb:8 # EventsController returns hello world
rake aborted!
ruby -S bundle exec rspec -fs --color ./spec/app/controllers/events<em>controller</em>spec.rb failed
Tasks: TOP => spec => spec:app
(See full trace by running task with --trace)
Which fails, but if you look at the example, the only difference is that the example expected the output to be “Hello World” as opposed to “Hello world”. The error is in the spelling. Once I change that…
$ padrino rake spec
=> Executing Rake spec ...
<p>EventsController
returns hello world
<p>Finished in 0.22763 seconds
1 example, 0 failures
The example passes. Then to verify that Cucumber is passing:
$ bundle exec cucumber
Using the default profile...
<p>Feature: hello world
I want the application to greet the world every time I use it
Scenario: greet the world # features/hello.feature:4
Given I visit the app # features/step<em>definitions/hello</em>steps.rb:1
Then it should greet the world # features/step<em>definitions/hello</em>steps.rb:5
1 scenario (1 passed)
2 steps (2 passed)
0m0.402s
I should also check the output for myself, by starting the app:
$ padrino start
=> Padrino/0.10.2 has taken the stage development at http://0.0.0.0:3000
[2011-10-03 00:51:01] INFO WEBrick 1.3.1
[2011-10-03 00:51:01] INFO ruby 1.9.2 (2010-08-18) [i686-linux]
[2011-10-03 00:51:01] INFO WEBrick::HTTPServer#start: pid=16767 port=3000
and opening the URL in the browser: http://0.0.0.0:3000.
This completes the desired output and demonstrates the BDD flow:
Cucumber –> RSpec –> Code
First I wrote the feature and described the behaviour, then I wrote the example in RSpec and finally I implemented the code to fit both the feature and the example.
Now for the final step, to commit it to Git:
$ git status
# On branch hello_world
# Changed but not updated:
# (use "git add/rm <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted: features/add.feature
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# app/controllers/
# app/helpers/
# features/hello.feature
# features/step_definitions/hello_steps.rb
# spec/app/
no changes added to commit (use "git add" and/or "git commit -a")
This lists all the new content that needs to be committed to Git along with the deleted content: features/add.feature file, which I removed earlier.
$ git add .
$ git status
# On branch hello_world
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: app/controllers/events.rb
# new file: app/helpers/events_helper.rb
# new file: features/hello.feature
# new file: features/step<em>definitions/hello</em>steps.rb
# new file: spec/app/controllers/events<em>controller</em>spec.rb
#
# Changed but not updated:
# (use "git add/rm <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted: features/add.feature
#
I stage the new content and then I add it:
$ git commit -m "Implemented the 'Hello world' example"
[hello_world de9a0d4] Implemented the 'Hello world' example
5 files changed, 58 insertions(+), 0 deletions(-)
create mode 100644 app/controllers/events.rb
create mode 100644 app/helpers/events_helper.rb
create mode 100644 features/hello.feature
create mode 100644 features/step_definitions/hello_steps.rb
create mode 100644 spec/app/controllers/events_controller_spec.rb
This concludes the first tutorial in the series. In the next tutorial I will show how to merge content in Git, how to deploy it to Heroku and I’ll carry on building the rest of the app. If this is your first look at Padrino, I’d love to hear what you think in the comments below. All the code can be obtained from here: https://github.com/RubySource/simic_padrino.
Laughing cowboy punching ignorance in the face (that was stolen from my Twitter stream) aka Aleksandar Simic, tweets as @dotemacs.