Nedap Rails Workshop

Preview:

Citation preview

Ruby on Rails

Hellow

Bart André Dirkjan

Hellow

Bart André Dirkjan

Laptops, apple

Development is more than coding

Ruby on Rails

Ruby on Rails

Ruby on Rails

Language

Ruby on Rails

Language

Framework

What are we doing today?

1

2

3

Ruby basics

Rails terminology / vision

Build something simple

What are we doing today?

1

2

3

Ruby basics

Rails terminology / vision

Build something simple

But first....

Very simple example

Address Book■ Generate a new Rails Application

■ Generate some stuff

■ Prepare the database

■ Start the application

■ View application and be excited!

Terminal

Terminal$ rails address_book

create create app/controllers create app/helpers create app/models create app/views/layouts create test/functional create test/integration

... create log/server.log create log/production.log create log/development.log create log/test.log

Terminal$ rails address_book

create create app/controllers create app/helpers create app/models create app/views/layouts create test/functional create test/integration

... create log/server.log create log/production.log create log/development.log create log/test.log

$ cd address_book

Terminal

Terminal$ ./script/generate scaffold person name:stringexists app/controllers/

exists app/helpers/ create app/views/people

...

Terminal$ ./script/generate scaffold person name:stringexists app/controllers/

exists app/helpers/ create app/views/people

...

./script/destroy to undo

Terminal

Terminal$ rake db:migrate

== 00000000000000 CreatePeople: migrating =============-- create_table(:people) -> 0.0177s== 00000000000000 CreatePeople: migrated (0.0180s) ====

Terminal$ rake db:migrate

== 00000000000000 CreatePeople: migrating =============-- create_table(:people) -> 0.0177s== 00000000000000 CreatePeople: migrated (0.0180s) ====

rake db:rollback to undo

⌘ N

Terminal

Terminal

$ cd address_book

Terminal

$ ./script/server=> Booting Mongrel => Rails 2.1.0 application starting on http://0.0.0.0:3000=> Call with -d to detach=> Ctrl-C to shutdown server** Starting Mongrel listening at 0.0.0.0:3000** Starting Rails with development environment...** Rails loaded.** Loading any Rails specific GemPlugins** Signals ready. TERM => stop. USR2 => restart. ** Rails signals registered. ** Mongrel 1.1.4 available at 0.0.0.0:3000** Use CTRL-C to stop.

$ cd address_book

http://localhost:3000/people

It all seems like magic...

You feel lost...

This is normal. It will pass.

Close all

Ruby

The basics■ Objects

■ Variables

■ Methods

■ Inheritance & Modules

■ Blocks

Objects, variables & methods

Objects, variables & methods

class Person attr_accessor :name def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

Objects, variables & methods

class Person attr_accessor :name def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class name

Objects, variables & methods

class Person attr_accessor :name def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class nameinstance variable

Objects, variables & methods

class Person attr_accessor :name def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class nameinstance variable

method

class Person attr_accessor :name def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

Console: using a class

class Person attr_accessor :name def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

Console: using a class

>> andre = Person.new

>> andre.name = ‘Andre’

class Person attr_accessor :name def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

Console: using a class

>> bart = Person.new

>> bart.name = ‘Bart’

>> andre = Person.new

>> andre.name = ‘Andre’

class Person attr_accessor :name def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

Console: using a class

“Bart thinks Andre is a stupid dog!”

>> bart = Person.new

>> bart.name = ‘Bart’

>> andre = Person.new

>> andre.name = ‘Andre’

>> bart.insults(andre, “dog”)

Inheritance

class Student < Person def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class Person attr_accessor :name end

Inheritance

class Student < Person def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

Student inherits from personclass Person

attr_accessor :name end

Modules

Woman Man

Person

Woman Man

Person

Driving skill

Woman Man

PersonDriving skill

Woman Man

Person

Driving skill

Andre Bart

Woman Man

Person

Driving skill

Modulesmodule Insulting def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class Person attr_accessor :name include Insulting end

Modulesmodule Insulting def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class Person attr_accessor :name include Insulting end

Creates an ‘Insulting’ module

Modulesmodule Insulting def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class Person attr_accessor :name include Insulting end

Creates an ‘Insulting’ module

Person classes can

‘Insult’

Modulesmodule Insulting def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class Person attr_accessor :name include Insulting end

Creates an ‘Insulting’ module

Person classes can

‘Insult’ class Robot

attr_accessor :name include Insulting

end

Everyone can insult now!

Console: extending on the fly

module Insulting def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class Person attr_accessor :name end

Console: extending on the fly

>> andre = Person.new

>> andre.name = “Andre”

>> andre.extend(Insulting)

module Insulting def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class Person attr_accessor :name end

nil

Console: extending on the fly

>> andre = Person.new

>> andre.name = “Andre”

>> andre.extend(Insulting)

module Insulting def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class Person attr_accessor :name end

nil

“Andre thinks Bart is a stupid cow!”

>> andre.insults(bart)

Console: extending on the fly

>> andre = Person.new

>> andre.name = “Andre”

>> andre.extend(Insulting)

module Insulting def insults(other, object="cow") "#{name} thinks #{other.name} is a stupid #{object}!" end end

class Person attr_accessor :name end

nil

“Andre thinks Bart is a stupid cow!”

>> andre.insults(bart)

We could also extend an entire class like this!

Man

Driving skill

Insulting

Gardening

Person

Man

Driving skill

Insulting

Gardening

Person

How do we test if this man can insult?

Duck-typing

— Lisa Graves

If it walks like a duck and quacks like a duck, it's a duck.

andre.respond_to?(:insult)

Woman Man

Person

DriverWoman Man

Person

Woman Man

Person

Driving skill

Blocks

Console: using blocks

class Person attr_accessor :name end

Console: using blocks

>> people

class Person attr_accessor :name end

[<#Person:0x00 @name=”Andre”>,<#Person:0x00 @name=”Bart”>]

Console: using blocks

>> people

class Person attr_accessor :name end

[<#Person:0x00 @name=”Andre”>,<#Person:0x00 @name=”Bart”>]

>> people.map{ |item| “#{item.name} is kewl” }[“Andre is kewl”, “Bart is kewl”]

Console: using blocks

>> people

class Person attr_accessor :name end

[<#Person:0x00 @name=”Andre”>,<#Person:0x00 @name=”Bart”>]

>> people.map{ |item| “#{item.name} is kewl” }[“Andre is kewl”, “Bart is kewl”]

We also have: select, reject and inject to work with collections!

You know Ruby!

You know Ruby! Sorta...

You know Ruby! Sorta...

PART II

Convention over configuration

railsenvy.com

M V CMODEL

VIEW

CONTROLLER

Models

Views

Controllers

Talk to the database, contain business logic

Show the content to the user

Provide the glue, prepare data when needed

— Tyler Durden, Fight Club

You are not a beautiful and unique snowflake. You are the same decaying organic matter as everyone else, and we are all a part of the same compost pile.

RESOURCE

SEE CHANGE

REMOVEADD

RESOURCE

SEE CHANGE

REMOVEADD

Show me something

RESOURCE

SEE CHANGE

REMOVEADD

Show me something

Add someone

RESOURCE

SEE CHANGE

REMOVEADD

Show me something

Add someone

Change something

RESOURCE

SEE CHANGE

REMOVEADD

Show me something

Add someone

Change something

Delete stuff

C R U DCREATE

DELETE

UPDATE

READ

C R U DCREATE

DELETE

UPDATE

READ

C R U DHTTP!

CREATE

DELETE

UPDATE

READ

C R U DPOST

HTTP!

CREATE

DELETE

UPDATE

READ

C R U DPOST

GET

HTTP!

CREATE

DELETE

UPDATE

READ

C R U DPOST PUT

GET

HTTP!

CREATE

DELETE

UPDATE

READ

C R U DPOST

DELETE

PUT

GET

HTTP!

CREATE

DELETE

UPDATE

READ

http://www.snowflake.org/people/1

U R IUNIVERSAL

RESOURCE

IDENTIFIER

/people POSTADD

/people

/people

POSTGET

ADDSEE

/people

/people

/people/1

POSTGETPUT

ADDSEECHANGE

/people

/people

/people/1

/people/1

POSTGETPUTDELETE

ADDSEECHANGEREMOVE

R E S TREPRESENTATIONAL STATE TRANSFER

RAILS CONTROLLER ACTIONS

INDEX NEW EDIT

SHOW

RAILS CONTROLLER ACTIONS

CREATE UPDATE

DESTROY

Building something...

Let’s get started

Choose a subject

2 hrsIT WON’T BE ENOUGH...

■ You should have an idea

■ You should have a rough sketch

■ You should have thought of what models you need

■ You should think of their relation to each other

■ Pick an pair of models with a 1..* relationship

First 15 minutes

BeerStudent1 *

Next 10 minutes■ You should have a new rails app

$ rails [your_app_name]

■ You should have generated the models$ ./script/generate scaffold [model_name] [attr]:[type

type = string, text, integer, float, boolean, date, time, datetime

reserved attrs => type, version

mate .

App structure- app

- models

- views

- controllers

- config

- db

- migrate

App structure- app

- models

- views

- controllers

- config

- db

- migrate

the model files

App structure- app

- models

- views

- controllers

- config

- db

- migrate

the model files

templates for HTML

App structure- app

- models

- views

- controllers

- config

- db

- migrate

the model files

templates for HTML

the controller files

App structure- app

- models

- views

- controllers

- config

- db

- migrate

the model files

templates for HTML

the controller files

basic configuration

App structure- app

- models

- views

- controllers

- config

- db

- migrate

the model files

templates for HTML

the controller files

basic configuration

database migrations

Migrations

db/migrate/00000000_create_people.rbclass CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :name

t.timestamps end end

def self.down drop_table :people endend

db/migrate/00000000_create_people.rbclass CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :name

t.timestamps end end

def self.down drop_table :people endend

Create a ‘People’ table on UP

db/migrate/00000000_create_people.rbclass CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :name

t.timestamps end end

def self.down drop_table :people endend

Create a ‘People’ table on UPWith a ‘Name’

String

db/migrate/00000000_create_people.rbclass CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :name

t.timestamps end end

def self.down drop_table :people endend

Create a ‘People’ table on UPWith a ‘Name’

String

And some time-stamps: created_at & updated_at

db/migrate/00000000_create_people.rbclass CreatePeople < ActiveRecord::Migration def self.up create_table :people do |t| t.string :name

t.timestamps end end

def self.down drop_table :people endend

Create a ‘People’ table on UPWith a ‘Name’

String

And some time-stamps: created_at & updated_at

Drop the table on DOWN

Relationships

BeerStudent1 *

app/models/student.rbclass Student < ActiveRecord::Base has_many :beers end

app/models/beer.rbclass Beer < ActiveRecord::Base belongs_to :student end

db/migrate/00000000_create_beers.rbclass CreateBeers < ActiveRecord::Migration def self.up create_table :beers do |t| t.string :brand t.references :student t.timestamps end end

def self.down drop_table :beers endend

db/migrate/00000000_create_beers.rbclass CreateBeers < ActiveRecord::Migration def self.up create_table :beers do |t| t.string :brand t.references :student t.timestamps end end

def self.down drop_table :beers endend

Add a reference to Student(:student_id)

Next 10 minutes■ You should update your model files when needed

belongs_to, has_many, has_one, has_many :through

■ You should add references to migrationst.references :student

Next 5 minutes■ You should have migrated the database

$ rake db:migrate

■ You should have a running server$ ./script/server

■ You should see your app$ open http://localhost:3000

Routes

config/routes.rb

ActionController::Routing::Routes.draw do |map| map.resources :students map.resources :beers

# You can have the root of your site routed with map.root # map.root :controller => "welcome"

map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format'end

config/routes.rb

ActionController::Routing::Routes.draw do |map| map.resources :students map.resources :beers

# You can have the root of your site routed with map.root # map.root :controller => "welcome"

map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format'end

Routes for each resource

config/routes.rb

ActionController::Routing::Routes.draw do |map| map.resources :students map.resources :beers

# You can have the root of your site routed with map.root # map.root :controller => "welcome"

map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format'end

Routes for each resource

Remove # and change :controller to ‘students’

/people

/people

/people/1

/people/1

POSTGETPUTDELETE

ADDSEECHANGEREMOVE

/people/1

/people/1/edit

/people/new

GETGETGET

SEECHANGEADD

Next 5 minutes■ Change the routes

Uncomment & choose default controller

■ Remove ‘public/index.html’$ rm public/index.html

■ Refresh your browser

ActiveRecord

One class per table

One instance per row

One class per table

One instance per rowColumns are attributes

Student.find_by_name(“Andre”)

Student.find(:first, :conditions => [“name = ?”,”Andre”])

Student.find_all_by_name(“Andre”)

Student.find(:all, :conditions => [“name = ?”,”Andre”])

andre = Student.new( :name => “Andre” )

andre.save

Next 10 minutes■ You should play with your app, create some instances

We created a student named “Andre”

■ You should start a console$ ./script/console

■ You should build a few relationships using the console>> andre = Student.find_by_name(“Andre”)

>> beer = andre.beers.create( :brand => “Grolsch” )

>> beer.student.name ”Andre”

Views

app/views/beer/new.html.erb

<h1>New beer</h1>

<% form_for(@beer) do |f| %> <%= f.error_messages %>

<p> <%= f.label :brand %><br /> <%= f.text_field :brand %> </p> <p> <%= f.submit "Create" %> </p><% end %>

<%= link_to 'Back', beers_path %>

app/views/beer/new.html.erb

<h1>New beer</h1>

<% form_for(@beer) do |f| %> <%= f.error_messages %>

<p> <%= f.label :brand %><br /> <%= f.text_field :brand %> </p> <p> <%= f.submit "Create" %> </p><% end %>

<%= link_to 'Back', beers_path %>

Title of the page

app/views/beer/new.html.erb

<h1>New beer</h1>

<% form_for(@beer) do |f| %> <%= f.error_messages %>

<p> <%= f.label :brand %><br /> <%= f.text_field :brand %> </p> <p> <%= f.submit "Create" %> </p><% end %>

<%= link_to 'Back', beers_path %>

Title of the pageA form

app/views/beer/new.html.erb

<h1>New beer</h1>

<% form_for(@beer) do |f| %> <%= f.error_messages %>

<p> <%= f.label :brand %><br /> <%= f.text_field :brand %> </p> <p> <%= f.submit "Create" %> </p><% end %>

<%= link_to 'Back', beers_path %>

Title of the pageA form

Show errors messages if something goes wrong

app/views/beer/new.html.erb

<h1>New beer</h1>

<% form_for(@beer) do |f| %> <%= f.error_messages %>

<p> <%= f.label :brand %><br /> <%= f.text_field :brand %> </p> <p> <%= f.submit "Create" %> </p><% end %>

<%= link_to 'Back', beers_path %>

Title of the pageA form

Show errors messages if something goes wrong

Some fields and a button

app/views/beer/new.html.erb

<h1>New beer</h1>

<% form_for(@beer) do |f| %> <%= f.error_messages %>

<p> <%= f.label :brand %><br /> <%= f.text_field :brand %> </p> <p> <%= f.submit "Create" %> </p><% end %>

<%= link_to 'Back', beers_path %>

Title of the pageA form

Show errors messages if something goes wrong

Some fields and a button

Back link

app/views/beer/new.html.erb

<h1>New beer</h1>

<% form_for(@beer) do |f| %> <%= f.error_messages %>

<p> <%= f.label :brand %><br /> <%= f.text_field :brand %> </p> <p> <%= f.submit "Create" %> </p><% end %>

<%= link_to 'Back', beers_path %>

app/views/beer/new.html.erb<h1>New beer</h1>

<% form_for(@beer) do |f| %> <%= f.error_messages %>

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

<p> <%= f.label :student %><br /> <%= f.collection_select :student_id, Student.find(:all), :id, :name %> </p>

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

<%= link_to 'Back', beers_path %>

Next 15 minutes■ Should be able to set a belongs_to relationship

collection_select

■ Relationship must be able to be set on new and existing objectsChange both the edit and new view!

■ Test that it really works!

Try to edit the show view to represent the object relationship so another human understands it!

ValidationsThis is a behaviour...

ValidationsThis is a behaviour...

B D DBEHAVIOUR DRIVEN DEVELOPMENT

How should a student behave?

Student■ should never have a blank name

Terminal

Terminal$ ./script/generate rspec create lib/tasks/rspec.rake

create script/autospec create script/spec create script/spec_server create spec create spec/rcov.opts create spec/spec.opts create spec/spec_helper.rb create stories create stories/all.rb create stories/helper.rb

Terminal$ ./script/generate rspec create lib/tasks/rspec.rake

create script/autospec create script/spec create script/spec_server create spec create spec/rcov.opts create spec/spec.opts create spec/spec_helper.rb create stories create stories/all.rb create stories/helper.rb

$ ./script/generate rspec_model student

Terminal$ ./script/generate rspec create lib/tasks/rspec.rake

create script/autospec create script/spec create script/spec_server create spec create spec/rcov.opts create spec/spec.opts create spec/spec_helper.rb create stories create stories/all.rb create stories/helper.rb

$ ./script/generate rspec_model student

$ rake db:migrate RAILS_ENV=test

spec/models/student_spec.rbrequire File.dirname(__FILE__) + '/../spec_helper'

describe Student do it "should never have a blank name" do

no_name = Student.new( :name => “” ) no_name.should_not be_valid

end end

spec/models/student_spec.rbrequire File.dirname(__FILE__) + '/../spec_helper'

describe Student do it "should never have a blank name" do

no_name = Student.new( :name => “” ) no_name.should_not be_valid

end end

ActiveRecord objects have a valid? method

Terminal: Running tests

require File.dirname(__FILE__) + '/../spec_helper'

describe Student do it "should never have a blank name" do

no_name = Student.new( :name => “” ) no_name.should_not be_valid

end end

Terminal: Running tests

$ ruby spec/models/student_spec.rb F

Failed:Student should never have a blank name (FAILED)

Finished in 0.1 seconds

1 examples, 1 failures, 0 pending

require File.dirname(__FILE__) + '/../spec_helper'

describe Student do it "should never have a blank name" do

no_name = Student.new( :name => “” ) no_name.should_not be_valid

end end

app/models/student.rbclass Student < ActiveRecord::Base has_many :beers validates_presence_of :name end

app/models/student.rbclass Student < ActiveRecord::Base has_many :beers validates_presence_of :name end

Next 5 minutes■ Add Rspec to your project

./script/generate rspec

■ Generate specs for your models - don’t replace files!./script/generate rspec_model [model-name]

■ Clean spec files. Make sure they look like this.

■ Build the test databaserake db:migrate RAILS_ENV=test

require File.dirname(__FILE__) + '/../spec_helper'

describe Student do end

Next 15 minutes■ Spec out all your validations

■ First, all your specs should fail

■ Add the validations to your modelsvalidates_presence_ofvalidates_uniqueness_ofvalidates_format_ofvalidates_length_ofvalidates_numericality_of

■ Then, all your specs should pass

Connecting dots

Connecting dots.............

Let’s make our view a little bit more fun

<p> <b>Name:</b> <%=h @student.name %></p>

<%= link_to 'Edit', edit_person_path(@person) %> |<%= link_to 'Back', people_path %>

app/views/students/show.html.erb

app/views/students/show.html.erb<p> <b>Name:</b> <%=h @student.name %></p>

<ul><% @student.beers.each do |beer| %> <li> 1 x <%= h beer.brand %> </li><% end %></ul>

<%= link_to 'Edit', edit_person_path(@person) %> |<%= link_to 'Back', people_path %>

Next 10 minutes■ Pimp one of your views!

You all get a PDF version of this book to continue working on your project,

thanks to pragprog.com

FREE!

</PRESENTATION>

Recommended