Retour au sommaire du support de cours

Cette partie traite des (B)ases (D)e (D)onnées dans Ruby On Rails, et particulièrement : (Dernière modification : 2011/11/21)

Cliquez sur la table des matières pour la cacher / développer :
Les Modèles sont les classes de Ruby On Rails qui gèrent la partie Modèle du MVC.
Elles utilisent par défaut ActiveRecord pour la détection automatique de la structure des tables.

ActiveRecord est l'interface par défaut de Mappage Objet Relationnelle (Object Relational Mapping, ORM) de Ruby On Rails :
Mappage Relationnel Objet

1) Initialisation de la B.D.D. :

1.1) Étapes préalables (avec PostgreSQL) :

su - postgres

postgres@albrecht:~$ createuser rails
	#Non
	Le nouveau rôle est-il super-utilisateur ? (o/n) n
	#Oui
	Le nouveau rôle est-il autorisé à créer des bases de données ? (o/n) o
	#Non
	Le nouveau rôle est-il autorisé à créer de nouveaux rôles ? (o/n) n

postgres@albrecht:~$ alter user rails with password '<mot_de_passe>'
ou :
su - postgres
createuser -U postgres -W -P rails

1.2) Création automatique de la B.D.D. :

Rake (make de Ruby)

Création de la base de développement :
cd <projet_rails>
rake db:create
Création de toutes les B.D.D. (Dev., Test et Prod°) :
rake db:create:all

Efface la B.D.D. courante :
rake db:drop

Efface la B.D.D., la recrée, et charge le schema courant :
rake db:reset
=> aucune donnée non insérée par les migrations ne survit

 

2) Information sur la B.D.D. :

2.1) Conventions de nommage :

Conventions de nommage de base.
Casse mixte veut dire : Camel Case.
Le noms des attributs des modèles utilisent des minuscules, et des tirets de soulignement pour séparer les mots qui les composent.
Exemple :
#Commande est la classe du modèle commande
@commande = Commande.find_by_id(19719)
#@commande est une instance de cette classe en mémoire (un objet Ruby),
#qui contient les valeurs (trouvées dans la B.D.D.) de la commande sélectionnée

if @commande.prix_ttc.blank?
	@commande.prix_ttc = @commande.prix_ht * 1.196
end
@commande.save

2.2) Le Schéma donne l'état de la structure actuelle de la B.D.D. :

On peut faire le choix de son format lors de sa génération par les migrations :
Dans config/environment.rb
config.active_record.schema_format
  • :sql
  • => génération du fichier :
    db/${RAILS_ENV}_structure.sql
  • :ruby
  • => génèration du fichier :
    db/schema.rb
    Mais ce type de schema ne peut pas exprimer les éléments spécifiques aux B.D.D. :
    • foreign key constraints
    • triggers
    • stored procedures

2.3) Annotations de schema :

Ne servent que sur les plateformes de développement => installation avec un Gem, pas besoin d'un Plugin.
gem install annotate-models

cd <projet>
annotate
annotate-models
#will wipe out the comment block from the last run including all comments you add to the block

2.4) Fichiers de Migration de tables

2.4.1) Définition :

Les fichiers de migration sont des Classes Ruby qui héritent de ActiveRecord::Migration.
Elles doivent définir deux méthodes :
  • self.up
  • self.down

2.4.2) Commandes Rake :

Lance les migrations jusqu'à celle indiquée
Invoque aussi (à la fin) db:schema:dump qui m-à-j db/schema.rb pour correspondre à la structure de la B.D.D.
rake db:migrate --trace VERSION=<SSAAMMJJHHMMSS>
Défait la(les) dernière(es) migrations
rake db:rollback STEP=<nb de migrations en arrière>
Défait puis refait la(les) dernière(es) migration(s)
rake db:migrate:redo STEP=<3>
Exécute le up ou down d'une migration particulière
rake db:migrate:{up, down} VERSION=<timestamp>

2.4.3) Instructions pour les fichiers de migration :

2.4.3.1) Manipulation des tables :
  • Création / modification de table
    • Les instructions à utiliser sont : create_table / change_table

    • Exemple de change_table :
      change_table :visites do |t|
      	#ajout d'une colonne
      	t.integer :duree
      end
      

    • Voici la liste de toutes les instructions qui permettent de modifier une table :
    • change_table :<table> do |t|
      	t.column
      	t.index
      	t.timestamps
      	t.change
      	t.change_default
      	t.rename
      	t.references
      	t.belongs_to
      	t.string
      	t.text
      	t.integer
      	t.float
      	t.decimal
      	t.datetime
      	t.timestamp
      	t.time
      	t.date
      	t.binary
      	t.boolean
      	t.remove
      	t.remove_references
      	t.remove_belongs_to
      	t.remove_index
      	t.remove_timestamps
      end
      

    • Options :
      • Désactiver l'auto-génération de la colonne id :
      • :id => false
      • Problème avec la clé primaire :
      • Question : toujours d'actualité ?
        If you observe any strange behaviour in a has_and_belongs_to_many association like :
        • mangled models IDs, or
        • exceptions about conflicting IDs
        chances are you forgot that
        :primary_key
      • moteur de la B.D.D.
      • :options => "ENGINE=BLACKHOLE"
        defaut :
        :options => "ENGINE=InnoDB"

  • Suppression :
  • drop_table :<nom_table>
2.4.3.2) Manipulation des colonnes :
  • Ajout d'une colonne
  • Exemple :
    add_column :products, :part_number, :string
    
    #ou avec la syntaxe 'sexy'
    t.<type> <nom_colonne>
    
    #soit dans l'exemple
    def self.up
    	create_table :products do |t|
    		t.string :part_number
    	end
    end
    
  • Changement d'une colonne :
  • Changer le type d'une colonne :
    change_table :nom_table do |t|
    	t.change :nom_colonne, :type_colonne, {options}
    end
    
  • Renommage d'une colonne :
  • rename_column :<table> :<ancien_nom>, :<nouv_nom>
    
    #ou avec la syntaxe 'sexy'
    t.rename :<ancien_nom>, :<nouv_nom>
    
  • Suppression d'une colonne
  • remove_column :<table> :<nom_col>, :<nom_col>, ...
    
    #ou avec la syntaxe 'sexy'
    t.remove :<nom_col>, :<nom_col>, ...
    
  • Options de add_column / change_column
  • :null => false
    :default => 0
    
  • Types des colonnes :
    • :primary_key
    • Exemple :
      add_column :visites, :id, :primary_key
    • :string
    • :text
    • :integer
    • :float
    • :decimal
    • :datetime
    • :timestamp
    • :time
    • :date
    • :binary
    • :boolean
    • Types non supportés par ActiveRecord :
    • Vous pouvez créer des colonnes de types non supportés par ActiveRecord en utilisant la syntaxe moins sexy :
      t.column :name, 'polygon', :null => false
2.4.3.3) Indexes :
  • Ajout :
  • add_index :accounts, [:branch_id, :party_id], :unique => true, :name => 'by_branch_party'
    
    t.index :<nom_index>
    
  • Suppression :
  • remove_index :<nom_indexe>
    				
2.4.3.4) Migrations et associations :
  • Clés étrangères :
  • On pourra consulter les références suivantes :
    http://agilewebdevelopment.com/plugins/foreign_key_associations
    http://agilewebdevelopment.com/plugins/foreign_key_migrations
    

    l'Ajout se fait avec l'instruction :
    t.references :<modele>
  • Cas des associations polymorphiques :
  • Si l'on a une association belongs_to polymorphique alors l'instruction references ajoutera les deux colonnes nécessaires :
    #Cette classe utilise Employe ou Produit de façon polymorphique
    class Photo < ActiveRecord::Base
    	belongs_to :imageable,
    	:polymorphic => true
    end
    
    class Employe < ActiveRecord::Base
    	has_many :photos, :as => :imageable
    end
    
    class Produit < ActiveRecord::Base
    	has_many :photos, :as => :imageable
    end
    
    Dans cet exemple, l'instruction :
    t.references :imageable, :polymorphic => {:default => 'Photo'}
    Ajoutera un colonne imageable_id et une colonne de type string imageable_type avec une valeur par défaut valant : 'Photo'.

    Utilisation :
    @picture.imageable
    @product.pictures
2.4.3.5) Exécution de code SQL :
Exemple : ajout d'une clé étrangère :
Pour le self.up :
execute <<-SQL
ALTER TABLE products
ADD CONSTRAINT fk_products_categories
FOREIGN KEY (category_id)
REFERENCES categories(id)
SQL

Pour le self.down :
execute "ALTER TABLE products DROP FOREIGN KEY fk_products_categories"
2.4.3.6) Interruption des migrations irréversibles :
raise ActiveRecord::IrreversibleMigration, "<message_explicatif>"
2.4.3.7) Rafraîchissement du cache de description tables :
Pour prendre en compte une M-À-J de la structure de la table entre temps (au sein d'un fichier de migration) :
<Classe_modele>.reset_column_information
Cela permet par exemple d'initialiser un colonne à partir de calculs, juste après son ajout à une table.

2.4.4) Conventions de nommage des fichiers de migrations :

  • Tous les fichiers de migration créés par :
  • script/generate model <NomModel>
    Ont un nom de la forme :
    SSAAMMJJHHMMSS_create_nom_model.rb

  • Lors de la génération automatique avec :
  • script/generate migration
    Si le nom de la migration est de la forme :
    "AddXXXToYYY"
    ou
    "RemoveXXXFromYYY"
    et est suivi d'une liste de noms de colonnes avec leur type,
    alors le fichier de migration généré contiendra les instructions add_column ou remove_column appropriés.
    Le nom de la table (YYY) doit être au pluriel.

    Exemples :
    script/generate migration AddDetailsToProducts part_number:string price:decimal
    script/generate migration AddPositionToDiapositives position:integer
    

2.4.5) Messages informatifs dans les migrations :

  • suppress_messages
  • Supprime toutes les sorties générées par ses blocs up et down.

  • say(<message>, <indentation>)
  • Affiche en sortie le 1er paramètre, le second paramètre contrôle si il sera indenté ou pas.

  • say_with_time
  • Affiche en sortie le 1er paramètre avec l'information du temps que cela a pris pour exécuter son bloc.
    Si le bloc retourne un entier, cette méthode suppose que c'est le nombre de ligne traitées.

2.5) Remplissage de la B.D.D. :

2.5.1) Garnitures :

Les fichiers de Garniture sont des fichiers qui permettent l'initialisation des tables dans la B.D.D., de façon cohérente et simple.
Un fichier de Garniture concerne une seule table.
Les Garnitures respectent le format YAML (YAML Ain't Markup Language).
ATTENTION : Les tabulations sont interdites dans les fichiers de migration!

ATTENTION : Avant le chargement de la Garniture, la table est vidée automatiquement!!!
  • Référence :
  • C.f.:
    http://ar.rubyonrails.org/classes/Fixtures.html

  • Localisation :
  • Les fichiers de garniture se trouvent dans :
    test/fixtures
    ATTENTION : ils ne sont pas forcément utilisés que pour les tests!

  • Chargement d'un fichier de garnitures :
  • # 1) Charge une garniture depuis un fichier Ruby :
    require 'active_record/fixtures'
    Fixtures.create_fixtures("#{Rails.root}/test/fixtures", "operating_systems")
    Fixtures.create_fixtures("#{Rails.root}/test/fixtures", "users")
    
    
    # 2) Charge un fichier de garnitures dans une migration avec up and down :
    
    #db/migrate/<fichier_de_migration>.rb
    require 'active_record/fixtures'
    
    class LoadCustomerData
    	def self.up
    		down
    		directory = File.join(File.dirname(__FILE__), "data")
    		Fixtures.create_fixtures(directory, "customers")
    	end
    
    	def self.down
    		Customer.delete_all
    	end
    end
    

2.5.1.1) Utilisation de fichiers au format C.S.V. :
gem install fastercsv
#ou
rake gem:install fastercsv
#config/environment.rb :
config.gem 'fastercsv'

#dans le fichier de migration du projet :
FasterCSV.foreach("#{RAILS_ROOT}/fichier.csv") do |ligne|
	titre = ligne[0]
	next if titre == "Title"
end

2.5.2) Initialisation de la B.D.D. avec le fichier de Seeds (Graines) :

C'est un fichier qui contient des instructions Ruby d'initialisation de la B.D.D.
Il se trouve dans :
db/seeds.rb
Utilisation :
rake db:seed
Exemples :
#db/migrate/<fichier_de_migration>.rb

# Chargement du fichier de seed depuis une migration
Rake::Task["db:seed"].invoke

# db/seeds.rb

# 1) Charge un fichier texte depuis le fichier de Seed
require 'open-uri'
require 'active_record/fixtures'

Country.delete_all
open("http://openconcept.ca/sites/openconcept.ca/files/country_code_drupal_0.txt")
do |countries|
	countries.read.each_line do |country|
		code, name = country.chomp.split("|")
		Country.create!(:name => name, :code => code)
	end
end

# 2) génère automatiquement un grand nombre d'enregistrement
Debut=11
Fin=2000

puts "création de #{Fin - Debut + 1} photos, à partir de #{Debut}"

(Debut..Fin).each { |i|
	 if Photo.find_by_nom("photo_bis_#{i}").nil?
		puts "création de photo_bis_#{i}"
		Photo.create( :nom => "photo_bis_#{i}", :commentaire => "commentaire photo bis #{i}" )
	 else
		puts "photo_bis_#{i} existe déjà"
	 end
}


2.5.3) Transfert de données entre Base De Données :

On peut décharger les données d'une base dans une autre base de même format (ici Mysql), et de même structure,
avec deux taches rake : l'une de déchargement (dump) et l'autre de chargement (load) :
# lib/tasks/dump_load.rake
namespace :db do
  namespace :seed do
    require 'db/seed_tables'

    desc "décharge les tables contenant les données initiales dans db/<RAILS_ENV>_seed.sql. La variable SEED_TABLES doit-être définie dans db/seed_tables.rb!!!"
    task :dump => :environment do
      config = ActiveRecord::Base.configurations[RAILS_ENV]
      dump_cmd = "mysqldump --user=#{config['username']} --password=#{config['password']} #{config['database']} #{SEED_TABLES.join(" ")} > db/#{RAILS_ENV}_seed.sql"
      system(dump_cmd)
    end

    desc "charge les données initiales depuis db/<RAILS_ENV>_seed.sql dans la base courante"
    task :load => :environment do
      config = ActiveRecord::Base.configurations['test']
      system("mysql --user=#{config['username']} --password=#{config['password']} #{config['database']} < db/#{RAILS_ENV}_seed.sql")
    end
  end
end

On peut aussi remplir des fichiers YAML avec les données des tables.
Attention : Les anciens fichiers seront écrasés.
Les fichiers YAML sont générés dans le répertoire test/fixtures :
# lib/tasks/fixtures.rake
namespace :db do
  namespace :fixtures do

    desc 'Personnalized : Create YAML test fixtures from data in an existing database. 
    Defaults to development database.  Set RAILS_ENV to override.'
    task :dump => :environment do
      puts "cette tâche apparaîtra dans (rake --tasks) du projet"

      sql  = "SELECT * FROM %s"
      skip_tables = ["schema_migrations"]
      ActiveRecord::Base.establish_connection(:development)
      (ActiveRecord::Base.connection.tables - skip_tables).each do |table_name|
        i = "000"
        j = 0

        File.open("#{RAILS_ROOT}/test/fixtures/#{table_name}.yml", 'w') do |file|
          data = ActiveRecord::Base.connection.select_all(sql % table_name)

          file.write data.inject({}) { |hash, record|
          	j += 1
            hash["#{table_name.singularize}_#{i.succ!}"] = record
            hash
          }.to_yaml

          puts "#{table_name} : [#{j}]"
        end
      end
    end
  end
end

La migration est effectuée avec les instructions suivantes :
RAILS_ENV=development rake db:fixtures:dump
RAILS_ENV=production rake db:fixtures:load

2.6) Instructions ActiveRecord :

  • L'enregistrement des dates de création et de mise-à-jour peut être désactivé temporairement :
  • Attention : Il faudra tout de même vérifier que cette instruction est thread safe.
    #Dans une méthode contrôleur :
    ActiveRecord::Base.record_timestamps = false #temporarily turn off magic column updates
    
    @page.update_attribute(:position, pos)
    
    ActiveRecord::Base.record_timestamps = true #turning updates back on
    

  • Suppression dynamique d'une colonne :
  • Exemple depuis la console :
    #rails console
    
    _sql = "alter table categories drop column parent_id"
    ActiveRecord::Base.connection.execute(_sql)
    

 

3) Modèles :

3.1) Create Read Update Delete (CRUD) :

CRUD désigne les quatre opérations de base pour la persistance des données, en particulier le stockage d'informations en Base De Données.
Soit : Create, Read (ou Retrieve), Update et Delete (ou Destroy). C'est-à-dire : créer, lire, mettre à jour et supprimer.
Ruby On Rails fournit une gestion automatique de ces opérations.

3.1.1) Create :


<controlleur>/new

3.1.2) Retrieve :


<controlleur>/<id>

3.1.3) Update :


 <controlleur>/<id>/edit


<input type="hidden" name="_method" value="put" />

3.1.4) Delete :


<a href="<controller>/<id>" onclick="if (confirm('Es-tu sûr(e)?')) { var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href;var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); m.setAttribute('value', 'delete'); f.appendChild(m);var s = document.createElement('input'); s.setAttribute('type', 'hidden'); s.setAttribute('name', 'authenticity_token'); s.setAttribute('value', 'arpS61rIZTta3ZSiNmmeO1G0NUb30szy0+7Qx9OQIHw='); f.appendChild(s);f.submit(); };return false;">Supprimer</a>

<a href="<controller>/<id>" data-confirm="Are you sure?" data-method="delete" rel="nofollow">Supprimer</a>


<input type="hidden" name="_method" value="delete" />

3.2) Gestion du cache :

Donner au 1er paramètre de la fonction retournant les lignes d'association la valeur true,
abandonne les copies en cache, et interroge de nouveau la B.D.D.
#exemple :
customer.orders(true).empty?

3.3) Manipulation d'enregistrements :

Les instructions suivantes retournent ou s'appliquent à une séquence d'enregistrements. Elles sont fournies par : ActiveRecord:Base :

3.3.1) La méthode de recherche dynamique : find_by_<nom_attribut> :

Renvoie une séquence de modèles contenant les enregistrements correspondant :
<classe_modele>.find_by_<nom_attribut>("<valeur_attribut>")

3.3.2) Retrouver tous les enregistrements, all :

Retourne une séquence de modèles contenant tous les enregistrements :
<classe_modele>.all

3.3.3) Trier les enregistrements, order :

C'est une méthode que l'on peut appliquer à une séquence de modèles :
<sequence_de_modeles>.order(:<tri_selon_cet_attribut_>)
Si l'on veut un tri sur un attribut virtuel (défini par une méthode dans le modèle) :
# nom_long est une méthode dynamique (à créer) du modèle (pas un attribut de la B.D.D.)
# notez le ! à la fin du sort, qui implique que la séquence elle-même est modifiée :
@all_categories.sort! { |a,b| a.nom_long <=> b.nom_long }

3.3.4) Chargement Zélé (Eager Loading) avec include :

Cela permet de pré-remplir les objets avec les tables de jointure :
Reservation.find_all_by_date(Date.today, :include => [:evenement, :sessions])
Attention : Les noms des relations en jointure doivent respecter le singulier ou pluriel du type de relation (C.f. le modèle).
Marche aussi sur les named_scopes.

On peut même aller jusqu'à charger des tables à deux jointures de la table d'origine :
Reservation.find_all_by_date(Date.today, :include => [{:evenement => :organisateur}, :sessions ])
: Un organisateur organise des évènements, qui ont plusieurs réservations, et elle-mêmes plusieurs sessions.

Autre exemple :
Les utilisateurs peuvent être membre de plusieurs communautés. Chaque communauté a une localisation (Ville, rue, immeuble, numéro etc.).
Nous voulons lister tous les utilisateurs d'une ville particulière :
@utilisateurs = Utilisateur.find(
	:all,
	:include => {
		:communautes => { :localisation => {} } ,
		:conditions => ["localisations.ville = ?", ville]
	}
)

3.4) Périmètres Nommés (Named Scopes) :

Ce sont des méthodes ActiveRecord qui permettent de filtrer / trier les données de façon paramétrée.
Ces méthodes peuvent être enchaînées ce qui rends leur utilisation très intéressante.

Exemples courants :
#Filtre sur la valeur d'une colonne :
named_scope :school_names, :conditions => {:requirement_type => Type::SCHOOL_NAMES}
named_scope :active, :conditions => {:active => true}

#Filtre avec bloc :
named_scope :recent,
	lambda {
		{
			:conditions => ['created_at > ?', 1.week.ago]
		}
	}

#Utilisation, on peut les utiliser en cascade :
User.active.recent
Exemple complet :
class Article
	named_scope :published_at_like, lambda {|date_at| { :conditions => ['published_at LIKE ? ', "%#{date_at}%"] }}
	named_scope :user_id, lambda {|user_id| { :conditions => {:user_id => user_id} }
	named_scope :published, { :conditions => {:published => true} }
	named_scope :not_published, { :conditions => { :published => false} }
	named_scope :category, lambda {|category_id| { :conditions => ['categorizations.category_id = ?', category_id], :include => 'categorizations' }}
	named_scope :draft, { :conditions => {:state => :draft} }
	named_scope :no_draft, {:conditions => ['state <> ?', 'draft'], :order => 'created_at DESC'}
	named_scope :searchstring,
		lambda {
			|search_string|
			tokens = search_string.split.collect {|c| "%#{c.downcase}%"}
			{
				:conditions => [(['(LOWER(body) LIKE ? OR LOWER(extended) LIKE ? OR LOWER(title) LIKE ?)']*tokens.size).join(' AND '),
							  *tokens.collect{ |token| [token] * 3 }.flatten]
			}
	}

	def self.search_no_draft_paginate(search_hash, paginate_hash)
		list_function  = ["Article.no_draft"]
		if search_hash.nil?
		  search_hash = {}
		end

		if search_hash[:searchstring]
		  list_function << 'searchstring(search_hash[:searchstring])'
		end

		if search_hash[:published_at] and %r{(\d\d\d\d)-(\d\d)} =~ search_hash[:published_at]
		  list_function << 'published_at_like(search_hash[:published_at])'
		end

		if search_hash[:user_id] && search_hash[:user_id].to_i > 0
		  list_function << 'user_id(search_hash[:user_id])'
		end

		if search_hash[:published]
		  list_function << 'published' if search_hash[:published].to_s == '1'
		  list_function << 'not_published' if search_hash[:published].to_s == '0'
		end

		if search_hash[:category] and search_hash[:category].to_i > 0
		  list_function << 'category(search_hash[:category])'
		end

		paginate_hash[:order] = 'created_at DESC'
		list_function << "paginate(paginate_hash)"
		eval(list_function.join('.'))
	end
end
Pour savoir quelles sont les conditions résultantes d'une cascade de named scopes, utilisez la méthode scope(:find) :
#Appliqué à l'exemple ci-dessus :
Article.user_id.category.published.scope(:find)

3.5) Suivi des modifications :

>> p = Post.find(:first)
=> ...

>> p.title = "Another Title"
=> "Another Title"

>> p.changed?
=> true

>> p.changed
=> ["title"]

>> p.changes
=> {"title"=>["New Title", "Another Title"]}

>> p.title_changed?
=> true

>> p.title_was
=> "New Title"

>> p.title_change
=> ["New Title", "Another Title"]

>> p.title_will_change!
=> "Another Title"

 

4) Relations / Associations :

C.f. : Le guide ROR donnant les basiques des associations

ATTENTION aux collisions de noms : Les attributs (colonnes de tables) et les noms de connexion sont de mauvais noms pour les associations.

4.1) Types de relations :

4.1.1) Has Many Trough (hm:t) :

La relation has_many :through est à utiliser si il y besoin sur le Modèle associé de
  • Validations
  • Callbacks
  • Attributs supplémentaires
has_many :<table_etrangere>, :through => :<table_intermediaire>

4.1.2) Has Many (hm) :

Cas de l'auto-relation :
#models/employee.rb
class Employee < ActiveRecord::Base
	has_many :subordinates, :class_name => 'Employee', :foreign_key => 'manager_id'
	belongs_to :manager, :class_name => 'Employee'
end

#controllers/employee_controller.rb
@employee.subordinates
@employee.manager

4.1.3) Has and Belongs To Many (hbtm) :

  • Cas standard :
  • #models/produit.rb
    class Produit < ActiveRecord::Base
    	has_and_belongs_to_many :categories
    end
    
    #models/categorie.rb
    #Attention aux inflexions sur cette classe !!!
    class Categorie < ActiveRecord::Base
    	has_and_belongs_to_many :produits
    end
    
    #controllers/produit_controller.rb
    @produit.categories
    
    #controllers/categorie_controller.rb
    @categorie.produits
    
  • Active Record crée le nom de l'association en utilisant l'ordre lexical des noms des classes.
  • Exemple: dans une jointure entre les modèles Client et Commandes, le nom de table de jointure par défaut sera :
    "clients_commandes" parce que 'cl' est avant 'co' dans l'ordre lexical.

  • Cependant, on peut spécifier le nom de la table de jointure avec le paramètre :join_table :
  • #exemple, models/produit.rb :
    has_and_belongs_to_many :categories, :join_table => "prods_cats"
    

4.1.4) Forcer le nom du Modèle et la clé étrangère :

belongs_to :follower, :class_name => "Membre", :foreign_key => "membres_follower"

4.2) Espaces de noms :

module MyApplication
	module Business
		class Supplier < ActiveRecord::Base
			has_one :account, :class_name => "MyApplication::Billing::Account"
		end
	end

	module Billing
		class Account < ActiveRecord::Base
			belongs_to :supplier, :class_name => "MyApplication::Business::Supplier"
		end
	end
end

4.3) Rappels de fonctions sur les associations :

De façon similaire aux callbacks (rappels) normaux qui se branchent sur le cycle de vie d'un objet Active Record,
vous pouvez aussi définir des callbacks qui sont appelés quand vous ajoutez ou supprimez un object de la collection d'une association.
class Project
	has_and_belongs_to_many :developers, :after_add => :evaluate_velocity

	def evaluate_velocity(developer)
		...
	end
end

4.4) Changements dans l'interface de requête entre Rails 2 et 3 :

Ces changement concernent :
  • Les méthodes find
  • Les paramètres :conditions des méthodes de requête
  • Les named_scope
C.f. :
http://m.onkey.org/2010/1/22/active-record-query-interface

 

5) Inflexions :

C.f. :
active_support/inflections.rb

#config/initializers/inflections.rb
inflect.plural(<sing>, <plur>)

inflect.singular(<plur>, <sing>)

#en anglais category => categories
inflect.irregular 'categorie', 'categories'
#=> génère dans la liste des inflexions :
(?i-mx:(c)ategorie$)\1ategories

#Les instructions suivantes ne marchent pas : une règle générale prends le pas
#inflect.singular 'categories', 'categorie'
#inflect.plural 'categorie', 'categories'

Pour tester :
'categories'.singularize

 

6) Instructions spécifiques à une B.D.D. :