nedap rails workshop
TRANSCRIPT
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
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>