Ruby on Rails Supinfo 2011. A propos Martin Catty, fondateur de Synbioz Rubyiste depuis 2006 _fuse...

Post on 26-Mar-2015

215 views 0 download

Transcript of Ruby on Rails Supinfo 2011. A propos Martin Catty, fondateur de Synbioz Rubyiste depuis 2006 _fuse...

Ruby on RailsSupinfo 2011

A propos

•Martin Catty, fondateur de Synbioz

•Rubyiste depuis 2006

_fuse mcatty@synbioz.com

Ruby avant Rails

•Ne partez pas sans vos bagages !

•Rails a aidé à l’essor de Ruby

•Mais pas de framework sans langage

RubyOn ne va pas (ou peu) parler de:

I/O

Réseau

ThreadTest

Debugger

Proc / lambda Exceptions

Garbage

VM

Implémentations

L’Histoire

•Créé par Yukihiro Matsumoto (Matz)

•Release publique en 2005

Un langage

•Plus objet que python

•Plus puissant que perl

•Fun

Les versions

•Stable actuelle: 1.9.2

•1.8.7 dans la branche 1.8

•1.8.7 (p250) fonctionne avec Rails 2 et 3

•< 1.8.7 = Rails 2

•> 1.8.7 = Rails 3

Le fameux hello world

classclass Hello { Hello {public static void main(String [] public static void main(String [] args) {args) {

system.out.println(‘Hello’);system.out.println(‘Hello’);}}

}}

classclass Hello { Hello {public static void main(String [] public static void main(String [] args) {args) {

system.out.println(‘Hello’);system.out.println(‘Hello’);}}

}}

Java Ruby

putsputs ‘Hello’ ‘Hello’putsputs ‘Hello’ ‘Hello’

</troll>

Tout est objet2.2.timestimes { puts "Bonjour { puts "Bonjour Supinfo." }Supinfo." }2.2.timestimes { puts "Bonjour { puts "Bonjour Supinfo." }Supinfo." }

=> Bonjour Supinfo

=> Bonjour Supinfo

3.3.uptoupto(5) { |i| puts i }(5) { |i| puts i }3.3.uptoupto(5) { |i| puts i }(5) { |i| puts i }

=> 3

=> 4

=> 5

p 1.p 1.zero?zero?p 1.p 1.zero?zero?

=> false

39 + 339 + 339 + 339 + 3

=> 39.+(3)

Les variablesclass Aclass A

MAXMAX = 42 = 42@@instances@@instances = 0 = 0def initialize(name)def initialize(name)

@name@name = = namename@@instances@@instances += 1 += 1

endend

def self.instancesdef self.instances@@instances@@instances

endendendend

class Aclass AMAXMAX = 42 = 42@@instances@@instances = 0 = 0def initialize(name)def initialize(name)

@name@name = = namename@@instances@@instances += 1 += 1

endend

def self.instancesdef self.instances@@instances@@instances

endendendend

A.new("a")A.new("b")A.new("c")A.new("d")A.new("e")

p A.instances #=> 5

Itérationsa = 1..9a = 1..9forfor i in a i in a

putsputs i iendend

a = 1..9a = 1..9forfor i in a i in a

putsputs i iendend

a = 1..9a = 1..9a.a.eacheach { |i| { |i| putsputs i } i }a = 1..9a = 1..9a.a.eacheach { |i| { |i| putsputs i } i }

=> 1…9

ou

i = 0i = 0looploop do i += 1 puts i do i += 1 puts i breakbreak if 10 == iend if 10 == iendi = 0i = 0looploop do i += 1 puts i do i += 1 puts i breakbreak if 10 == iend if 10 == iend

1.1.uptoupto(10) do |i| (10) do |i| nextnext if i.odd? if i.odd? # pas d'impair en ruby puts # pas d'impair en ruby puts iendiend

1.1.uptoupto(10) do |i| (10) do |i| nextnext if i.odd? if i.odd? # pas d'impair en ruby puts # pas d'impair en ruby puts iendiend

=> 2, 4, 6, 8, 10

=> 1…9

=> 1…9

1.upto(2) do |i| v = rand(2) 1.upto(2) do |i| v = rand(2) retryretry if v.zero?end if v.zero?end1.upto(2) do |i| v = rand(2) 1.upto(2) do |i| v = rand(2) retryretry if v.zero?end if v.zero?end

Conditionsifif index == 1 index == 1elseelseendend

ifif index == 1 index == 1elseelseendend

puts ‘0’ puts ‘0’ ifif index.zero?index.zero?puts ‘0’ puts ‘0’ ifif index.zero?index.zero?

puts ‘not 0’ puts ‘not 0’ unlessunless index.zero?index.zero?puts ‘not 0’ puts ‘not 0’ unlessunless index.zero?index.zero?

def what_is_it?(a) def what_is_it?(a) casecase a a whenwhen 1..2 1..2 puts "1 or 2" puts "1 or 2" whenwhen 3 puts "3" 3 puts "3" whenwhen /4.*/ # Regexp puts "something /4.*/ # Regexp puts "something starting with 4." starting with 4." whenwhen "foo" puts "foo" puts "foo" "foo" elseelse puts "I don't know." puts "I don't know." endendendend

def what_is_it?(a) def what_is_it?(a) casecase a a whenwhen 1..2 1..2 puts "1 or 2" puts "1 or 2" whenwhen 3 puts "3" 3 puts "3" whenwhen /4.*/ # Regexp puts "something /4.*/ # Regexp puts "something starting with 4." starting with 4." whenwhen "foo" puts "foo" puts "foo" "foo" elseelse puts "I don't know." puts "I don't know." endendendend

what_is_it?(1)# 1 or 2what_is_it?(2)# 1 or 2what_is_it?(3)# 3what_is_it?("4004")# something starting with 4.what_is_it?("foo")# foowhat_is_it?(5)# Don't know.

Tableaux 1/2lost = [8, 15, 16, lost = [8, 15, 16, 23]23]lost lost <<<< 42 # 42 # pushpushlost.lost.unshiftunshift(4)(4)# [4, 8, 15, 16, # [4, 8, 15, 16, 23, 42]23, 42]

lost = [8, 15, 16, lost = [8, 15, 16, 23]23]lost lost <<<< 42 # 42 # pushpushlost.lost.unshiftunshift(4)(4)# [4, 8, 15, 16, # [4, 8, 15, 16, 23, 42]23, 42]

lost lost <<<< nil nil <<<< nilnil

# [4, 8, 15, 16, # [4, 8, 15, 16, 23, 42, nil, nil]23, 42, nil, nil]

lost lost <<<< nil nil <<<< nilnil

# [4, 8, 15, 16, # [4, 8, 15, 16, 23, 42, nil, nil]23, 42, nil, nil]

lost.lost.compact!compact!

# [4, 8, 15, 16, # [4, 8, 15, 16, 23, 42]23, 42]

lost.lost.compact!compact!

# [4, 8, 15, 16, # [4, 8, 15, 16, 23, 42]23, 42]

lost lost <<<< [4, 8] [4, 8]

# [4, 8, 15, 16, # [4, 8, 15, 16, 23, 42, [4, 8]]23, 42, [4, 8]]

lost lost <<<< [4, 8] [4, 8]

# [4, 8, 15, 16, # [4, 8, 15, 16, 23, 42, [4, 8]]23, 42, [4, 8]]

lost.lost.flatten!flatten!..uniq!uniq!

# [4, 8, 15, 16, # [4, 8, 15, 16, 23, 42]23, 42]

lost.lost.flatten!flatten!..uniq!uniq!

# [4, 8, 15, 16, # [4, 8, 15, 16, 23, 42]23, 42]

lost.lost.indexindex(23)(23)

# 4# 4

lost.lost.indexindex(23)(23)

# 4# 4

lost.lost.shuffleshuffle# [16, 23, 42, 4, # [16, 23, 42, 4, 15, 8]15, 8][5, 3, 7, 39, 1, [5, 3, 7, 39, 1, 15].15].sortsort# [1, 3, 5, 7, 15, # [1, 3, 5, 7, 15, 39]39]

lost.lost.shuffleshuffle# [16, 23, 42, 4, # [16, 23, 42, 4, 15, 8]15, 8][5, 3, 7, 39, 1, [5, 3, 7, 39, 1, 15].15].sortsort# [1, 3, 5, 7, 15, # [1, 3, 5, 7, 15, 39]39]

('a'..'z').('a'..'z').to_ato_a["a", "b", "c", ["a", "b", "c", "d"…]"d"…]

('a'..'z').('a'..'z').to_ato_a["a", "b", "c", ["a", "b", "c", "d"…]"d"…]

lost.lost.atat(0)(0)# 4# 4lost[-1]lost[-1]# 42# 42

lost.lost.atat(0)(0)# 4# 4lost[-1]lost[-1]# 42# 42

1111

Tableaux 2/2double_lost = lost.double_lost = lost.mapmap { |v| v * { |v| v * 2 }2 }# => [8, 16, 30, 32, 46, 84]# => [8, 16, 30, 32, 46, 84]

double_lost = lost.double_lost = lost.mapmap { |v| v * { |v| v * 2 }2 }# => [8, 16, 30, 32, 46, 84]# => [8, 16, 30, 32, 46, 84]

# lost: [4, 8, 15, 16, 23, 42]# lost: [4, 8, 15, 16, 23, 42]double_lost double_lost -- lost lost# => [30, 32, 46, 84]# => [30, 32, 46, 84]

# lost: [4, 8, 15, 16, 23, 42]# lost: [4, 8, 15, 16, 23, 42]double_lost double_lost -- lost lost# => [30, 32, 46, 84]# => [30, 32, 46, 84]

# intersection# intersectiondouble_lost double_lost && lost lost# [8, 16]# [8, 16]

# intersection# intersectiondouble_lost double_lost && lost lost# [8, 16]# [8, 16]

# jointure# jointure(double_lost (double_lost || lost).sort lost).sort# [4, 8, 15, 16, 23, 30, 32, 42, # [4, 8, 15, 16, 23, 30, 32, 42, 46, 84]46, 84]

# jointure# jointure(double_lost (double_lost || lost).sort lost).sort# [4, 8, 15, 16, 23, 30, 32, 42, # [4, 8, 15, 16, 23, 30, 32, 42, 46, 84]46, 84]

String

str = str = "a"str."a"str.succsucc# => # => "b""b"

str = str = "a"str."a"str.succsucc# => # => "b""b"

# # InterpolationInterpolationputsputs "foo "foo #{#{strstr}}""# # => foo bar=> foo bar

# # InterpolationInterpolationputsputs "foo "foo #{#{strstr}}""# # => foo bar=> foo bar

# attention, en 1.8# attention, en 1.8str = "éhé"str = "éhé"str.sizestr.size# => 5 et non # => 5 et non 33str[0]str[0]# 195 et non é # 195 et non é (code ascii)(code ascii)

# patché dans rails# patché dans railsstr.str.mb_charsmb_chars[0][0]# é # é str.mb_chars.sizestr.mb_chars.size# 3# 3

# attention, en 1.8# attention, en 1.8str = "éhé"str = "éhé"str.sizestr.size# => 5 et non # => 5 et non 33str[0]str[0]# 195 et non é # 195 et non é (code ascii)(code ascii)

# patché dans rails# patché dans railsstr.str.mb_charsmb_chars[0][0]# é # é str.mb_chars.sizestr.mb_chars.size# 3# 3

str = "bar"str = "bar"putsputs 'foo 'foo #{str}'#{str}'# => foo # => foo #{str}#{str}

str = "bar"str = "bar"putsputs 'foo 'foo #{str}'#{str}'# => foo # => foo #{str}#{str}

str = "foo\str = "foo\n"str.n"str.chompchomp# # => "foo"=> "foo"

str = "foo\str = "foo\n"str.n"str.chompchomp# # => "foo"=> "foo"

str = str = "foo"str."foo"str.chopchop# # => "fo"=> "fo"

str = str = "foo"str."foo"str.chopchop# # => "fo"=> "fo"

"supinfo"."supinfo".capitalicapitalizeze# => "Supinfo"# => "Supinfo""supinfo"."supinfo".upcaseupcase# => "SUPINFO"# => "SUPINFO"

"supinfo"."supinfo".capitalicapitalizeze# => "Supinfo"# => "Supinfo""supinfo"."supinfo".upcaseupcase# => "SUPINFO"# => "SUPINFO"

Hash

h = { :a => 'a', :b => 42, :c => { h = { :a => 'a', :b => 42, :c => { :d => 'f' } }:d => 'f' } }h = { :a => 'a', :b => 42, :c => { h = { :a => 'a', :b => 42, :c => { :d => 'f' } }:d => 'f' } }

h2 = { :a => 'foo' }h2 = { :a => 'foo' }h.h.mergemerge(h2)(h2)=> => {:a=>"foo", :b=>42, :c=>{:d=>"{:a=>"foo", :b=>42, :c=>{:d=>"f"}}f"}}

h2 = { :a => 'foo' }h2 = { :a => 'foo' }h.h.mergemerge(h2)(h2)=> => {:a=>"foo", :b=>42, :c=>{:d=>"{:a=>"foo", :b=>42, :c=>{:d=>"f"}}f"}}

Association clé / valeur

Class: les constructeurs

classclass A def A.new A def A.new endendendendA.newA.newclassclass A def A.new A def A.new endendendendA.newA.new

classclass B def self.new B def self.new endendendend

B.newB.new

classclass B def self.new B def self.new endendendend

B.newB.new

classclass C def C def initializeinitialize endendendendC.newC.newclassclass C def C def initializeinitialize endendendendC.newC.new

class D # pas de class D # pas de constructeur multiple def constructeur multiple def initializeinitialize; end def ; end def initializeinitialize(*args) endend(*args) endendD.new # => KOD.new(1) D.new # => KOD.new(1) # => OK# => OK

class D # pas de class D # pas de constructeur multiple def constructeur multiple def initializeinitialize; end def ; end def initializeinitialize(*args) endend(*args) endendD.new # => KOD.new(1) D.new # => KOD.new(1) # => OK# => OK

Class: les accesseurs

classclass Product Productdef def initializeinitialize(name, (name, description, price)description, price)

@name = name@name = name@description = description@description = description@price = price@price = price

endend

def namedef name@name@name

endend

def name=(name)def name=(name)@name = name@name = name

endend……

endend

classclass Product Productdef def initializeinitialize(name, (name, description, price)description, price)

@name = name@name = name@description = description@description = description@price = price@price = price

endend

def namedef name@name@name

endend

def name=(name)def name=(name)@name = name@name = name

endend……

endend

classclass Product Productattr_accessorattr_accessor :name :nameattr_readerattr_reader: description: descriptionattr_writerattr_writer :price :price

def def initializeinitialize(name, (name, description, price)description, price)

@name = name@name = name@description = description@description = description@price = price@price = price

endendendend

classclass Product Productattr_accessorattr_accessor :name :nameattr_readerattr_reader: description: descriptionattr_writerattr_writer :price :price

def def initializeinitialize(name, (name, description, price)description, price)

@name = name@name = name@description = description@description = description@price = price@price = price

endendendend

Class: portée & héritage

classclass Animal def Animal def initializeinitialize puts puts "Born to be alive." end "Born to be alive." end protectedprotected def breathe? puts def breathe? puts "inhale, exhale" "inhale, exhale" truetrue end end privateprivate def speak; end def speak; endendend

# Animal.new.speak # Animal.new.speak # => fail with private method # => fail with private method `speak'`speak'

classclass Animal def Animal def initializeinitialize puts puts "Born to be alive." end "Born to be alive." end protectedprotected def breathe? puts def breathe? puts "inhale, exhale" "inhale, exhale" truetrue end end privateprivate def speak; end def speak; endendend

# Animal.new.speak # Animal.new.speak # => fail with private method # => fail with private method `speak'`speak'

classclass Dog < Animal def Dog < Animal def alive?alive? puts "I'm alive" if breathe? end puts "I'm alive" if breathe? end def def speakspeak puts "woff." endend puts "woff." endend

snoopy = Dog.newsnoopy = Dog.new# Born to be alive.# Born to be alive.snoopy.speaksnoopy.speak# woff.# woff.snoopy.alive?snoopy.alive?# inhale, exhale# I'm alive# inhale, exhale# I'm alive

classclass Dog < Animal def Dog < Animal def alive?alive? puts "I'm alive" if breathe? end puts "I'm alive" if breathe? end def def speakspeak puts "woff." endend puts "woff." endend

snoopy = Dog.newsnoopy = Dog.new# Born to be alive.# Born to be alive.snoopy.speaksnoopy.speak# woff.# woff.snoopy.alive?snoopy.alive?# inhale, exhale# I'm alive# inhale, exhale# I'm alive

Class: étendre

# Etendre un objetstr = "foo"class << str def blank? self !~ /\S/ endendstr.blank?# => false# Etendre un objetstr = "foo"class << str def blank? self !~ /\S/ endendstr.blank?# => false

# Etendre une classe# Etendre une classeclassclass String String defdef blank? self ! blank? self !~ /\S/ ~ /\S/ endendendend" ".blank?" ".blank?# # => true=> true"foo".blank?"foo".blank?# => false# => false

# Etendre une classe# Etendre une classeclassclass String String defdef blank? self ! blank? self !~ /\S/ ~ /\S/ endendendend" ".blank?" ".blank?# # => true=> true"foo".blank?"foo".blank?# => false# => false

Class: ce qui n’existe pas

•Pas d’héritage multiple

•Basé sur les mixins

•Pas d’interface

•Pas de classe abstraite native

Les outils

[17:59:57] fuse@h [~]$ irb[17:59:57] fuse@h [~]$ irbruby-1.8.7-ruby-1.8.7-p302 :001 >p302 :001 > puts ‘foo’ puts ‘foo’foo => nilfoo => nilruby-1.8.7-p302 :002 >ruby-1.8.7-p302 :002 > 21 * 2 => 42 21 * 2 => 42ruby-1.8.7-p302 :008 >ruby-1.8.7-p302 :008 > String.methods.sort => ["<", "<=", String.methods.sort => ["<", "<=", "<=>", "==", "===", "=~", ">", …]"<=>", "==", "===", "=~", ">", …]

[17:59:57] fuse@h [~]$ irb[17:59:57] fuse@h [~]$ irbruby-1.8.7-ruby-1.8.7-p302 :001 >p302 :001 > puts ‘foo’ puts ‘foo’foo => nilfoo => nilruby-1.8.7-p302 :002 >ruby-1.8.7-p302 :002 > 21 * 2 => 42 21 * 2 => 42ruby-1.8.7-p302 :008 >ruby-1.8.7-p302 :008 > String.methods.sort => ["<", "<=", String.methods.sort => ["<", "<=", "<=>", "==", "===", "=~", ">", …]"<=>", "==", "===", "=~", ">", …]

irb : votre shell ruby

rvm : gérer plusieurs versions de ruby

Documentation

•http://www.ruby-doc.org/

•http://www.ruby-lang.org/

•http://apidock.com/ruby

•Le pickAxe: LA référence

•http://tryruby.org/

•http://groups.google.com/group/rubyfr-public

RailsOn ne va pas (ou peu) parler de:

Test

CacheRake

ActionMailer

ActiveSupport

I18n

Déploiement

Copieurs

Rails 3

L’Histoire

•Créé par DHH (David Heinemeier Hansson)

•Version 1 en décembre 2005

•Pragmatique dès le début: basecamp

Rails en entreprise

•Plus un jouet

•Présent en entreprise, grandes et petites

•Opportunités en temps que développeur

Les versions

•Stable actuelle: 3.0.7

•Rails 3 issue du merge avec merb (08/10)

•Framework agnostic

•Stable actuelle: 2.3.11

•La plus rencontrée en entreprise

Des conventions

•Convention over configuration

•DRY

•MVC

Quelques noms barbares:

Convention over configuration

•Votre passion est de customiser Tomcat ?Désolé !

•0 conf pour commencer à développer

•Serveur web embarqué

•BDD embarquée

Don’t repeat yourself

•- de code = - de bug

•- de code = - de maintenance

•- de code = + de temps

Modèle - Vue - Contrôleur

ContrôlContrôleureur

ContrôlContrôleureurModèleModèleModèleModèle VueVueVueVueContrôlContrôleureur

ContrôlContrôleureur

Métier Fonctionnel Affichage

Rails en vrai

•Pas de hello world, c’est trop simple…

•La classique liste de produits plutôt.

Créons notre application

$ rails app$ rails app$ rails app$ rails app

3 environnements

class class ProductProduct < ActiveRecord::Base < ActiveRecord::Baseendendclass class ProductProduct < ActiveRecord::Base < ActiveRecord::Baseendend

Modèleapp/models/product.rb

class class ProductsControllerProductsController < < ApplicationControllerApplicationController

def indexdef index@products@products = = ProductProduct.all.all

endendendend

class class ProductsControllerProductsController < < ApplicationControllerApplicationController

def indexdef index@products@products = = ProductProduct.all.all

endendendend

Contrôleurapp/controllers/products_controller.rb

<html><body><html><body><% for product in <% for product in @products@products %> %><p><%= product.name %></p><p><%= product.name %></p><% end %><% end %></body></html></body></html>

<html><body><html><body><% for product in <% for product in @products@products %> %><p><%= product.name %></p><p><%= product.name %></p><% end %><% end %></body></html></body></html>

Vueapp/views/products/index.html.erb

Que vient on de faire ?

•Créer une classe product qui hérite de AR::Base

•Créer un contrôleur qui gère les actions (ex: index)

•Créer des vues pour afficher la liste et les formulaires

Model

Contrôleur

Vue

Le modèle avec Active Record

•S’interface avec la base de données

•SQLite par défaut

•Plus de SQL manuel

•Plus de risque d’injections

Créer un modèle$ ruby $ ruby script/generatescript/generate model Product name: model Product name:stringstring description: description:texttext price:price:floatfloat category_id:category_id:integerinteger$ ruby $ ruby script/generatescript/generate model Product name: model Product name:stringstring description: description:texttext price:price:floatfloat category_id:category_id:integerinteger

classclass CreateProducts < CreateProducts < ActiveRecord::Migration def self.up ActiveRecord::Migration def self.up create_tablecreate_table :products do |t| :products do |t| t.string :name t.text :description t.string :name t.text :description t.float :price t.integer :category_id t.float :price t.integer :category_id t.timestamps end end def self.down t.timestamps end end def self.down drop_tabledrop_table :products endend :products endend

classclass CreateProducts < CreateProducts < ActiveRecord::Migration def self.up ActiveRecord::Migration def self.up create_tablecreate_table :products do |t| :products do |t| t.string :name t.text :description t.string :name t.text :description t.float :price t.integer :category_id t.float :price t.integer :category_id t.timestamps end end def self.down t.timestamps end end def self.down drop_tabledrop_table :products endend :products endend

db/migrate/20110322113407_create_products.rb

rake db:migrate

Configuration & log

@products@products = = ProductProduct.all.all@products@products = = ProductProduct.all.all

Dans le contrôleurlog/development.log

Product Load (0.3ms) Product Load (0.3ms) SELECT * FROM SELECT * FROM "products""products"

Product Load (0.3ms) Product Load (0.3ms) SELECT * FROM SELECT * FROM "products""products"

config/database.yml

development: adapter: development: adapter: sqlite3 database: sqlite3 database: db/development.sqlite3 db/development.sqlite3 pool: 5 timeout: 5000pool: 5 timeout: 5000

development: adapter: development: adapter: sqlite3 database: sqlite3 database: db/development.sqlite3 db/development.sqlite3 pool: 5 timeout: 5000pool: 5 timeout: 5000

Adaptateurs pour MySQL, Postgresql, oracle…

Créer un produit

c = Category.firstc = Category.firstProduct.Product.createcreate({ ({

:name => "Ruby 'on Rails", # injection SQL:name => "Ruby 'on Rails", # injection SQL:description => "First book on RoR", :description => "First book on RoR", :price => 10.0, :price => 10.0, :category => c }):category => c })

c = Category.firstc = Category.firstProduct.Product.createcreate({ ({

:name => "Ruby 'on Rails", # injection SQL:name => "Ruby 'on Rails", # injection SQL:description => "First book on RoR", :description => "First book on RoR", :price => 10.0, :price => 10.0, :category => c }):category => c })

Product Create (0.8ms)Product Create (0.8ms) INSERT INTO "products" INSERT INTO "products" ("name", "price", "created_at", "updated_at", "category_id", ("name", "price", "created_at", "updated_at", "category_id", "description") VALUES"description") VALUES('Ruby ''on Rails', 10.0, '2011-03-22 18:13:07', '2011-03-22 ('Ruby ''on Rails', 10.0, '2011-03-22 18:13:07', '2011-03-22 18:13:07', 1, 'First book on RoR')18:13:07', 1, 'First book on RoR')

Product Create (0.8ms)Product Create (0.8ms) INSERT INTO "products" INSERT INTO "products" ("name", "price", "created_at", "updated_at", "category_id", ("name", "price", "created_at", "updated_at", "category_id", "description") VALUES"description") VALUES('Ruby ''on Rails', 10.0, '2011-03-22 18:13:07', '2011-03-22 ('Ruby ''on Rails', 10.0, '2011-03-22 18:13:07', '2011-03-22 18:13:07', 1, 'First book on RoR')18:13:07', 1, 'First book on RoR')

log/development.log

Encore trop complexe ?

•Create

•Read

•Update

•Delete

Besoin d’un outil qui gère le CRUD en 1 commande:

Scaffold

Plus qu’une migration

•Et vous pouvez:

•Créer des produits (Create)

•Les lister et afficher (Read)

•Les mettre à jour (Update)

•Et les supprimer (Delete)

En images

Les relationsclass class CategoryCategory < <

ActiveRecord::BaseActiveRecord::Basehas_manyhas_many :products :products

endend

class class CategoryCategory < < ActiveRecord::BaseActiveRecord::Base

has_manyhas_many :products :productsendend

Une catégorie possède n produits

class class ProductProduct < < ActiveRecord::BaseActiveRecord::Base

belongs_tobelongs_to :category :categoryendend

class class ProductProduct < < ActiveRecord::BaseActiveRecord::Base

belongs_tobelongs_to :category :categoryendend

Un produit appartient à une catégorie

class class ProductProduct < < ActiveRecord::BaseActiveRecord::Base

has_and_belongs_to_manyhas_and_belongs_to_many :ca :categorytegory

endend

class class ProductProduct < < ActiveRecord::BaseActiveRecord::Base

has_and_belongs_to_manyhas_and_belongs_to_many :ca :categorytegory

endend

Une catégorie possède n produits et un produit n catégories

ou

Les finderProduct.find(Product.find(:all:all, , :conditions:conditions => { :price => => { :price => 18 })18 })Product.find(Product.find(:first:first))Product.find(Product.find(:last:last))

Product.find(Product.find(:all:all, , :conditions:conditions => { :price => => { :price => 18 })18 })Product.find(Product.find(:first:first))Product.find(Product.find(:last:last))

Plusieurs finder dynamiques:

Product.Product.find_by_namefind_by_name(‘rails’)(‘rails’)Product.Product.find_all_by_namefind_all_by_name(‘rails’)(‘rails’)Product.Product.find_by_name_and_pricefind_by_name_and_price(‘book’, 18)(‘book’, 18)

Product.Product.find_by_namefind_by_name(‘rails’)(‘rails’)Product.Product.find_all_by_namefind_all_by_name(‘rails’)(‘rails’)Product.Product.find_by_name_and_pricefind_by_name_and_price(‘book’, 18)(‘book’, 18)

Product.find(:all, Product.find(:all, :joins:joins => :category, => :category, :conditions:conditions => { :categories => { :name => => { :categories => { :name => "books" }})"books" }})

Product.find(:all, Product.find(:all, :joins:joins => :category, => :category, :conditions:conditions => { :categories => { :name => => { :categories => { :name => "books" }})"books" }})

Jointure:

Les scopeclass class ProductProduct < ActiveRecord::Base < ActiveRecord::Base

named_scopenamed_scope :recent, :recent, lambdalambda { { { { :conditions:conditions => ["created_at > ?", => ["created_at > ?", 5.days.ago5.days.ago] } } ] } } named_scopenamed_scope :limit, :limit, lambdalambda { |n| { { |n| { :limit:limit => n } } => n } } named_scopenamed_scope :ordered_by_name, { :ordered_by_name, { :order:order => "NAME ASC" } => "NAME ASC" }endend

class class ProductProduct < ActiveRecord::Base < ActiveRecord::Base named_scopenamed_scope :recent, :recent, lambdalambda { { { { :conditions:conditions => ["created_at > ?", => ["created_at > ?", 5.days.ago5.days.ago] } } ] } } named_scopenamed_scope :limit, :limit, lambdalambda { |n| { { |n| { :limit:limit => n } } => n } } named_scopenamed_scope :ordered_by_name, { :ordered_by_name, { :order:order => "NAME ASC" } => "NAME ASC" }endend

Invoquer les scope, de façon chaînable

Product.Product.recentrecent..ordered_by_nameordered_by_name..limitlimit(2)(2)Product Load (0.3ms)Product Load (0.3ms) SELECT * FROM "products" WHERE (created_at > SELECT * FROM "products" WHERE (created_at > '2011-03-17 21:08:24') ORDER BY NAME ASC LIMIT 2'2011-03-17 21:08:24') ORDER BY NAME ASC LIMIT 2

Product.Product.recentrecent..ordered_by_nameordered_by_name..limitlimit(2)(2)Product Load (0.3ms)Product Load (0.3ms) SELECT * FROM "products" WHERE (created_at > SELECT * FROM "products" WHERE (created_at > '2011-03-17 21:08:24') ORDER BY NAME ASC LIMIT 2'2011-03-17 21:08:24') ORDER BY NAME ASC LIMIT 2

Les validationsclass class ProductProduct < ActiveRecord::Base < ActiveRecord::Base

belongs_to :categorybelongs_to :categoryvalidates_presence_ofvalidates_presence_of :name, :category_id :name, :category_idvalidates_numericality_ofvalidates_numericality_of :price :price

class class ProductProduct < ActiveRecord::Base < ActiveRecord::Basebelongs_to :categorybelongs_to :categoryvalidates_presence_ofvalidates_presence_of :name, :category_id :name, :category_idvalidates_numericality_ofvalidates_numericality_of :price :price

et bien d’autres:

validates_confirmation_ofvalidates_exclusion_ofvalidates_format_ofvalidates_inclusion_ofvalidates_length_of…

Le contrôleur avec ActionController

•L’interface entre le modèle et la vue

•Association automatique méthode / vue

•@variable directement disponible dans la vue

•Possibilité de filtrer des actions

Le routeurconfig/routes.rb

ActionController::Routing::Routes.draw do |map|ActionController::Routing::Routes.draw do |map|# # RESTfulRESTful /categories/1/products/1/map. /categories/1/products/1/map.resourcesresources :categories do | :categories do |category|category|

category.resources :productscategory.resources :productsendend# route nommée# route nomméemap.map.purchasepurchase 'products/:id/purchase', :controller => 'catalog', :action 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'=> 'purchase'# namespace# namespacemap.map.namespacenamespace :admin do |admin| :admin do |admin|

# /admin/products# /admin/productsadmin.resource :productsadmin.resource :products

endend# route par défaut# route par défautmap.map.rootroot :controller => 'products' :controller => 'products'endend

ActionController::Routing::Routes.draw do |map|ActionController::Routing::Routes.draw do |map|# # RESTfulRESTful /categories/1/products/1/map. /categories/1/products/1/map.resourcesresources :categories do | :categories do |category|category|

category.resources :productscategory.resources :productsendend# route nommée# route nomméemap.map.purchasepurchase 'products/:id/purchase', :controller => 'catalog', :action 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'=> 'purchase'# namespace# namespacemap.map.namespacenamespace :admin do |admin| :admin do |admin|

# /admin/products# /admin/productsadmin.resource :productsadmin.resource :products

endend# route par défaut# route par défautmap.map.rootroot :controller => 'products' :controller => 'products'endend

Les routesrake routes categories categories GETGET /categories(.:format) /categories(.:format) {:controller=>"categories", :action=>"index"} {:controller=>"categories", :action=>"index"} POSTPOST /categories(.:format) {:controller=>"categories", :action=>"create"} /categories(.:format) {:controller=>"categories", :action=>"create"} new_category GET /categories/new(.:format) new_category GET /categories/new(.:format) {:controller=>"categories", :action=>"new"}edit_category GET {:controller=>"categories", :action=>"new"}edit_category GET /categories/:id/edit(.:format) {:controller=>"categories", :action=>"edit"} /categories/:id/edit(.:format) {:controller=>"categories", :action=>"edit"} category GET /categories/:id(.:format) category GET /categories/:id(.:format) {:controller=>"categories", :action=>"show"} {:controller=>"categories", :action=>"show"} PUTPUT /categories/:id(.:format) {:controller=>"categories", :action=>"update"} /categories/:id(.:format) {:controller=>"categories", :action=>"update"} DELETEDELETE /categories/:id(.:format) /categories/:id(.:format) {:controller=>"categories", :action=>"destroy"} products GET {:controller=>"categories", :action=>"destroy"} products GET /products(.:format) {:controller=>"products", :action=>"index"} /products(.:format) {:controller=>"products", :action=>"index"} POST /products(.:format) POST /products(.:format) {:controller=>"products", :action=>"create"} new_product GET {:controller=>"products", :action=>"create"} new_product GET /products/new(.:format) {:controller=>"products", :action=>"new"} /products/new(.:format) {:controller=>"products", :action=>"new"} edit_product GET /products/:id/edit(.:format) edit_product GET /products/:id/edit(.:format) {:controller=>"products", :action=>"edit"} product GET {:controller=>"products", :action=>"edit"} product GET /products/:id(.:format) {:controller=>"products", :action=>"show"} /products/:id(.:format) {:controller=>"products", :action=>"show"} PUT /products/:id(.:format) PUT /products/:id(.:format) {:controller=>"products", :action=>"update"} DELETE {:controller=>"products", :action=>"update"} DELETE /products/:id(.:format) {:controller=>"products", :action=>"destroy"} /products/:id(.:format) {:controller=>"products", :action=>"destroy"} /:controller/:action/:id /:controller/:ac /:controller/:action/:id /:controller/:action/:id(.:format) tion/:id(.:format) rootroot / / {:controller=>"products", :action=>"index"}{:controller=>"products", :action=>"index"}

categories categories GETGET /categories(.:format) /categories(.:format) {:controller=>"categories", :action=>"index"} {:controller=>"categories", :action=>"index"} POSTPOST /categories(.:format) {:controller=>"categories", :action=>"create"} /categories(.:format) {:controller=>"categories", :action=>"create"} new_category GET /categories/new(.:format) new_category GET /categories/new(.:format) {:controller=>"categories", :action=>"new"}edit_category GET {:controller=>"categories", :action=>"new"}edit_category GET /categories/:id/edit(.:format) {:controller=>"categories", :action=>"edit"} /categories/:id/edit(.:format) {:controller=>"categories", :action=>"edit"} category GET /categories/:id(.:format) category GET /categories/:id(.:format) {:controller=>"categories", :action=>"show"} {:controller=>"categories", :action=>"show"} PUTPUT /categories/:id(.:format) {:controller=>"categories", :action=>"update"} /categories/:id(.:format) {:controller=>"categories", :action=>"update"} DELETEDELETE /categories/:id(.:format) /categories/:id(.:format) {:controller=>"categories", :action=>"destroy"} products GET {:controller=>"categories", :action=>"destroy"} products GET /products(.:format) {:controller=>"products", :action=>"index"} /products(.:format) {:controller=>"products", :action=>"index"} POST /products(.:format) POST /products(.:format) {:controller=>"products", :action=>"create"} new_product GET {:controller=>"products", :action=>"create"} new_product GET /products/new(.:format) {:controller=>"products", :action=>"new"} /products/new(.:format) {:controller=>"products", :action=>"new"} edit_product GET /products/:id/edit(.:format) edit_product GET /products/:id/edit(.:format) {:controller=>"products", :action=>"edit"} product GET {:controller=>"products", :action=>"edit"} product GET /products/:id(.:format) {:controller=>"products", :action=>"show"} /products/:id(.:format) {:controller=>"products", :action=>"show"} PUT /products/:id(.:format) PUT /products/:id(.:format) {:controller=>"products", :action=>"update"} DELETE {:controller=>"products", :action=>"update"} DELETE /products/:id(.:format) {:controller=>"products", :action=>"destroy"} /products/:id(.:format) {:controller=>"products", :action=>"destroy"} /:controller/:action/:id /:controller/:ac /:controller/:action/:id /:controller/:action/:id(.:format) tion/:id(.:format) rootroot / / {:controller=>"products", :action=>"index"}{:controller=>"products", :action=>"index"}

Les contrôleursclass ProductsController < ApplicationControllerclass ProductsController < ApplicationController

before_filterbefore_filter :authenticate, :authenticate, :only:only => :index => :index

def def indexindex @products@products = Product.all #disponible dans la vue = Product.all #disponible dans la vuerespond_torespond_to do |format| do |format|

# GET /products# GET /productsformat.html # index.html.erbformat.html # index.html.erb# GET /products.xml# GET /products.xmlformat.xml { format.xml { renderrender :xml => :xml => @products@products } } endendendend

privateprivatedef def authenticateauthenticate# do some stuff# do some stuffendend

endend

class ProductsController < ApplicationControllerclass ProductsController < ApplicationControllerbefore_filterbefore_filter :authenticate, :authenticate, :only:only => :index => :index

def def indexindex @products@products = Product.all #disponible dans la vue = Product.all #disponible dans la vuerespond_torespond_to do |format| do |format|

# GET /products# GET /productsformat.html # index.html.erbformat.html # index.html.erb# GET /products.xml# GET /products.xmlformat.xml { format.xml { renderrender :xml => :xml => @products@products } } endendendend

privateprivatedef def authenticateauthenticate# do some stuff# do some stuffendend

endend

La vue avec ActionView

•Les vues : pas forcément du HTML

•Layout

•Partials

•Helpers

Les types de vues

i18n des vuesapp/views/products/app/views/products/index.fr.html.erbindex.fr.html.erbapp/views/products/app/views/products/index.es.html.erbindex.es.html.erb

app/views/products/app/views/products/index.fr.html.erbindex.fr.html.erbapp/views/products/app/views/products/index.es.html.erbindex.es.html.erb

Rails >= 2.3

Différents formats

app/views/products/app/views/products/index.iphone.erbindex.iphone.erbapp/views/products/app/views/products/index.xml.erbindex.xml.erb

app/views/products/app/views/products/index.iphone.erbindex.iphone.erbapp/views/products/app/views/products/index.xml.erbindex.xml.erb

Le layoutSquelette de base app/views/app/views/layoutslayouts//

application.html.erbapplication.html.erbapp/views/app/views/layoutslayouts//application.html.erbapplication.html.erb

Modifiable depuis le contrôleur

<!DOCTYPE…><html><body><<!DOCTYPE…><html><body><%= %= yieldyield %></body></html> %></body></html><!DOCTYPE…><html><body><<!DOCTYPE…><html><body><%= %= yieldyield %></body></html> %></body></html>

Les partialsDes morceaux de vue

<!DOCTYPE…><html><body><!DOCTYPE…><html><body><%= render :<%= render :partialpartial => => ‘products/sidebar’ %><%= ‘products/sidebar’ %><%= yieldyield %></body></html>%></body></html>

<!DOCTYPE…><html><body><!DOCTYPE…><html><body><%= render :<%= render :partialpartial => => ‘products/sidebar’ %><%= ‘products/sidebar’ %><%= yieldyield %></body></html>%></body></html>

app/views/products/app/views/products/_sidebar.html.erb_sidebar.html.erbapp/views/products/app/views/products/_sidebar.html.erb_sidebar.html.erb

Exemple de vue: liste

<h1>Listing categories</h1><table> <tr> <h1>Listing categories</h1><table> <tr> <th>Name</th> </tr><% @categories.each do |category| <th>Name</th> </tr><% @categories.each do |category| %> <tr> <td><%=h %> <tr> <td><%=h truncatetruncate(category.name, :size => 10) (category.name, :size => 10) %></td> <td><%= link_to 'Show', category %></td> %></td> <td><%= link_to 'Show', category %></td> <td><%= link_to 'Edit', <td><%= link_to 'Edit', edit_category_pathedit_category_path(category) (category) %></td> <td><%= link_to 'Destroy', category, :%></td> <td><%= link_to 'Destroy', category, :confirmconfirm => => 'Are you sure?', :'Are you sure?', :methodmethod => :delete %></td> </tr><% end => :delete %></td> </tr><% end %></table><br /><%= link_to 'New category', %></table><br /><%= link_to 'New category', new_category_path %>new_category_path %>

<h1>Listing categories</h1><table> <tr> <h1>Listing categories</h1><table> <tr> <th>Name</th> </tr><% @categories.each do |category| <th>Name</th> </tr><% @categories.each do |category| %> <tr> <td><%=h %> <tr> <td><%=h truncatetruncate(category.name, :size => 10) (category.name, :size => 10) %></td> <td><%= link_to 'Show', category %></td> %></td> <td><%= link_to 'Show', category %></td> <td><%= link_to 'Edit', <td><%= link_to 'Edit', edit_category_pathedit_category_path(category) (category) %></td> <td><%= link_to 'Destroy', category, :%></td> <td><%= link_to 'Destroy', category, :confirmconfirm => => 'Are you sure?', :'Are you sure?', :methodmethod => :delete %></td> </tr><% end => :delete %></td> </tr><% end %></table><br /><%= link_to 'New category', %></table><br /><%= link_to 'New category', new_category_path %>new_category_path %>

Exemple de vue: édition

<% <% form_forform_for(@product) do |f| %> <%= f.error_messages %> (@product) do |f| %> <%= f.error_messages %> <p> <%= f.label :name %><br /> <%= f.<p> <%= f.label :name %><br /> <%= f.text_fieldtext_field :name :name %> </p><p> <%= f.label :description %><br /> <%= %> </p><p> <%= f.label :description %><br /> <%= f.f.text_areatext_area :description %> </p><p> <%= f.label :available :description %> </p><p> <%= f.label :available %><br /> <%= f.%><br /> <%= f.check_boxcheck_box :available %> </p><p> <%= :available %> </p><p> <%= f.label :category_id %><br /> <%= f.f.label :category_id %><br /> <%= f.selectselect :category_id, :category_id, (Category.all.map do |c| [c.name, c.id] end) %> </p><p> <(Category.all.map do |c| [c.name, c.id] end) %> </p><p> <%= f.%= f.submitsubmit 'Update' %> </p><% end %><%= 'Update' %> </p><% end %><%= link_tolink_to 'Show', @product %> |<%= 'Show', @product %> |<%= link_tolink_to 'Back', products_path %> 'Back', products_path %>

<% <% form_forform_for(@product) do |f| %> <%= f.error_messages %> (@product) do |f| %> <%= f.error_messages %> <p> <%= f.label :name %><br /> <%= f.<p> <%= f.label :name %><br /> <%= f.text_fieldtext_field :name :name %> </p><p> <%= f.label :description %><br /> <%= %> </p><p> <%= f.label :description %><br /> <%= f.f.text_areatext_area :description %> </p><p> <%= f.label :available :description %> </p><p> <%= f.label :available %><br /> <%= f.%><br /> <%= f.check_boxcheck_box :available %> </p><p> <%= :available %> </p><p> <%= f.label :category_id %><br /> <%= f.f.label :category_id %><br /> <%= f.selectselect :category_id, :category_id, (Category.all.map do |c| [c.name, c.id] end) %> </p><p> <(Category.all.map do |c| [c.name, c.id] end) %> </p><p> <%= f.%= f.submitsubmit 'Update' %> </p><% end %><%= 'Update' %> </p><% end %><%= link_tolink_to 'Show', @product %> |<%= 'Show', @product %> |<%= link_tolink_to 'Back', products_path %> 'Back', products_path %>

Autour de Rails

•Communauté active

•Autour de github

•Gems

•Authentification

•Pagination

•…

config/environment.rbRails::Initializer.run do |Rails::Initializer.run do |config|config|

config.gemconfig.gem ‘hpricot’ ‘hpricot’endend

Rails::Initializer.run do |Rails::Initializer.run do |config|config|

config.gemconfig.gem ‘hpricot’ ‘hpricot’endend

Documentation

•http://www.rubyonrails.org/

•http://guides.rubyonrails.org/

•http://apidock.com/rails

•http://railsforzombies.org/

•http://groups.google.com/group/railsfrance

Merci, c’est à vous !

•Des questions ?

_fuse mcatty@synbioz.com