100% found this document useful (2 votes)
166 views

Padrino Preview

padrino sample app for beginners

Uploaded by

Mo Fo
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (2 votes)
166 views

Padrino Preview

padrino sample app for beginners

Uploaded by

Mo Fo
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 53

Padrino - The Elegant Ruby Web Framework

A practical approach to learn crafting web applications

Matthias Gnther

Padrino - The Elegant Ruby Web Framework


A practical approach to learn crafting web applications Matthias Gnther
This book is for sale at http://leanpub.com/padrino This version was published on 2013-04-10 This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and many iterations to get reader feedback, pivot until you have the right book and build traction once you do.

2011 - 2013 Matthias Gnther

Tweet This Book!


Please help Matthias Gnther by spreading the word about this book on Twitter! The suggested hashtag for this book is #padrino. Find out what other people are saying about the book by clicking on this link to search for this hashtag on Twitter: https://twitter.com/search/#padrino

Test test what will happen with this text field???

Contents
1 Introduction . . . . . . . . . . . . . Basics And Tools . . . . . . . . . . . Ruby Knowledge . . . . . . . . . Installing Ruby With rbenv . . . . . . ruby-build . . . . . . . . . . . . Compiling Ruby On Your Own . Hello world . . . . . . . . . . . . . . Folder structure of a Padrino app Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2 2 3 3 4 6 9 11 12 13 14 15 17 18 19 20 23 23 28 29 31 34 39 39 40 44 44 46

2 Job Vacancy Application . . . . . . . . . . . . . . . . . . . Creating a new application . . . . . . . . . . . . . . . . . . . Basic Layout Template . . . . . . . . . . . . . . . . . . . First Controller And Routing . . . . . . . . . . . . . . . App Template With ERB . . . . . . . . . . . . . . . . . CSS design using Twitter bootstrap . . . . . . . . . . . . Navigation . . . . . . . . . . . . . . . . . . . . . . . . . Writing Tests . . . . . . . . . . . . . . . . . . . . . . . . Creation Of The Models . . . . . . . . . . . . . . . . . . . . User Model . . . . . . . . . . . . . . . . . . . . . . . . . Job Offer Model . . . . . . . . . . . . . . . . . . . . . . Creating Connection Between User And Job Offer Model Testing our associations in the console . . . . . . Testing our app with RSpec + Factory Girl . . . . User Login and Registration . . . . . . . . . . . . . . . . . . Extending our User Model . . . . . . . . . . . . . . . . . Validating attributes . . . . . . . . . . . . . . . . . . . . Users Controller . . . . . . . . . . . . . . . . . . . . . . Signup Form . . . . . . . . . . . . . . . . . . . . User Controller Signup Actions . . . . . . . . . .

1 Introduction
Why another book about how to develop an application (app) in Rails? But wait, this book should give you a basic introduction how to develop a web app with Padrino. Padrino is The Elegant Ruby Web Framework. Padrino is based upon Sinatra, which is a simple a Domain Specific Language for quickly creating web apps in Ruby. When writing Sinatra apps many developers miss some of the extra conveniences that Rails offers, this is where Padrino comes in as it provides many of these while still staying true to Sinatras philosophy of being simple and lightweight. In order to understand the mantra of the Padrino webpage: Padrino is a full-stack ruby framework built upon Sinatra you have to read on.
http://www.padrinorb.com/ http://www.sinatrarb.com/

Introduction

Basics And Tools


I wont tell you which operating system you should use - there is an interesting discussion on hackernews. I leave it free for the reader of this book which to use, because basically you are reading this book to learn Padrino. To actually see a running padrino app, you need a web browser of your choice. For writing the application, you can either use an Integrated Development Environment (IDE) or with a plain text editor. Nowadays there are a bunch of Integrated Development Environments (IDEs) out there: RubyMine by JetBrains - commercial, available for all platforms Aptana RadRails - free, available for all platforms Here is a list of plain text editors which are a popular choice among Ruby developers: Emacs - free, available for all platforms. Gedit - free, available for Linux. Notepad++ - free, available only for Windows. SublimeText - commercial, available for all platform. Textmate - commercial, available only for Mac. Vim - free, available for all platform.

All tools have their strengths and weaknesses. Try to find the software that works best for you. The main goal is that you are comfortable with it because you will spend a lot of time with it.

Ruby Knowledge
For any non-Ruby people, I strongly advise you to check out one of these books and learn the basics of Ruby before continuing here. Programming Ruby - the standard book on Ruby. Poignant Guide to Ruby - written by the nebulous programmer why the lucky stiff in a entertaining and educational way. In this book, I assume readers having Ruby knowledge and will not be explaining every last detail. I will, however, explain Padrino-specific coding techniques.
http://news.ycombinator.com/item?id=3786674 http://www.jetbrains.com/ruby/ http://www.aptana.com/products/radrails http://www.gnu.org/s/emacs/ http://projects.gnome.org/gedit/ http://notepad-plus-plus.org/ http://www.sublimetext.com http://macromates.com/ http://www.vim.org/ http://pragprog.com/book/ruby3/programming-ruby-1-9 http://www.scribd.com/doc/8545174/Whys-Poignant-Guide-to-Ruby http://en.wikipedia.org/wiki/Why_the_lucky_stiff

Introduction

Installing Ruby With rbenv


Instead of using the build-in software package for Ruby of your operating system, we will use rbenv which lets you switch between multiple versions of Ruby. First, we need to use git to get the current version of rbenv:
$ cd $HOME $ git clone git://github.com/sstephenson/rbenv.git .rbenv

In case you shouldnt want to use git, you can also download the latest version as a zip file from Github. You need to add the directory that contains rbenv to your $PATHenvironment variable. If you are on Mac, you have to replace .bashrc with .bash_profile in all of the following commands):
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc

To enable auto completion for rbenv commands, we need to perform the following command:
$ echo 'eval "$(rbenv init -)"' >> ~/.bashrc

Next, we need to restart our shell to enable the last changes:


$ exec $SHELL

Basically, there are two ways to install different versions of Ruby: You can compile Ruby on your own and try to manage the versions and gems on your own, or you use a tool that helps you.

ruby-build
Because we dont want to download and compile different Ruby versions on our own, we will use ruby-build plugin for rbenv:
$ mkdir ~/.rbenv/plugins $ cd ~/.rbenv/plugins $ git clone git://github.com/sstephenson/ruby-build.git

If you now run rbenv install you can see all the different Ruby version you can install and use for different Ruby projects. We are going to install ruby 1.9.3-p286:
https://github.com/sstephenson/rbenv/ http://git-scm.org http://github.com https://github.com/sstephenson/ruby-build

Introduction

$ rbenv install 1.9.3-p286

This command will take a couple of minutes, so its best to grab a Raider, which is now known as Twix. After everything runs fine, you have to run rbenv rehash to rebuild the internal rbenv libraries. The last step is to makeRuby 1.9.3-p286 the current executable on your machine:
$ rbenv global 1.9.3-p286

Check that the correct executable is active by exexuting ruby -v. The output should look like:
$ 1.9.3-p286 (set by /home/.rbenv/versions)

Now you are a rookie Ruby Rogue.

Compiling Ruby On Your Own


If you want to compile a different version of Ruby that is not offered with rbenv, then make sure you have the following packages installed for your os: make, g++, wget and unzip. Continue to select your preferred Ruby versions and then download the appropriate package:
$ cd ~/.rbenv/versions $ wget http://ftp.ruby-lang.org/pub/ruby/ruby-1.9.3-p286.zip

Go to the directory .rbenv/versions where you will find the downloaded file. Next unzip the file:
$ unzip ruby-1.9.3-p286.zip

Configure the compilation and perform the installation:


$ $ $ $ cd ~/.rbenv/versions ./configure --prefix=$HOME/.rbenv/versions/ruby-1.9.3.p286 make make install

Following these steps, you gain knowledge about the whole process of configuration and compilation of custom Ruby versions. However, this doesnt always work:

http://en.wikipedia.org/wiki/Twix http://rubyrogues.com/ http://ftp.ruby-lang.org/pub/ruby/

Introduction

$ ruby -v Segmentation fault

If you want to be on the safe side, then use ruby-build.

Introduction

Hello world
The basic layout of our application is displayed on the following image application:

Figure 1-1. Start page of the app

It is possible that you know this section from several tutorials, which makes you even more comfortable with your first program. Now, get your hands dirty and start coding. First of all we need to install the padrino gem. We are using the last stable version of Padrino (during the release of this book it is version 0.10.7). Execute this command.
$ gem install padrino

This will install all necessary dependencies and makes you ready to get started. Now we will generate a fresh new Padrino project:
$ padrino generate project hello-world

Lets go through each part of this command: padrino generate: Tells Padrino to execute the generator with the specified options. The options can be used to create other components for your app like a mailing system or an admin panel to manage your database entries. We will handle these things in a further chapter. A shortcut for generate is g which we will use in all following examples. project: Tells Padrino to generate a new app. hello-world: The name of the new app, which is also the folder name. The console output should looks like the following:

Introduction

create create create create create create create create create create create create create create create create create create skipping skipping skipping skipping applying apply insert skipping identical

.gitignore config.ru config/apps.rb config/boot.rb public/favicon.ico public/images public/javascripts public/stylesheets tmp .components app app/app.rb app/controllers app/helpers app/views app/views/layouts Gemfile orm component... test component... mock component... script component... haml (renderer)... renderers/haml Gemfile stylesheet component... .components

================================================================= hello-world is ready for development! ================================================================= $ cd ./hello-world $ bundle install =================================================================

The last line in the console output tells you the next steps you have to perform. Before we are going to start coding our app, we need some sort of package managing for Ruby gems. Ruby has a nice gem manager called bundler which installs all necessary gems in the versions you would like to have for your project. This makes it very easy for other developers to work with your project even after years. The Gemfile declares the gems that you want to install. Bundler takes the content of the Gemfile and will install everything declared inside this file. To install bundler, execute the following command and check the console output:
http://gembundler.com/ http://gembundler.com/gemfile.html

Introduction

$ gem install bundler Fetching: bundler-1.2.3.gem (100%) Successfully installed bundler-1.2.3 1 gem installed

Now we have everything to run the bundle command to install our dependencies:
$ cd hello-world $ bundle Fetching gem metadata from http://rubygems.org/......... Using rake (10.0.3) Using i18n (0.6.1) Using multi_json (1.5.0) Using activesupport (3.2.9) Using bundler (1.2.3) Using haml (3.1.7) Using rack (1.4.1) Using url_mount (0.2.1) Using http_router (0.10.2) Using mime-types (1.19) Using polyglot (0.3.3) Using treetop (1.4.12) Using mail (2.3.3) Using rack-protection (1.3.2) Using tilt (1.3.3) Using sinatra (1.3.3) Using thor (0.15.4) Using padrino-core (0.10.7) Using padrino-helpers (0.10.7) Using padrino-admin (0.10.7) Using padrino-cache (0.10.7) Using padrino-gen (0.10.7) Using padrino-mailer (0.10.7) Using padrino (0.10.7) Using sinatra-flash (0.3.0) Your bundle is complete! Use `bundle show [gemname]` to see where a bundl\ ed gem is installed.

Lets open the file app/app.rb (think of it as the root controller of your app) and insert the following before the last end in the file:

Introduction

get "/" do "Hello World!" end

Now run the app with:


$ padrino start

Instead of writing start, we can also use the alias s. Now, fire up your browser with the URL http://localhost:3000 and see the Hello World Greeting being printed. Congratulations! Youve built your first Padrino app.

Folder structure of a Padrino app


Navigating through the various parts of a project is essential. Thus we will go through the basic file structure of the hello-world project. The app consists of the following parts:
|-|-|-| | | | | |-| | |-|-| | | | `-Gemfile Gemfile.lock app |-- app.rb |-- controllers |-- helpers `-- views `-- layouts config |-- apps.rb `-- boot.rb config.ru public |-- favicon.ico |-- images |-- javascripts `-- stylesheets tmp

We will go through each part. Gemfile: The place where you put all the necessary gems for your project. Bundle takes the content of this file and installs all the declared dependencies inside this file. Gemfile.lock: This is a file generated by Bundler after you have run bundle install within your project. It is a snapshot of all the gems and their versions that have been installed.

Introduction

10

app: Contains the executable files of your project with controllers, helpers, and views of your app. app.rb: The primary configuration file of your application. controller: The controllers make the model data available to the view. They define the URL routes that are callable in your app and defines the actions that are triggered by requests. helper: Helpers are small snippet of code that can be called in your views to help you to prevent repetition - following the DRY (Dont Repeat Yourself) principle. views: Contains the templates that are filled with model data and rendered by a controller. config: General settings for the app, including hooks (explained later) that should be performed before or after the app is loaded, setting the environment (e.g. production, development, test) and mounting other apps within the existing app under different subdomains. apps.rb: Allows you to configure a compound app that consists of several smaller apps. Each app has his own default route form which requests will be handled by that app. boots.rb: Basic settings for your app which will be run when you start the app. config.ru: Contains the complete configuration options of the app, such as which port the app listens to, whenever it uses other Padrino apps as middleware and more. This file will be used when Padrino is started from the command line. public: Folder where you put static resources like images folder, JavaScript files, and style sheets. tmp: This directory holds temporary files for intermediate processing.

Introduction

11

Conclusion
We have covered a lot of stuff in this chapter: installing the Padrino gem, finding the right tools to manage different Ruby versions, and creating our first Padrino app. Now it is time to jump into a real project!

2 Job Vacancy Application


There are more IT jobs out there than there are skilled people available. It would be great if we could have the possibility to offer a platform where users can easily post new jobs vacancies to recruit people for their company. Now our job is to build this software using Padrino. We will apply K.I.S.S principle to obtain a very easy and extensible design. First, we are going to create the app file and folder structure. Then we are adding feature by feature until the app is complete. First, we will take a look at the basic design of our app. Afterwards, we will implement one feature at a time.
Is an acronym for Keep it simple, stupid.

12

Job Vacancy Application

13

Creating a new 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:
$ $ $ a mkdir ~/padrino-projects cd ~/padrino_projects padrino g project job-vacancy -d activerecord -t rspec -s jquery -e erb -\ sqlite

Explanation of the fields commands: g: Is shortcut for generate. -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 ORM[orm] database adapter is sqlite. It is easy to install because 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:
Component orm test script renderer stylesheet mock Default none none none haml none none Aliases -d -t -s -e -c -m Options mongoid, activerecord, datamapper, couchrest, mongomatic, ohm, ripple, sequel bacon, shoulda, cucumber, testspec, riot, rspec, minitest prototype, rightjs, jquery, mootools, extcore, dojo erb, haml, slim, liquid sass, less, scss, compass rr, mocha

https://rubygems.org/gems/activerecord https://github.com/dchelimsky/rspec/wiki/get-in-touch http://jquery.com/ http://ruby-doc.org/stdlib-1.9.3/libdoc/erb/rdoc/ERB.html http://haml.info/ http://slim-lang.com/ http://www.sqlite.org/ https://github.com/rspec/rspec-mocks https://rubygems.org/gems/rr http://gofreerange.com/mocha/docs/

Job Vacancy Application

14

Besides the project option for generating new Padrino apps, the following table illustrates the other generators available:
Option project app mailer controller model migration plugin admin admin_page Description Generates a completely new app from the scratch. You can define other apps to be mounted in your main app. Creating new mailers within your app. A controller takes date from the models and puts them into view that are rendered. Models describe data objects of your application. Migrations simplify changing the database schema. Creating new Padrino projects based on a template file - its like a list of commands. which create your new app. A very nice built-in admin dashboard. Creates for an existing model the CRUD operation for the admin interface

Later, when the time comes, we will add extra gems, for now though well grab the current gems using bundle by running at the command line:
$ bundle install

Basic Layout Template


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>

Explanation of the parts: <!DOCTYPE html> - The document type tells the browser which HTML version should be used for rendering the content correctly. <head>...</head> - Specifying meta information like title, description, and other things, this is also the place to where to add CSS and JavaScript files.
http://en.wikipedia.org/wiki/HTML5

Job Vacancy Application

15

<body>...</body> - In this section the main content of the page is displayed. 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 se 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. But hey, you might ask Why do we use the bundle exec command - isnt just 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 requeste 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 lets create a controller in Padrino names page:
$ padrino g controller page

The output of this command is:


create create create apply create app/controllers/page.rb app/helpers/page_helper.rb app/views/page tests/rspec spec/app/controllers/page_controller_spec.rb

(If you have questions about the output above, please drop me a line - I think it is so clear that it doesnt need any explanation about it.) Lets take a closer look at our page-controller:
http://localhost:3000/index.html

Job Vacancy Application

16

# app/controller/page.rb JobVacancy.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. Lets change this and define the about, contact, and home actions:
# app/controller/page.rb JobVacancy.controllers :page do get :about, :map => '/about' do render :erb, 'page/about' end get :contact , :map => "/contact" do render :erb, 'page/contact' end get :home, :map => "/" do render :erb, 'page/home' end end

We will go through each line:

Job Vacancy Application

17

JobVacancy.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 (in the form of a Ruby symbol ), and is finally mapped to the explicit URL /about. When you start your server with bundle exec padrino s and visit the URL http.//localhost:3000/about, you can see the rendered output of this request. render :erb, 'page/about' - This action tells us that we want to render an the erb file page/about. This file is actually located at app/views/page/about.erb file. Normally the views are placed under *app/views/ To see what routes you have defined for your app just call padrino rake routes:
$ padrino rake routes => Executing Rake routes ...

Application: JobVacancy URL REQUEST (:page, :about) GET (:page, :contact) GET (:page, :home) GET

PATH /about /contact /

This command crawls through your app looking for delicious routes and gives you a nice overview about URL, REQUEST, and PATH.

App Template With ERB


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. Lets 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>

Job Vacancy Application

18

Lets 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. It is available from Github at public repository on Github. Padrino itself also provides built-in templates for common tasks done on web app. These padrinorecipes 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/boot\ strap_plugin.rb create public/stylesheets/bootstrap.css create public/stylesheets/bootstrap-responsive.css create public/javascripts/bootstrap.js create public/javascripts/bootstrap.min.js create public/images/glyphicons-halflings.png create public/images/glyphicons-halflings-white.png

Next we need to include the style sheet in our app template for the whole app:
<!DOCTYPE html> <html lang="en-US"> <head> <title>Job Vacancy - find the best jobs</title> <%= stylesheet_link_tag 'bootstrap', 'bootstrap-responsive' %> <%= 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/javascript directory. TBD Add section how to integrate asset pipeline
https://github.com/twitter/bootstrap/ https://github.com/padrino/padrino-recipes http://twitter.com/#!/arthur_chiu https://github.com/padrino/padrino-recipes/blob/master/plugins/bootstrap_plugin.rb

Job Vacancy Application

19

Navigation
Next we want to create the top-navigation for our app. So 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.
<!DOCTYPE html> <html lang="en-US"> <head> <title>Job Vacancy - find the best jobs</title> <%= stylesheet_link_tag 'bootstrap', 'bootstrap-responsive' %> <%= 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_for(:page, :home) %> </div> <div class="span2"> <%= link_to 'About', url_for(:page, :about) %> </div> <div class="span2"> <%= link_to 'Contact', url_for(: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.

Job Vacancy Application

20

url_for - This helper return the link which can be used as the second parameter for the link_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, sa\ ns-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 dont know how to use it, please go through w3c school css tutorial. Since we are using the asset pipeline, we dont 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
http://www.w3schools.com/css/default.asp

Job Vacancy Application

21

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 g controller page? Thereby, Padrino created a corresponding spec file spec/app/controller/page_controller_spec.rb which has the following content:
require 'spec_helper' describe "PageController" do before do get "/" end it "returns hello world" do last_response.body.should == "Hello World" end end

Lets 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:
require 'spec_helper' describe "PageController" do describe "GET #about" do it "renders the :about view" do get '/about' last_response.should be_ok end end describe "GET #contact" do it "renders the :contact view" do get '/contact' last_response.should be_ok end end describe "GET #home" do it "renders :home view" do
http://rspec.info/

Job Vacancy Application

22

get '/' last_response.should be_ok end end end

Lets 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 lets run the tests with rspec spec/app/page_controller_spec.rb and see whats going on:
... Finished in 0.21769 seconds 3 examples, 0 failures

Cool, all tests passed! We didnt exactly use behavior-driven development until now, but will do so in the next parts. 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.

Job Vacancy Application

23

Creation Of The Models


User Model
There are many different ways how to develop a user entity for your system. A user in our system will have an unique identification number id, a name, and an email. You can use commands on the command-line to create models too:
$ padrino g model user name:string email:string apply apply create create create orms/activerecord tests/rspec models/user.rb spec/models/user_spec.rb db/migrate/001_create_users.rb

Wow, it created a quite a bunch of files for us. Lets examine each of them: user.rb
# models/user.rb class User < ActiveRecord::Base end

All we have is an empty class which inherits from ActiveRecord::Base. ActvieRecord provides a simple object-relational-mapper from our models to corresponding database tables. You can also define relations between models through associations. spec/models/user_spec.rb
# models/user.rb require 'spec_helper' describe "User Model" do let(:user) { User.new } it 'can be created' do user.should_not be_nil end end

As you can see, the generator created already a test for us, which basically checks if the model can be created. What would happen if you run the tests for this model? Let the code speak of its own and run the tests, thats what they are made for after all:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html

Job Vacancy Application

24

$ rspec spec/models User Model can be created (FAILED - 1) Failures: 1) User Model can be created Failure/Error: let(:user) { User.new } ActiveRecord::StatementInvalid: Could not find table 'users' # ./spec/models/user_spec.rb:4:in `new' # ./spec/models/user_spec.rb:4:in `block (2 levels) in <top (required)\ >' # ./spec/models/user_spec.rb:6:in `block (2 levels) in <top (required)\ >' Finished in 0.041 seconds 1 example, 1 failure Failed examples:

Executing the test resulted in an error. However, it very explicitly told us the reason: The user table does not exist yet. And how do we create one? Here, migrations enter the stage. Migrations helps you to change the database in an ordered manner. Lets have a look at our first migration:
db/migrate/001_create_users.rb class CreateUsers < ActiveRecord::Migration def self.up create_table :users do |t| t.string :name t.string :email t.timestamps end end def self.down drop_table :users end end

This code will create a users table with the name and email attributes. The id attribute will be created automatically unless you specify to use a different attribute as the unique key to a

Job Vacancy Application

25

database entry. By the way, the convention to name tables of models in the plural form comes from Ruby On Rails. Now we need to run this migration:
$ padrino rake ar:migrate => Executing Rake ar:migrate ... DEBUG - (0.1ms) select sqlite_version(*) DEBUG - (143.0ms) CREATE TABLE "schema_migrations" ("version" varchar(2\ 55) NOT NULL) DEBUG - (125.2ms) CREATE UNIQUE INDEX "unique_schema_migrations" ON "sc\ hema_migrations" ("version") DEBUG - (0.2ms) SELECT "schema_migrations"."version" FROM "schema_migra\ tions" INFO - Migrating to CreateUsers (1) DEBUG - (0.1ms) begin transaction ...

Since we are working in the development environment, Padrino automatically created the development database for us:
$ ls db/ job_vacancy_development.db

job_vacancy_test.db

migrate

schema.rb

Now lets start sqlite3, connect to the database, and see if the users table was created properly:
$ sqlite3 db/job_vacancy_development.db SQLite version 3.7.13 2012-06-11 02:05:22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .tables schema_migrations users sqlite> .schema users CREATE TABLE "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "nam\ e" varchar(255), "email" varchar(255), "created_at" datetime NOT NULL, "upd\ ated_at" datetime NOT NULL); sqlite> .exit

Lets have a look on the config/database.rb file to understand more about the different databases:

http://rubyonrails.org/ http://www.sqlite.org/

Job Vacancy Application

26

ActiveRecord::Base.configurations[:development] = { :adapter => 'sqlite3', :database => Padrino.root('db', 'job_vacancy_development.db') } ActiveRecord::Base.configurations[:production] = { :adapter => 'sqlite3', :database => Padrino.root('db', 'job_vacancy_production.db') } ActiveRecord::Base.configurations[:test] = { :adapter => 'sqlite3', :database => Padrino.root('db', 'job_vacancy_test.db') }

As you can see, each of the different environments development, production, and test have their own database. Letss be sure that all databases are created:
$ padrino rake ar:create:all bundle exec padrino rake ar:create:all => Executing Rake ar:create:all ... /home/elex/Dropbox/git-repositorie/job-vacancy/db/job_vacancy_development.d\ b already exists /home/helex/Dropbox/git-repositorie/job-vacancy/db/job_vacancy_development.\ db already exists /home/helex/Dropbox/git-repositories/job-vacancy/db/job_vacancy_production.\ db already exists /home/helex/Dropbox/git-repositories/job-vacancy/db/job_vacancy_test.db alr\ eady exists /home/helex/Dropbox/git-repositories/job-vacancy/db/job_vacancy_test.db alr\ eady exists

Alright, now we are ready to re-execute the tests again.


$ rspec spec/models User Model can be created (FAILED - 1) Failures: 1) User Model can be created Failure/Error: let(:user) { User.new } ActiveRecord::StatementInvalid:

Job Vacancy Application

27

Could not find table 'users' # ./spec/models/user_spec.rb:4:in `new' # ./spec/models/user_spec.rb:4:in `block (2 levels) in <top (required)\ >' # ./spec/models/user_spec.rb:6:in `block (2 levels) in <top (required)\ >' Finished in 0.04847 seconds 1 example, 1 failure Failed examples: rspec ./spec/models/user_spec.rb:5 # User Model can be created

But why are the tests still failing? Because the migration for the user table was not executed for the test environment. Lets fix this with the following command:
$ padrino rake ar:migrate -e test => Executing Rake ar:migrate ... == CreateUsers: migrating ================================================\ ==== -- create_table(:users) -> 0.0030s == CreateUsers: migrated (0.0032s) =======================================\ ====

Finally the test passes:


$ rspec spec/models User Model can be created Finished in 0.05492 seconds 1 example, 0 failures

How can we run all the tests in our application and see if everything is working? Just execute padrino rake spec to run all tests in the spec/ folder:

Job Vacancy Application

28

$ padrino rake spec => Executing Rake spec ... /home/helex/.rbenv/versions/1.9.3-p286/bin/ruby -S rspec ./spec/models/user\ _spec.rb -fs --color User Model can be created Finished in 0.05589 seconds 1 example, 0 failures /home/helex/.rbenv/versions/1.9.3-p286/bin/ruby -S rspec ./spec/app/control\ lers/page_controller_spec.rb -fs --color PageController GET #about renders the :about view GET #contact renders the :contact view GET #home renders the :home view Finished in 0.20325 seconds 3 examples, 0 failures

This is very handy to make sure that you didnt broke anything in the existing codebase when you are working on a next feature. Run these regression tests frequently and enjoy it to see your app growing feature by feature.

Job Offer Model


Since we now know how to create the basic model of our users, its time to create a model for presenting a job offer. A job offer consists of the following attributes: title: The name of the job position. location: The geographical location of the job. description: Details about the position. contact: An email address of the contact person. time-start: The earliest entry date for this position. time-end: A job offer isnt valid forever.

Lets run the Padrino command to create the model for us:

Job Vacancy Application

29

$ padrino g model job_offer title:string location:string description:text c\ ontact:string time_start:date time_end:date apply orms/activerecord apply tests/rspec create models/job_offer.rb create spec/models/job_offer_spec.rb create db/migrate/002_create_job_offers.rb

Next, we need to run our new database migration so that our database has the right scheme:
bundle exec padrino rake ar:migrate => Executing Rake ar:migrate ... DEBUG - (0.4ms) SELECT "schema_migrations"."version" FROM "schema_migra\ tions" INFO - Migrating to CreateUsers (1) INFO - Migrating to CreateJobOffers (2) DEBUG - (0.3ms) select sqlite_version(*) DEBUG - (0.2ms) begin transaction == CreateJobOffers: migrating ============================================\ ==== -- create_table(:job_offers) ...

In order to run our tests, we also need to run our migrations for the test environment:
$ padrino rake ar:migrate -e test => Executing Rake ar:migrate ... == CreateJobOffers: migrating ============================================\ ==== -- create_table(:job_offers) -> 0.0302s == CreateJobOffers: migrated (0.0316s) ===================================\ ====

TBD: Find a way to run ar:migrate for all environments (mainly production and test) If you run your tests with padrino rake spec, everything should be fine.

Creating Connection Between User And Job Offer Model


Since we now have created our two main models, its time to define associations between them. Associations make common operations like deleting or updating data in our relational database easier. Just imagine that we have a user in our app that added many job offers in our system. Now this customer decides that he wants to cancel his account. We decide that all his job offers should also disappear in the system. One solution would be to delete the user, remember his

Job Vacancy Application

30

id, and delete all job offers entries that originate from this id. This manual effort disappears when associations are used: It becomes as easy as If I delete this user from the system, delete automatically all corresponding jobs for this user. We will quickly browse through the associations. has_many This association is the most commonly used one. It does exactly as it tells us: One object has many other objects. We define the association between the user and the job offers as shown in the following expression:
# models/user.rb class User < ActiveRecord::Base has_many :job_offers end

belongs_to The receiving object of the has_many relationship defines that it belongs to exactly one object, and therefore:
# models/job_offer.rb class JobOffer < ActiveRecord::Base belongs_to :user end

Migrate after associate Whenever you modify your models, remember that you need to run migrations too. Because we added the associations manually, we also need to write the migrations. Luckily, Padrino helps us with this task a bit. We know that the job offer is linked to a user via the users id. This foreign key relationship results in adding an extra column user_id to the job_offers table. For this change, we can use the following command to create a migration:
$ padrino g migration AddUserIdToJobOffers user_id:integer apply orms/activerecord create db/migrate/003_add_user_id_to_job_offers.rb

Lets take a look at the created migration:

Job Vacancy Application

31

class AddUserIdToJobOffers < ActiveRecord::Migration def self.up change_table :joboffers do |t| t.integer :user_id end end def self.down change_table :joboffers do |t| t.remove :user_id end end end

Can you see the small bug? This migration wont work, you have to change joboffers to job_offers. For the time being, generators can help you to write code, but not prevent you from thinking. Finally lets run our migrations: $ padrino rake ar:migrate $ padrino rake ar:migrate -e test

Testing our associations in the console


To see whether the migrations were executed, we connected to the sqlite3 database via the command line. Lets use a different approach and use the Padrino console this time. All you have to do is to run the following command:
$ padrino c => Loading development console (Padrino v.0.10.7) => Loading Application JobVacancy >>

Now you are in an environment which acts like IRB, the Interactive Ruby shell. This allows you to execute Ruby commands and immediately see its response. Lets run the shell to create a user with job offers:
user = User.new(:name => 'Matthias Gnther', :email => 'matthias.guenther') => #<User id: nil, name: "Matthias Gnther", email: "matthias.guenther", cr\ eated_at: nil, updated_at: nil> >> user.name => "Matthias Gnther"

This creates a user object in our session. If we want to add an entry permanently into the database, you have to use create method:
http://en.wikipedia.org/wiki/Interactive_Ruby_Shell

Job Vacancy Application

32

User.create(:name => 'Matthias Gnther', :email => 'matthias.guenther') DEBUG - (0.2ms) begin transaction DEBUG - SQL (114.6ms) INSERT INTO "users" ("created_at", "email", "name"\ , "updated_at") VALUES (?, ?, ?, ?) [["created_at", 2012-12-26 08:32:51 +0100], ["email", "matthias.guenther"\ ], ["name", "Matthias Gnther"], ["updated_at", 2012-12-26 08:32:51 +0100]] DEBUG - (342.0ms) commit transaction => #<User id: 1, name: "Matthias Gnther", email: "matthias.guenther", crea\ ted_at: "2012-12-26 08:32:51", updated_at: "2012-12-26 08:32:51"> >>

Please note that now you have an entry in your development database db/job_vacancy_development.db. To see this, connect to the database and execute a SELECT statement::
$ sqlite3 db/job_vacancy_development.db SQLite version 3.7.13 2012-06-11 02:05:22 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> SELECT * FROM users; 1|Matthias Gnther|matthias.guenther|2012-12-26 08:32:51.323349|2012-12-26 \ 08:32:51.323349 sqlite>.exit

Since we have an user, its time to some job offers too:


$ padrino c => Loading development console (Padrino v.0.10.7) => Loading Application JobVacancy JobOffer.create(:title => 'Padrino Engineer', :location => 'Berlin', :description => 'Come to this great place', :contact => '[email protected]', :time_start => '2013/01/01', :time_end => '2013/03/01', :user_id => 1) ... => #<JobOffer id: 1, title: "Padrino Engineer", location: "Berlin", des\ cription: "Come to this great place", contact: "[email protected]", time_start: "2013-01-01", time_en\ d: "2013-03-01", created_at: "2012-12-26 10:12:07", updated_at: "2012-12-26 10:12:07", user_id: 1>

And now lets create a second one for our first user:

Job Vacancy Application

33

>> JobOffer.create(:title => 'Padrino Engineer 2', :location => 'Berlin', :description => 'Come to this great place', :contact => '[email protected]', :time_start => '2013/01/01', :time_end => '2013/03/01', :user_id => 1) ... => #<JobOffer id: 2, title: "Padrino Engineer 2", location: "Berlin", \ description: "Come to this great place", contact: "[email protected]", time_start: "2013-01-01", time_e\ nd: "2013-03-01", created_at: "2012-12-26 10:41:29", updated_at: "2012-12-26 10:41:29", user_id: 1>

Now its time to test our association between the user and the job-offer model. We will use the find_by_id method to get the user from our database, and the job_offers method to get all the job-offers from the user.
>> user = User.find_by_id(1) DEBUG - User Load (0.6ms) SELECT "users".* FROM "users" WHERE "users"."i\ d" = 1 LIMIT 1 => #<User id: 1, name: "Matthias Gnther", email: "matthias.guenther", cr\ eated_at: "2012-12-26 08:32:51", updated_at: "2012-12-26 08:32:51"> >> user.job_offers DEBUG - JobOffer Load (0.6ms) SELECT "job_offers".* FROM "job_offers" WH\ ERE "job_offers"."user_id" = 1 => [#<JobOffer id: 1, title: "Padrino Engineer", location: "Berlin", desc\ ription: "Come to this great place", contact: "[email protected]", time_start: "2013-01-01", time_end:\ "2013-03-01", created_at: "2012-12-26 10:12:07", updated_at: "2012-12-26 10:12:07", user_id: 1>, #<JobOffer id:\ 2, title: "Padrino Engineer 2", location: "Berlin", description: "Come to this great place", contact: "recruter@pad\ rino-firm.org", time_start: "2013-01-01", time_end: "2013-03-01", created_at: "2012-12-26 10:41:29", updated_at: "2\ 012-12-26 10:41:29", user_id: 1>]

Here you can see the advantage of using associations: When you declare them, you automatically get methods for accessing the data you want. Ok, we are doing great so far. With users and job offers in place, lets add some tests to create and associate these objects.

Job Vacancy Application

34

Testing our app with RSpec + Factory Girl


When you use data for the tests, you need to decide how to create them. You could, of course, define a set of test data with pure SQL and add it to your app. A more convenient solution instead is to use factories and fixtures. Think of factories as producers for you data. You are telling the factory that you need 10 users that should have different names and emails. This kind of mass object creation which are called fixtures in testing, can easily be done with Factory Girl. Factory Girl defines its own language to create fixtures in an ActiveRecord-like way, but with a much cleaner syntax. What do we need to use Factory Girl in our app? Right, we first we need to add a gem to our Gemfile:
# Gemfile ... gem 'factory_girl', '~> 4.1.0', :group => 'test'

If you pay a closer look into the Gemfile, you can see that we have several gems with the :group option:
# Gemfile ... gem 'rspec' , '~> 2.12.0', :group => 'test' gem 'factory_girl', '~> 4.1.0', :group => 'test' gem 'rack-test', '~> 0.6.2', :require => 'rack/test', :group => 'test'

Luckily we can use the :group <name> do ... end syntax to cleanup to get rid of several :group => 'test' lines in our Gemfile:
# Gemfile group gem gem gem end :test do 'rspec' , '~> 2.12.0' 'factory_girl', '~> 4.1.0' 'rack-test', '~> 0.6.2', :require => 'rack/test'

Execute bundle and the new gem will be installed. Next we need to define a factory to include all the fixtures of our models:

https://github.com/thoughtbot/factory_girl

Job Vacancy Application

35

# spec/factories.rb # encoding: utf-8 FactoryGirl.define do factory :user do name "Matthias Gnther" email "[email protected]" end end

I want to add myself as a test user. Since Im German, I want to use special symbols, called umlauts, from my language. To make Ruby aware of this, Im putting # encoding: utf-8 at the header of the file. The symbol :user stands for the definition for user model. To make our factory available in all our tests, we just have to require our factory in the spec_helper.rb:
# spec/spec_helper.rb PADRINO_ENV = 'test' unless defined?(PADRINO_ENV) require File.expand_path(File.dirname(__FILE__) + "/../config/boot") require File.dirname(__FILE__) + "/factories" ...

Now we have everything at hand to create a user with the factory while testing our app:
# spec/models/user_spec.rb require 'spec_helper' describe "User Model" do let(:user) { FactoryGirl.build(:user) } let(:job_offer) { {:title => 'Padrino Engineer', :location => 'Berlin', :\ description => 'Come to this great place'} } it 'can be created' do user.should_not be_nil end it 'fresh user should have no offers' do user.job_offers.size.should == 0 end it 'have job-offers' do user.job_offers.build(job_offer) user.job_offers.size.should == 1

Job Vacancy Application

36

end end

The basic philosophy behind testing with fixtures is that you create objects as you need them with convenient expressions. Instead of using User.create, we are using FactoryGirl.build(:user) to temporarily create a user fixture. The job offer that we are adding for the tests is defined as an attribute hash - you map the attributes (keys) to their values. If you run the tests, they will pass. The build method that we use to create the user will only add the test object in memory. If you want to permanently add fixtures to the database, you have to use create instead. Play with it, and see that the same test using create instead of build takes much longer because it hits the database. We can improve our test by creating a factory for our job offer too and cleaning the user_spec.rb file:
# spec/factories.rb ... factory :user do name "Matthias Gnther" email "[email protected]" end factory :job_offer do title "Padrino Engineer" location "Berlin" description "We want you ..." contact "[email protected]" time_start "0/01/2013" time_end "01/03/2013" end ...

And now we modify our user_spec:

Job Vacancy Application

37

# spec/user_spec.rb require 'spec_helper' describe "User Model" do let(:user) { FactoryGirl.build(:user) } it 'can be created' do user.should_not be_nil end it 'fresh user should have no offers' do user.job_offers.size.should == 0 end it 'has job-offers' do user.job_offers.build(FactoryGirl.attributes_for(:job_offer)) user.job_offers.size.should == 1 end end

As you see, the job fixtures us created with FactoryGirls attributes_for method. This method takes a symbol as an input and returns the attributes of the fixture as a hash. Now, our tests are looking fine and they are still green. But we can do even better. We can remove the FactoryGirl expressions if we add make the following change to our spec_helper.rb:
# spec/spec_helper.rb RSpec.configure do |conf| conf.include Rack::Test::Methods conf.include FactoryGirl::Syntax::Methods end

Now we can change our test to:


# spec/models/user_spec.rb require 'spec_helper' describe "User Model" do let(:user) { build(:user) } it 'can be created' do user.should_not be_nil end

Job Vacancy Application

38

it 'fresh user should have no offers' do user.job_offers.size.should == 0 end it 'has job-offers' do user.job_offers.build(attributes_for(:job_offer)) user.job_offers.size.should == 1 end end

Job Vacancy Application

39

User Login and Registration


In traditional frameworks you would generate a user with a user model and a users_controller with the actions new, create, update, and delete. At the end we would need to find a method of safely storing the password for the user. We will do this by hand to see how we can do something like this on our own. But if you dont have to reinvent the wheel you can use Padrinos beautiful Admin interface for your user authentication.

Extending our User Model


Before we are going to build the controller and the sign-up form for our application we need to specify the data each user has.
Name: String Email: String Password: String

Recording from chapter [LEARN HOW TO MAKE REFERENCES TO OTHER CHAPTERS IN LEANPUB] we only need to add the Password fields to the user table: Lets create a migration:
$ padrino g migration AddRegistrationFieldsToUsers apply orms/activerecord create db/migrate/004_add_registration_fields_to_users.rb

And write the fields into the migration file:


# db/migrate/004_add_registration_fields_to_users.rb class AddRegistrationFieldsToUsers < ActiveRecord::Migration @fields = [:password] def self.up change_table :users do |t| @fields.each { |field| t.string field} end end def self.down
http://www.padrinorb.com/guides/padrino-admin

Job Vacancy Application

40

change_table :users do |t| @fields.each { |field| remove_column field} end end end

Next we need to make the attributes of our user model changeable secure through the usage with attr_accessible:

Why should you use :attr_accessor?


Saves you from mass assignment BIG TODO EXPLORATION

Validating attributes
Before we are going to implement what we think, we are going to write pending specs:
# spec/models/user_spec.rb require 'spec_helper' describe "User Model" do ... pending('no blank name') pending('no blank email') describe "passwords" do pending('no blank password') pending('no blank password_confirmation') pending('password and password_confirmation should fail if not equal') pending('password and password_confirmation should be equal') end describe "when name is already used" do pending('should not be saved') end describe "when email address is already used" do pending('should not save an user with an existing address') end

Job Vacancy Application

41

describe "valid email address" do pending('is valid') end describe "not valid email address" do pending('not valid') end pending('password length should be at least 5 characters long') end

Before writing code to pass these specs, we need to add the password field to our factory:
# spec/factories.rb factory :user do name "Matthias Gnther" email "[email protected]" password "foo" end

Lets implement the first pending example that a user cant have a blank name.
# spec/models/user_spec.rb ... it 'have no blank name' do user.name = "" user.save.should be_false end

If we run the test we get the following error


$ rspec spec Failures: 1) User Model have no blank name Failure/Error: user.save.should be_false expected: false value got: true # ./spec/models/user_spec.rb:20:in `block (2 levels) in <top (required\ )>'

Job Vacancy Application

42

Finished in 0.42945 seconds 10 examples, 1 failure, 5 pending Failed examples: rspec ./spec/models/user_spec.rb:18 # User Model have no blank name

To make this test pass we need to validate the email property in our user model with the help of the presence method: # models/user.rb class User < ActiveRecord::Base validates :name, :presence => true
has_many :job_offers

end Please make the same changes to the email and password and write the appropriate spec as an exercise on your own. We dont want to have duplicated names in our application. To simply test this we need as second user with the same name. In order to have to create a second user with the same name. In order to write the test for it, we need to extend or factory with the sequence function:
# spec/factories # encoding: utf-8 FactoryGirl.define do sequence(:email){ |n| "matthias.guenther#{n}@wikimatze.de"} factory :user do name "Matthias Gnther" email password "foo" end ... end

Whenever you build a new user fixture with the email attribute, the value email_number is incremented and guarantees you a new unique user. You can write a test for this ability in the following way:

Job Vacancy Application

43

# spec/models/user_spec.rb describe "when name is already used" do let(:user_second) { build(:user) } it 'should not be saved' do user_second.save.should be_false end end

To make the test green you have to use the uniqueness validation. All what it does is to validates that the attributes value is unique before it gets saved. Lets use it:
# models/user.rb class User < ActiveRecord::Base validates :name, :email, :password, :presence => true validates :name, :uniqueness => true has_many :job_offers end

Next we are going to implement the validation for our email field:
# spec/models/user.spec describe "valid email address" do let(:user_second) { build(:user) } it 'is valid' do adresses = %w[[email protected] [email protected]] adresses.each do |email| user_second.email = email user_second.should be_valid end end end describe "not valid email adress" do let(:user_second) { build(:user)} it 'not save them' do adresses = %w[spamspamspam.de heman,test.com] adresses.each do |email| user_second.email= email user_second.should_not be_valid end end end

Job Vacancy Application

44

We can test the correctness of the email field with a regular expression. First we are going to define a regular expression and use the format argument which takes our regular expression against which the field will be tested.
# models/user.rb class User < ActiveRecord::Base ... VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, format: { with: VALID_EMAIL_REGEX } ... end

Regular Expressions
Regular expressions are your first tool when you need to match certain parts (or whole) strings against a predefined pattern. The drawback of using them is that you have to learn a formal language to define your patterns. I can highly recommend you the Rubular tool (http://rubular.com/) which make it very easy to build and test your patterns against test data.

Users Controller
Since we already have a model for potential users four our platform, its time to create a controller for them, which handles their registration. We are creating in a first step our users controller with a very simple signup form:
$ padrino create create create apply create g controller Users get:new app/controllers/users.rb app/helpers/users_helper.rb app/views/users tests/rspec spec/app/controllers/users_controller_spec.rb

The new thing about the controller command above is the get:new option. This will create an URL rout :new to users/new. Before writing the logic in our controller we create the

Signup Form
The stage is set: We have the model with the constraints, a controller for the user which handles the action and tests for it. Time to create a signup form for getting new users on our platform. For this case we can use the form_for helper. This method takes an object as its input and creates a form using the attributes of the object.

Job Vacancy Application

45

# views/users/new.erb <h1>Registration</h1> <% form_for(@user, '/users/create') do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.text_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation %> <p> <%= f.submit "Create" %> </p> <% end %>

And this will be translated in the following HTML part:


<form method="post" action="/users/create" accept-charset="UTF-8"> <label for="user_name">Name: </label> <input id="user_name" name="user[name]" type="text" /> <label for="user_email">Email: </label> <input id="user_email" name="user[email]" type="text" /> <label for="user_password">Password: </label> <input id="user_password" name="user[password]" type="password" /> <label for="user_password_confirmation">Password confirmation: </label> <input id="user_password_confirmation" name="user[password_confirmation]"\ type="password" /> <p> <input value="Create" type="submit" /> </p> </form>

Lets break it down: form_for: Is part of Padrinos Form Builder and allows you to create standard input fields based on a model. The first argument to the function is an object (mostly a model), the second argument is an string (the action to which the form should be send after an submit), and the third parameter are setting in form of an hash (for example :id => "text", :class => "special"). The action="/users/create" says, that we want to use the post action to the users controller with the create action. f.label and f.text: Will create the fields for the attributes of your model. For example will be
http://www.padrinorb.com/guides/application-helpers#formbuilders

Job Vacancy Application

46

<%= f.label :name %> <%= f.text_field:name %>

translated into:
<label for="user_name">Name: </label> <input id="user_name" name="user[name]" type="text" />

f.password_field: Constructs a password input from the given attribute of the form. You can pass options as hashes as optional parameters (for example :id => "text", :class => "special"). f.submit: Take an string as an caption for the submit button and options as hashes for additional parameter (for example :class => 'long') Before we can see the result of the action in when visiting the URL http://localhost:3000/login we need to make sure to have the right actions in our controller.

User Controller Signup Actions


Since we are using the views/users/new.erb in our users controller, we need to add this action in our controller:
JobVacancy.controllers :users do get :new, :map => "/login" do @user = User.new render 'users/new' end end

So far so good, but what about the mistakes a user mades during his input? How can we display any mistakes a user is making and preserve the things he already typed in? And we still dont save anything? If you remember of section [HAVE TO LOOK UP WHERE USER CREATE/VALIDATE WAS CALLED] we can use this method for validation before we are going to save it. Before doing two steps at a time lets code the create action which saves the new registered user without going into error validation.
# app/controllers/users.rb

post :create do @user = User.new(params[:user]) @user.save redirect(/) redirect(/) end User.new(params[:users]): will create a new user object based on the information of the form attributes of the @user model which will be filled with the attributes of the views/users/new.erb page.

Job Vacancy Application

47

@user.save: Will save the user in the database. redirect: Will redirect the user to the root directory of our application. If you send the formular without any inputs, you will see that you are redirected into the root of your application. You cant figure out whats wrong, but look in your log of your application:
DEBUG - (0.1ms) begin transaction DEBUG - User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."na\ me" = '' LIMIT 1 DEBUG - User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."em\ ail" = '' LIMIT 1 DEBUG - (0.2ms) rollback transaction DEBUG POST (0.0162ms) /users/create - 303 See Other DEBUG - TEMPLATE (0.0004ms) /page/home DEBUG - TEMPLATE (0.0002ms) /application DEBUG GET (0.0057ms) / - 200 OK DEBUG GET (0.0005ms) application.css?1365616902 - 200 OK DEBUG GET (0.0003ms) application.js?1365616902 - 200 OK DEBUG GET (0.0017ms) /favicon.ico - 404 Not Found

The part with the rollback transaction means, that user was not saved because he violated the validation of our user model. If you fully fill on the form withj complete valid parameters and watch your log again, you can see the following log:
DEBUG - (0.2ms) begin transaction DEBUG - User Exists (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."na\ me" = 'Testuser' LIMIT 1 DEBUG - User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."em\ ail" = '[email protected]' LIMIT 1 DEBUG - SQL (0.2ms) INSERT INTO "users" ("created_at", "email", "name", "p\ assword", "updated_at") VALUES (?, ?, ?, ?, ?) [["created_at", 2013-04-10 \ 20:09:10 +0200], ["email", "[email protected]"], ["name", "Testuser"], ["passwor\ d", "example"], ["updated_at", 2013-04-10 20:09:10 +0200]] DEBUG - (174.1ms) commit transaction DEBUG POST (0.1854ms) /users/create - 303 See Other DEBUG - TEMPLATE (0.0004ms) /page/home DEBUG - TEMPLATE (0.0002ms) /application GET (0.0058ms) / - 200 OK DEBUG DEBUG GET (0.0006ms) application.css?1365617350 - 200 OK GET (0.0002ms) application.js?1365617350 - 200 OK DEBUG DEBUG GET (0.0018ms) /favicon.ico - 404 Not Found

Logs can help you to see whats going on in your backend when you dont want to trust the frontend of your applicaton.

Job Vacancy Application

48

Why VALUES (?, ?, ?, ?, ?)


These Parameterized Queries. A parameterized query is a query in which placeholders are used for parameters and the parameter values are supplied at execution time. The most important reason to use parameterized queries is to avoid SQL injection attacks.

You might also like