Présentation de ActiveRecord

Nicolas Chuche

Excuses

  • désolé je n'ai que 20 minutes
  • et 23 transparents (sans compter celui-ci)
  • donc accrochez vous

J'adore SQL et Perl

    my $query_base =
      "SELECT ACL.id from ACL, Groups, Principals, CachedGroupMembers WHERE  " .
      "(ACL.RightName = 'SuperUser' OR  ACL.RightName = '$right') "
      # Never find disabled groups.
      . "AND Principals.Disabled = 0 "
      . "AND CachedGroupMembers.Disabled = 0 "
      # We always grant rights to Groups
      . "AND Principals.id = Groups.id "
      . "AND Principals.PrincipalType = 'Group' "
      # See if the principal is a member of the group recursively or _is the rightholder_
      # never find recursively disabled group members
      # also, check to see if the right is being granted _directly_ to this principal,
      #  as is the case when we want to look up group rights
      . "AND Principals.id = CachedGroupMembers.GroupId "
      . "AND CachedGroupMembers.MemberId = ". $self->Id ." "
      # Make sure the rights apply to the entire system or to the object in question
      . "AND ($check_objects) ";
    # The groups query does the query based on group membership and individual user rights
    my $groups_query = $query_base
      # limit the result set to groups of types ACLEquivalence (user),
      # UserDefined, SystemInternal and Personal. All this we do
      # via (ACL.PrincipalType = 'Group') condition
      . "AND ACL.PrincipalId = Principals.id "
      . "AND ACL.PrincipalType = 'Group' ";
    $self->_Handle->ApplyLimits( \$groups_query, 1 ); #only return one result
    my $hitcount = $self->_Handle->FetchResult($groups_query);
    return 1 if $hitcount; # get out of here if success
    # The roles query does the query based on roles
    my $roles_query = $query_base
      . "AND ACL.PrincipalType = Groups.Type "
      . "AND ($check_roles) ";
    $self->_Handle->ApplyLimits( \$roles_query, 1 ); #only return one result

Qu'est-ce que activerecord

  • un ORM en ruby

  • développé pour jouer la couche Modèle de Ruby on Rails

  • utilisable indépendamment de RoR

Grands principes

  • DRY : Don't Repeat Yourself
  • Convention over Configuration
    • pas de XML

    • réflexivité et extension dynamique au chargement

    • magique n'est pas une insulte

  • la base n'est pas sale
    • utilisez du SQL dans les cas tordus et/ou pour la performance

    • pourquoi dupliquer la description de vos données, votre base la connait

Migration

  • un DSL qui vous permet
    • de décrire votre schéma de façon agnostique

    • de migrer vos schémas facilement

    • de revenir en arrière

    • de versionner vos schémas

Migration

  • une description agnostique de vos tables et de vos index
      def self.up
        create_table "adherents" do |t|
          t.column "nom",       :string, :limit => 50, :null => false
          t.column "prenom",    :string, :limit => 50, :null => false
          t.column "naissance", :date,                 :null => false
          [...]
        end
      end

Migration

  • permettant un développement incrémental
      def self.up
        add_column :inscriptions, :confirmation, :boolean
        drop_table :asupprimer
      end
  • simple à mettre en oeuvre
      % rake db:migrate          
      (in /home/nc/travail/rails/fpw2006/test)
      == ModificationSchema: migrating ==============================================
      -- add_column(:inscriptions, :confirmation, :boolean)
         -> 0.1418s
      -- drop_table(:asupprimer)
         -> 0.0148s
      == ModificationSchema: migrated (0.2641s) =====================================

Déclaration du modèle relationnel

  • la classe
       class Rando < ActiveRecord::Base
       end
  • est automatiquement mappée à la table « randos » :
      CREATE TABLE "randos" (
        "id" integer NOT NULL PRIMARY KEY,
        "jour" date NOT NULL,
        "titre" varchar(250) NOT NULL,
        "distance" integer NOT NULL,
        "rythme" varchar(1) NOT NULL,
        "publication" date NOT NULL,
        "descriptif" text NOT NULL
      );
  • oui la table est au pluriel...

Les méthodes créées par ce modèle

  • quelques méthodes classiques
      Rando.find(:all)
      Rando.find(:first)
  • et d'autres moins classiques
      Rando.find_by_id(1)
      Rando_find_by_rythme('L')

Les relations

Déclarées par de simples « macros » :

  • has_one
  • has_many
  • belongs_to
  • has_and_belongs_to_many

Les relations

  • la classe Animateur
      class Animateur < ActiveRecord::Base
        belongs_to :adherent
        has_and_belongs_to_many :randos
        has_many   :filleuls,
                   :class_name => "Animateur",
                   :foreign_key => "parrain_id"
        belongs_to :parrain,
                   :class_name  => "Animateur",
                   :foreign_key => "parrain_id"
      end
  • oui certains noms sont au pluriel

Les relations

  • Tout est bien sur configurable à loisir :
       class Inscription < ActiveRecord::Base
    	belongs_to :adherent,
    		   :class_name  => "Personne",
    		   :foreign_key => "identifiant",
    		   :conditions  => "attribute is not null"
       end
  • mais si vous créez votre schéma, collez aux conventions

Les requêtes

  • créer un objet comme ça
      ad = Adherent.new
      ad.nom    = "De Mesmaeker"
      ad.prenom = "Aimé"
      ad.save
  • ou bien comme ça
      ad = Adherent.new(:nom => "De Mesmaeker", :prenom => "...)
      ad.save
  • ou encore
      ad = Adherent.create(:nom => "De Mesmaeker", :prenom => "...)

Les requêtes

  • une requête simple
      >> Adherent.find(:all).each { |a| puts a.nom }
      Lagaffe
      Le Rouge
      Prunelle
  • une requête plus évoluée
      >> Adherent.find_by_nom_and_prenom("Lagaffe", "Gaston",
    				     :select => "nom")
      => #<Adherent:0xb74cfb8c @attributes={"nom"=>"Lagaffe"}>
  • encore plus évoluée
      >> Adherent.find(:all, :conditions 
              => ["echeance > ? and naissance < ?",
                  Time.now, Time.local(1970,1,1)])
      => [#<Adherent:0xb74a934c @attributes={"nom"=>"Lagaffe"...

Les requêtes

  • Modifier un enregistrement
      >> a = Adherent.find_by_nom("Lagaffe")
      => #<Adherent:0xb75f1330 ... "naissance"=>"2004-04-06"
      >> a.naissance="06-01-1966"
      => "06-01-1966"
      >> a.save
      => true
      >> Adherent.find_by_nom("Lagaffe").naissance.to_s
      => "1966-01-06"
  • supprimer un enregistrement
      >> a = Adherent.find_by_nom("Le Rouge")
      => #<Adherent:0xb73dc84c ...
      >> a.destroy
      >> Adherent.find_by_nom("Le Rouge")
      => nil

Les requêtes

  • requêtes et relation inter-tables
  • ajouter un animateur
      >> Rando.find(1).animateurs << Animateur.find(1)
      => [#<Animateur:0xb745f648 @attributes={"id"=>"1",
    	...
  • trouver les animateurs encadrant la rando
      >> Rando.find(1).animateurs
      => [#<Animateur:0xb7445040 @attributes={"id"=>"1",
             "adherent_id"=>"3", "rando_id"=>"1",
             "animateur_id"=>"1", "parrain_id"=>nil,
             "date_diplome"=>"2000-04-01 00:00:00"}>]
  • trouver les noms des animateurs encadrant la rando
      >> Rando.find(1).animateurs.each { |a| puts a.adherent.nom }
      Prunelle

Les requêtes

  • vous voulez du SQL ?
      Adherent.find_by_sql("select * from adherents where ...")
  • par défaut, « lazy loading » mais on peut passer en « eagger »
      Adherent.find(:all, :include => [:animateur, :randos])

Validation des données

  • un ensemble de « macros » facilite la validation des données
       class Rando < ActiveRecord::Base
    	validates_presence_of     :jour, :titre, :rythme
    	validates_length_of       :titre, :maximum => 250
    	validates_length_of       :nom,   :in => 3..50
    	validates_format_of       :nom,   :with => /^\w+$/
    	validates_numericality_of :distance
    	validates_inclusion_of    :rythme, :in %w{L M S R}
       end
  • cet ensemble peut bien sur être étendu
      class Rando < ActiveRecord::Base
        def validate
          ...
        end
      end

les callbacks

  • un certain nombre de callback

    before_validation, before_validation_on_create, before_validation_on_update after_validation, before_save, before_create, after_save, after_update, after_create, before_destroy, after_destroy

  • permettent plein de choses intéressantes :
      class Adherent < ActiveRecord::Base
        def before_create
          self.adhesion ||= Time.now
        end
        def after_create
          logger.info "Adhérent #{adherent.id} modifié"
        end
      end

les transactions

(Désolé, je n'avais pas trouvé d'exemple dans le thème)

  • ActiveRecord peut laisser le SGBD gérer les transactions
      Compte.transaction do
        david.retrait(100)
        book.depot(100)
      end
  • ou bien les gérer avec le SGBD
      Compte.transaction(david,book) do
        david.retrait(100)
        book.depot(100)
      end

Extensions

  • des plugins permettent d'étendre le fonctionnement d'ActiveRecord avec entre autre :
    • la gestion des clefs étrangères

    • de valider au niveau du modèle les contraintes imposées dans le schéma de la base

    • une interface d'administration (streamlined)

    • ...

Je manque de temps

  • mais je veux citer rapidement :
  • act_as_list, act_as_tree, gestion des versions d'objet, ...
  • héritage, observateurs
  • PostgreSQL, Mysql, sqlite, Oracle, DB2, SQLServer, ODBC

Pourquoi un perliste sur Ruby et RoR ?

  • Ruby et RoR ont vraiment quelque chose de spécial (je dirais quelque chose de perlish...)
  • les différentes couches de RoR sont bien intégrées et homogènes (ne me parlez pas de hibernate/struts/... ou catalyst/cdbi/...)
  • il y a de la documentation de bonne qualité disponible

FIN

  • ayez du fun
  • Merci à Maddingue pour ses CSS