Ruby on Rails Supinfo 2011
A propos Martin Catty, fondateur de Synbioz Rubyiste depuis 2006 _fuse
Ruby avant Rails Ne partez pas sans vos bagages ! Rails a aidé à lessor de Ruby Mais pas de framework sans langage
Ruby On ne va pas (ou peu) parler de: I/O Réseau Thread Test Debugger Proc / lambda Exceptions Garbage VM Implémentations
LHistoire Créé par Yukihiro Matsumoto (Matz) Release publique en 2005
Un langage Plus objet que python Plus puissant que perl Fun
Les versions Stable actuelle: dans la branche (p250) fonctionne avec Rails 2 et 3 < = Rails 2 > = Rails 3
Le fameux hello world class Hello { public static void main(String [] args) { system.out.println(Hello);}} class Hello { public static void main(String [] args) { system.out.println(Hello); } } JavaRuby puts Hello
Tout est objet 2.times { puts "Bonjour Supinfo." } => Bonjour Supinfo 3.upto(5) { |i| puts i } => 3 => 4 => 5 p 1.zero? => false => 39.+(3)
Les variables class A MAX = 42 = 0 def = name += 1 end def self.instances class A MAX = 42 = 0 def = name += 1 end def self.instances A.new("a")A.new("b")A.ne w("c")A.new("d")A.new("e") p A.instances #=> 5
Itérations a = 1..9 for i in a puts i end a = 1..9 for i in a puts i end a = 1..9 a.each { |i| puts i } a = 1..9 a.each { |i| puts i } => 1…9 ou i = 0loop do i += 1 puts i break if 10 == iend i = 0loop do i += 1 puts i break if 10 == iend 1.upto(10) do |i| next if i.odd? # pas d'impair en ruby puts iend 1.upto(10) do |i| next if i.odd? # pas d'impair en ruby puts iend => 2, 4, 6, 8, 10 => 1…9 1.upto(2) do |i| v = rand(2) retry if v.zero?end 1.upto(2) do |i| v = rand(2) retry if v.zero?end
Conditions if index == 1 elseend elseend puts 0 if index.zero? puts not 0 unless index.zero? def what_is_it?(a) case a when 1..2 puts "1 or 2" when 3 puts "3" when /4.*/ # Regexp puts "something starting with 4." when "foo" puts "foo" else puts "I don't know." endend 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/2 lost = [8, 15, 16, 23] lost << 42 # push lost.unshift(4) # [4, 8, 15, 16, 23, 42] lost = [8, 15, 16, 23] lost << 42 # push lost.unshift(4) # [4, 8, 15, 16, 23, 42] lost << nil << nil # [4, 8, 15, 16, 23, 42, nil, nil] lost << nil << nil # [4, 8, 15, 16, 23, 42, nil, nil] lost.compact! # [4, 8, 15, 16, 23, 42] lost.compact! # [4, 8, 15, 16, 23, 42] lost << [4, 8] # [4, 8, 15, 16, 23, 42, [4, 8]] lost << [4, 8] # [4, 8, 15, 16, 23, 42, [4, 8]] lost.flatten!.uniq! # [4, 8, 15, 16, 23, 42] lost.flatten!.uniq! # [4, 8, 15, 16, 23, 42] lost.index(23) # 4 lost.index(23) # 4 lost.shuffle # [16, 23, 42, 4, 15, 8] [5, 3, 7, 39, 1, 15].sort # [1, 3, 5, 7, 15, 39] lost.shuffle # [16, 23, 42, 4, 15, 8] [5, 3, 7, 39, 1, 15].sort # [1, 3, 5, 7, 15, 39] ('a'..'z').to_a ["a", "b", "c", "d"…] ('a'..'z').to_a ["a", "b", "c", "d"…] lost.at(0) # 4 lost[-1] # 42 lost.at(0) # 4 lost[-1] # 42 11
Tableaux 2/2 double_lost = lost.map { |v| v * 2 } # => [8, 16, 30, 32, 46, 84] double_lost = lost.map { |v| v * 2 } # => [8, 16, 30, 32, 46, 84] # lost: [4, 8, 15, 16, 23, 42] double_lost - lost # => [30, 32, 46, 84] # lost: [4, 8, 15, 16, 23, 42] double_lost - lost # => [30, 32, 46, 84] # intersection double_lost & lost # [8, 16] # intersection double_lost & lost # [8, 16] # jointure (double_lost | lost).sort # [4, 8, 15, 16, 23, 30, 32, 42, 46, 84] # jointure (double_lost | lost).sort # [4, 8, 15, 16, 23, 30, 32, 42, 46, 84]
String str = "a"str.succ# => "b" str = "a"str.succ# => "b" # Interpolationputs "foo #{str}"# => foo bar # Interpolationputs "foo #{str}"# => foo bar # attention, en 1.8 str = "éhé" str.size# => 5 et non 3 str[0]# 195 et non é (code ascii) # patché dans rails str.mb_chars[0] # é str.mb_chars.size # 3 # attention, en 1.8 str = "éhé" str.size# => 5 et non 3 str[0]# 195 et non é (code ascii) # patché dans rails str.mb_chars[0] # é str.mb_chars.size # 3 str = "bar" puts 'foo #{str}'# => foo #{str} str = "bar" puts 'foo #{str}'# => foo #{str} str = "foo\n"str.chomp# => "foo" str = "foo\n"str.chomp# => "foo" str = "foo"str.chop# => "fo" str = "foo"str.chop# => "fo" "supinfo".capitalize # => "Supinfo" "supinfo".upcase # => "SUPINFO" "supinfo".capitalize # => "Supinfo" "supinfo".upcase # => "SUPINFO"
Hash h = { :a => 'a', :b => 42, :c => { :d => 'f' } } h2 = { :a => 'foo' } h.merge(h2) => {:a=>"foo", :b=>42, :c=>{:d=>"f"}} h2 = { :a => 'foo' } h.merge(h2) => {:a=>"foo", :b=>42, :c=>{:d=>"f"}} Association clé / valeur
Class: les constructeurs class A def A.new endendA.new class A def A.new endendA.new class B def self.new endend B.new class B def self.new endend B.new class C def initialize endendC.new endendC.new class C def initialize endendC.new class D # pas de constructeur multiple def initialize; end def initialize(*args) endend D.new # => KOD.new(1) # => OK class D # pas de constructeur multiple def initialize; end def initialize(*args) endend D.new # => KOD.new(1) # => OK
Class: les accesseurs class Product def initialize(name, description, = = = price end def def = name end…end class Product def initialize(name, description, = = = price end def end def = name end … end class Product attr_accessor :name attr_reader: description attr_writer :price def initialize(name, description, = = = price endend class Product attr_accessor :name attr_reader: description attr_writer :price def initialize(name, description, = = = price endend
Class: portée & héritage class Animal def initialize puts "Born to be alive." end protected def breathe? puts "inhale, exhale" true end private def speak; endend # Animal.new.speak # => fail with private method `speak' class Animal def initialize puts "Born to be alive." end protected def breathe? puts "inhale, exhale" true end private def speak; endend # Animal.new.speak # => fail with private method `speak' class Dog < Animal def alive? puts "I'm alive" if breathe? end def speak puts "woff." endend snoopy = Dog.new # Born to be alive.snoopy.speak # woff.snoopy.alive? # inhale, exhale# I'm alive class Dog < Animal def alive? puts "I'm alive" if breathe? end def speak puts "woff." endend snoopy = Dog.new # Born to be alive.snoopy.speak # woff.snoopy.alive? # inhale, exhale# I'm alive
Class: étendre # Etendre un objetstr = "foo"class false # Etendre un objetstr = "foo"class << str def blank? self !~ /\S/ endendstr.blank?# => false # Etendre une classe class String def blank? self !~ /\S/ endend" ".blank?# => true"foo".blank?# => false # Etendre une classe class String def blank? self !~ /\S/ endend" ".blank?# => true"foo".blank?# => false
Class: ce qui nexiste pas Pas dhéritage multiple Basé sur les mixins Pas dinterface Pas de classe abstraite native
Les outils [17:59:57] [~]$ irbruby p302 :001 > puts foo foo => nil ruby p302 :002 > 21 * 2 => 42 ruby p302 :008 > String.methods.sort => [" ", "==", "===", "=~", ">", …] [17:59:57] [~]$ irbruby p302 :001 > puts foo foo => nil ruby p302 :002 > 21 * 2 => 42 ruby p302 :008 > String.methods.sort => [" ", "==", "===", "=~", ">", …] irb : votre shell ruby rvm : gérer plusieurs versions de ruby
Documentation Le pickAxe: LA référence public public
Rails On ne va pas (ou peu) parler de: Test Cache Rake ActionMailer ActiveSupport I18n Déploiement Copieurs Rails 3
LHistoire 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: Rails 3 issue du merge avec merb (08/10) Framework agnostic Stable actuelle: 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
Dont repeat yourself - de code = - de bug - de code = - de maintenance - de code = + de temps
Modèle - Vue - Contrôleur Contrôle ur ModèleModèleVueVue MétierFonctionnelAffichage
Rails en vrai Pas de hello world, cest trop simple… La classique liste de produits plutôt.
Créons notre application $ rails app 3 environnements
class Product < ActiveRecord::Base end end Modèle app/models/product.rb class ProductsController < ApplicationController def = Product.all endend class ProductsController < ApplicationController def = Product.all endend Contrôleur app/controllers/products _controller.rb <html><body> </body></html><html><body> </body></html> Vue app/views/products/i ndex.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 Sinterface avec la base de données SQLite par défaut Plus de SQL manuel Plus de risque dinjections
Créer un modèle $ ruby script/generate model Product name:string description:text price:float category_id:integer class CreateProducts < ActiveRecord::Migration def self.up create_table :products do |t| t.string :name t.text :description t.float :price t.integer :category_id t.timestamps end end def self.down drop_table :products endend db/migrate/ _create_products. rb rake db:migrate
Configuration & = Product.all Dans le contrôleurlog/development.log Product Load (0.3ms) SELECT * FROM "products" config/database.yml development: adapter: sqlite3 database: db/development.sqlite3 pool: 5 timeout: 5000 Adaptateurs pour MySQL, Postgresql, oracle…
Créer un produit c = Category.first Product.create({ :name => "Ruby 'on Rails", # injection SQL :description => "First book on RoR", :price => 10.0, :category => c }) c = Category.first Product.create({ :name => "Ruby 'on Rails", # injection SQL :description => "First book on RoR", :price => 10.0, :category => c }) Product Create (0.8ms) INSERT INTO "products" Product Create (0.8ms) INSERT INTO "products" ("name", "price", "created_at", "updated_at", "category_id", "description") VALUES ('Ruby ''on Rails', 10.0, ' :13:07', ' :13:07', 1, 'First book on RoR') Product Create (0.8ms) INSERT INTO "products" Product Create (0.8ms) INSERT INTO "products" ("name", "price", "created_at", "updated_at", "category_id", "description") VALUES ('Ruby ''on Rails', 10.0, ' :13:07', ' :13:07', 1, 'First book on RoR') log/development.log
Encore trop complexe ? Create Read Update Delete Besoin dun outil qui gère le CRUD en 1 commande:
Scaffold
Plus quune 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 relations class Category < ActiveRecord::Base has_many :products end class Category < ActiveRecord::Base has_many :products end Une catégorie possède n produits class Product < ActiveRecord::Base belongs_to :category end class Product < ActiveRecord::Base belongs_to :category end Un produit appartient à une catégorie class Product < ActiveRecord::Base has_and_belongs_to_many :category end class Product < ActiveRecord::Base has_and_belongs_to_many :category end Une catégorie possède n produits et un produit n catégories ou
Les finder Product.find(:all, :conditions => { :price => 18 }) Product.find(:first) Product.find(:last) Product.find(:all, :conditions => { :price => 18 }) Product.find(:first) Product.find(:last) Plusieurs finder dynamiques: Product.find_by_name(rails) Product.find_all_by_name(rails) Product.find_by_name_and_price(book, 18) Product.find_by_name(rails) Product.find_all_by_name(rails) Product.find_by_name_and_price(book, 18) Product.find(:all, :joins => :category, :conditions => { :categories => { :name => "books" }}) Product.find(:all, :joins => :category, :conditions => { :categories => { :name => "books" }}) Jointure:
Les scope class Product < ActiveRecord::Base named_scope :recent, lambda { { :conditions => ["created_at > ?", 5.days.ago] } } named_scope :limit, lambda { |n| { :limit => n } } named_scope :recent, lambda { { :conditions => ["created_at > ?", 5.days.ago] } } named_scope :limit, lambda { |n| { :limit => n } } named_scope :ordered_by_name, { :order => "NAME ASC" } named_scope :ordered_by_name, { :order => "NAME ASC" }end class Product < ActiveRecord::Base named_scope :recent, lambda { { :conditions => ["created_at > ?", 5.days.ago] } } named_scope :limit, lambda { |n| { :limit => n } } named_scope :recent, lambda { { :conditions => ["created_at > ?", 5.days.ago] } } named_scope :limit, lambda { |n| { :limit => n } } named_scope :ordered_by_name, { :order => "NAME ASC" } named_scope :ordered_by_name, { :order => "NAME ASC" }end Invoquer les scope, de façon chaînable Product.recent.ordered_by_name.limit(2) Product Load (0.3ms) SELECT * FROM "products" WHERE (created_at > ' :08:24') ORDER BY NAME ASC LIMIT 2 Product.recent.ordered_by_name.limit(2) Product Load (0.3ms) SELECT * FROM "products" WHERE (created_at > ' :08:24') ORDER BY NAME ASC LIMIT 2
Les validations class Product < ActiveRecord::Base belongs_to :category validates_presence_of :name, :category_id validates_numericality_of :price class Product < ActiveRecord::Base belongs_to :category validates_presence_of :name, :category_id validates_numericality_of :price et bien dautres: validates_confirmation_of validates_exclusion_of validates_format_oflidates_format_of valivalidates_inclusion_of validates_length_ofdates_length_of …
Le contrôleur avec ActionController Linterface entre le modèle et la vue Association automatique méthode / directement disponible dans la vue Possibilité de filtrer des actions
Le routeur config/routes.rb ActionController::Routing::Routes.draw do |map| # RESTful /categories/1/products/1/map.resources :categories do |category| category.resources :products end # route nommée map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' # namespace map.namespace :admin do |admin| # /admin/products admin.resource :products end # route par défaut map.root :controller => 'products' end ActionController::Routing::Routes.draw do |map| # RESTful /categories/1/products/1/map.resources :categories do |category| category.resources :products end # route nommée map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' # namespace map.namespace :admin do |admin| # /admin/products admin.resource :products end # route par défaut map.root :controller => 'products' end
Les routes rake routes categories GET /categories(.:format) {:controller=>"categories", :action=>"index"} POST /categories(.:format) {:controller=>"categories", :action=>"create"} new_category GET /categories/new(.:format) {:controller=>"categories", :action=>"new"}edit_category GET /categories/:id/edit(.:format) {:controller=>"categories", :action=>"edit"} category GET /categories/:id(.:format) {:controller=>"categories", :action=>"show"} PUT /categories/:id(.:format) {:controller=>"categories", :action=>"update"} DELETE /categories/:id(.:format) {:controller=>"categories", :action=>"destroy"} products GET /products(.:format) {:controller=>"products", :action=>"index"} POST /products(.:format) {:controller=>"products", :action=>"create"} new_product GET /products/new(.:format) {:controller=>"products", :action=>"new"} edit_product GET /products/:id/edit(.:format) {:controller=>"products", :action=>"edit"} product GET /products/:id(.:format) {:controller=>"products", :action=>"show"} PUT /products/:id(.:format) {:controller=>"products", :action=>"update"} DELETE /products/:id(.:format) {:controller=>"products", :action=>"destroy"} /:controller/:action/:id /:controller/:action/:id(.:format) root / {:controller=>"products", :action=>"index"} categories GET /categories(.:format) {:controller=>"categories", :action=>"index"} POST /categories(.:format) {:controller=>"categories", :action=>"create"} new_category GET /categories/new(.:format) {:controller=>"categories", :action=>"new"}edit_category GET /categories/:id/edit(.:format) {:controller=>"categories", :action=>"edit"} category GET /categories/:id(.:format) {:controller=>"categories", :action=>"show"} PUT /categories/:id(.:format) {:controller=>"categories", :action=>"update"} DELETE /categories/:id(.:format) {:controller=>"categories", :action=>"destroy"} products GET /products(.:format) {:controller=>"products", :action=>"index"} POST /products(.:format) {:controller=>"products", :action=>"create"} new_product GET /products/new(.:format) {:controller=>"products", :action=>"new"} edit_product GET /products/:id/edit(.:format) {:controller=>"products", :action=>"edit"} product GET /products/:id(.:format) {:controller=>"products", :action=>"show"} PUT /products/:id(.:format) {:controller=>"products", :action=>"update"} DELETE /products/:id(.:format) {:controller=>"products", :action=>"destroy"} /:controller/:action/:id /:controller/:action/:id(.:format) root / {:controller=>"products", :action=>"index"}
Les contrôleurs class ProductsController < ApplicationController before_filter :authenticate, :only => :index def = Product.all #disponible dans la vue respond_to do |format| # GET /products format.html # index.html.erb # GET /products.xml format.xml { render :xml } end endprivate def authenticate # do some stuff endend class ProductsController < ApplicationController before_filter :authenticate, :only => :index def = Product.all #disponible dans la vue respond_to do |format| # GET /products format.html # index.html.erb # GET /products.xml format.xml { render :xml } end endprivate def authenticate # do some stuff endend
La vue avec ActionView Les vues : pas forcément du HTML Layout Partials Helpers
Les types de vues i18n des vues app/views/products/index.fr.html.erb app/views/products/index.es.html.erb app/views/products/index.fr.html.erb app/views/products/index.es.html.erb Rails >= 2.3 Différents formats app/views/products/index.iphone.erb app/views/products/index.xml.erb app/views/products/index.iphone.erb app/views/products/index.xml.erb
Le layout Squelette de base app/views/layouts/application.html.erb Modifiable depuis le contrôleur
Les partials Des morceaux de vue <!DOCTYPE…><html><body> products/sidebar %> products/sidebar %> <!DOCTYPE…><html><body> app/views/products/_sidebar.html. erb
Exemple de vue: liste Listing categories Name 10) %> 'Are you sure?', :method => :delete %> Listing categories Name 10) %> 'Are you sure?', :method => :delete %>
Exemple de vue: édition | |
Autour de Rails Communauté active Autour de github Gems Authentification Pagination … config/environment.rb Rails::Initializer.run do |config| config.gem hpricot end Rails::Initializer.run do |config| config.gem hpricot end
Documentation ce ce
Merci, cest à vous ! Des questions ? _fuse