Le méconnu inverse_of

Image non disponible Image non disponible

L'article original peut être lu sur le blog de Synbioz : Le méconnu inverse_of.

Commentez Donner une note à l'article (5)

Article lu   fois.

Les deux auteurs

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

Active record : le patron de conception

Active record, avant d'être le nom d'une célèbre gem incluse dans Rails, est le nom d'un design pattern, permettant de faire du mapping objet - relationnel.

C'est-à-dire qu'il vous permet de manipuler sous forme d'objets vos données stockées dans une base de données.

Optimiser la communication entre les objets et la base de données

La gem ActiveRecord vous offre un maximum de confort en mettant à disposition toutes les méthodes pour gérer les opérations basiques de CRUD mais aussi les associations entre tables.

Imaginons deux classes product et category :

 
Sélectionnez
  class Category < ActiveRecord::Base

    has_many :products

  end

  class Product < ActiveRecord::Base

    belongs_to :category

  end

Regardons quels sont les appels effectués à la base lorsque nous parcourons les objets :

 
Sélectionnez
  irb(main):008:0> c2 = Category.first(:include => :products)

    Category Load (0.1ms)  SELECT "categories".* FROM "categories" LIMIT 1

    Product Load (0.2ms)  SELECT "products".* FROM "products" WHERE "products"."category_id" IN (1)

  => #<Category id: 1, name: "Téléphone", created_at: "2012-01-27 11:21:59", updated_at: "2012-01-27 11:21:59">

  irb(main):009:0> c3 = c2.products.first.category

    Category Load (0.2ms)  SELECT "categories".* FROM "categories" WHERE "categories"."id" = 1 LIMIT 1

  => #<Category id: 1, name: "Téléphone", created_at: "2012-01-27 11:21:59", updated_at: "2012-01-27 11:21:59">

  irb(main):010:0> c3.object_id == c2.object_id

  => false

ActiveRecord n'est pas suffisamment malin pour comprendre que nous sommes en fait sur la même catégorie quand nous demandons celle d'un produit qui fait partie de la liste des produits de cette même catégorie.

Le rôle d'inverse_of

Redéfinissons nos relations pour utiliser inverse_of :

 
Sélectionnez
  class Category < ActiveRecord::Base

    has_many :products, :inverse_of => :category

  end

  class Product < ActiveRecord::Base

    belongs_to :category, :inverse_of => :products

  end

  irb(main):004:0> c2 = Category.first(:include => :products)

    Category Load (0.2ms)  SELECT "categories".* FROM "categories" LIMIT 1

    Product Load (0.2ms)  SELECT "products".* FROM "products" WHERE "products"."category_id" IN (1)

  => #<Category id: 1, name: "Téléphone", created_at: "2012-01-27 11:21:59", updated_at: "2012-01-27 11:21:59">

  irb(main):005:0> c3 = c2.products.first.category

  => #<Category id: 1, name: "Téléphone", created_at: "2012-01-27 11:21:59", updated_at: "2012-01-27 11:21:59">

  irb(main):006:0> c3.object_id == c2.object_id

  => true

En explicitant la relation inverse, ActiveRecord sait maintenant retrouver ses petits et éviter d'inutiles requêtes SQL.

Les formulaires imbriqués

Imaginons que vous souhaitiez pouvoir créer directement une catégorie avec un ou plusieurs produits et que vos produits doivent obligatoirement avoir une catégorie.

 
Sélectionnez
  class Category < ActiveRecord::Base

    has_many :products, :inverse_of => :category



    accepts_nested_attributes_for :products

  end

  class Product < ActiveRecord::Base

    belongs_to :category, :inverse_of => :products



    validates_presence_of :category

  end

Sans inverse_of, vous risquez d'avoir des soucis à cause de la validation. En effet, le produit ayant une validation sur sa catégorie, il va chercher à la charger.

Sans inverse_of il essayera de la charger depuis la base de données, alors qu'elle n'existe pas encore. Avec, il sera capable de récupérer directement l'objet.

Identity map

Très franchement, ActiveRecord nous a habitués à un peu plus de magie et d'automatisme pour que l'on n'ait pas envie de définir manuellement tous ses inverse_of.

D'autant plus qu'il semble assez simple de retrouver les associations concordantes et que cette fonctionnalité est présente dans datamapper.

C'est en fait le rôle de la fonction d'identity map introduite par Rails 3.1 mais désactivée par défaut.

L'objectif est de n'avoir jamais à recharger un objet depuis la base quand il est déjà en mémoire.

L'activation de la fonctionnalité se fait dans application.rb :

 
Sélectionnez
  config.active_record.identity_map = true

  irb(main):005:0> ActiveRecord::IdentityMap.use { c = Category.find(1) ; c2 = Category.find(1) }

    Category Load (57.9ms)  SELECT "categories".* FROM "categories" WHERE "categories"."id" = ? LIMIT 1  [["id", 1]]

    Category Loaded  From Identity Map (id: 1)

  => #<Category id: 1, name: "Téléphone", created_at: "2012-01-27 11:21:59", updated_at: "2012-01-27 11:21:59">

L'utilisation se fait dans un bloc et l'usage est encore limité. Par exemple utiliser Category.first, même dans le bloc, entraînera tout de même deux requêtes.

De plus l'activation de la fonction ne permet pas pour le moment de remplacer le inverse_of.

C'est pourtant une fonctionnalité très importante car elle permettra de fortement limiter le nombre d'instanciations d'objets.

Pour autant, elle n'est pas simple à mettre en place en conservant un comportement thread-safe : on imagine bien deux morceaux de code en train de mettre à jour le même objet en mémoire.

Conclusion et remerciements

En résumé, utilisez le inverse_of dès aujourd'hui si ce n'est pas encore le cas car Rails n'est pas prêt de le faire automatiquement pour vous.

Cet article a été publié avec l'aimable autorisation de Synbioz, l'article original (Le méconnu inverse_of) peut être vu sur le blog de Synbioz.

Nous tenons à remercier kdmbella, ClaudeLELOUP et djibril pour leur relecture attentive de cet article.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2012 Synbioz. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.