#22 Eager Loading
Un buon punto di partenza per cominciare quando si vogliono aumentare le prestazioni delle vostre applicazioni Rails è dare un’occhiata agli accessi al database. Ridurre il numero di query fatte al database può incrementare la velocità delle vostre applicazioni in modo significativo. Un modo per farlo è quello di adottare una tecnica nota con il nome di eager loading.
I nostri modelli applicativi Task e Project.
La pagina di sopra mostra un numero consistente di task, e il progetto a cui ognuno di essi appartiene. Nel nostro TaskController
, otteniamo tutti questi task e iteriamo su ciascuno di essi nella vista:
<h1>Tasks</h1> <ul> <% @tasks.each do |task| %> <li><%= link_to task.name, task %> in <%= task.project.name %></li> <% end %> </ul>The view code for the Tasks index page.
La pagina prende il nome del task e poi il nome dei suoi progetti attraverso l’associazione. Il problema nell’uso di questo approccio è che ogni volta che si ottiene il nome di un progetto del task, viene generata una query SQL. Ne si può avere conferma controllando i log di sviluppo.
Project Load (0.2ms) SELECT * FROM "projects" WHERE ("projects"."id" = 60) CACHE (0.0ms) SELECT * FROM "projects" WHERE ("projects"."id" = 60) CACHE (0.0ms) SELECT * FROM "projects" WHERE ("projects"."id" = 60) CACHE (0.0ms) SELECT * FROM "projects" WHERE ("projects"."id" = 60) CACHE (0.0ms) SELECT * FROM "projects" WHERE ("projects"."id" = 60)
La nostra richiesta causa ripetute chiamate al database.
Possiamo vedere dai log che viene eseguita la stessa chiamata tante volte quanti sono i nomi dei progetti. Rails ci aiuta a ridurre il numero di chiamate, facendo cache delle richieste e recuperando le richieste ripetute dalla cache, piuttosto che dal database. Il sistema di caching è stato introdotto con Rails 2.0; se facessimo la stessa richiesta con Rails 1.x, ogni richiesta per il nome di un progetto associato al task comporterebbe una richiesta al database.
Il sistema di caching è utile, ma l’eager loading riduce le chiamate al database ulteriormente. Lo possiamo abilitare facendo una piccola modifica al TasksController:
class TasksController < ApplicationController def index @tasks = Task.find(:all, :include => :project) end end
Ciò che abbiamo fatto sopra è di aggiungere un parametro alla linea che recupera tutti i task, in modo tale che nel recupero si porti dietro anche i progetti associati. La ragione per cui nel codice abbiamo usato il simbolo al singolare (:project
), anzichè :projects
è che usiamo il nome dell’associazione, e nel modello Task
cè scritto belongs_to => :project
. Ora, se ricarichiamo la pagina e controlliamo i log, vediamo che il numero di richieste al database si è ridotto da 101 a 2, con un conseguente aumento delle performance della nostra applicazione.
Inclusione di più di una associazione
Oltre ad appartenere ad un Project
, il nostro modello Task
ha anche un’altra associazione con il modello Comment
.
class Task < ActiveRecord::Base belongs_to :project has_many :comments end
Possiamo portarci dietro i commenti di un task insieme al suo progetto di appartenenza, includendo i nomi di entrambe le associazioni in un array. (notate che per i commenti usiamo il plurale poichè questo è ora il nome dell’associazione.)
class TasksController < ApplicationController def index @tasks = Task.find(:all, :include => [:project, :comments]) end end
Potremmo fare uso perfino di associazioni ancor più complesse. Se il nostro modello Comment fosse così …
class Comment < ActiveRecord::Base belongs_to :task belongs_to :user end
…potremmo portarci dietro anche gli utenti associati ai commenti, usando un hash:
@tasks = Task.find(:all, :include => [:project, {:comments => :user }])
L’utilizzo dell’eager loading è un potente modo per ridurre gli accessi al database delle vostre applicazioni Rails. Ci sono più informazioni a riguardo nel sito delle API Rails su http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html.