#202 Active Record Queries in Rails 3
- Download:
- source codeProject Files in Zip (99.6 KB)
- mp4Full Size H.264 Video (15.1 MB)
- m4vSmaller H.264 Video (10.7 MB)
- webmFull Size VP8 Video (31.6 MB)
- ogvFull Size Theora Video (24 MB)
Im Verlauf der letzten beiden Episoden haben wir Ihnen gezeigt, wie Sie Ihren Computer für Rails 3 vorbereiten und eine neue Rails 3 Applikation erzeugen können. In dieser Folge werden wir einige der neuen Features betrachten. Wir beginnen mit ActiveRecord, der jetzt ein neues Interface anbietet, um Datenbankanfragen durchzuführen. Pratik Naik hat sich diesem Thema kürzlich in einem Beitrag in seinem Blog ausführlich gewidmet, der durchaus einen Blick wert ist.
Ein paar einfache Beispiele
Zu Beginn zeigen wir Ihnen ein paar Beispiele von alten ActiveRecord-find
Aufrufen und übersetzen diese in das neue Format. Zu diesem Zweck verwenden wir eine einfache Rails Applikation mit zwei Models: Article
und Comment
, wobei gilt: Article:
has_many :comments
.
Der erste find
-Aufruf gibt die zehn zuletzt veröffentlichen Artikel zurück:
Article.find(:all, :order => "published_at desc", :limit => 10)
Grundlegend sieht man sich beim übersetzen von ActiveRecord Queries in das neue Rails 3 Format den Hash mit Optionen an, die an find
übergeben werden und ersetzt jeweils einen Teil mit einem äquivalenten Methodenaufruf. An Stelle des find
-Aufrufs von oben kann man schreiben:
Article.order("published_at desc").limit(10)
Wie Sie sehen, ist die Übersetzung sehr einfach und bietet eine insgesamt schönere Syntax.
Die Optionen aus dem alten Hash lassen sich direkt in die neuen Methoden übertragen, wie das nächste Beispiel zeigt.
Article.find(:all, :conditions => ["published_at <= ?", Time.now], :include => :comments)
Es gibt nur zwei tatsächliche Ausnahmen, die im nachfolgenden Beispiel verwendet wurden. Der soeben genannte find
-Aufruf liefert alle Artikel, deren Veröffentlichungszeitpunkt in der Vergangenheit liegt inklusive der dazugehörigen Kommentare. In Rails 3 wird daraus:
Article.where("published_at <= ?", Time.now).includes(:comments)
An Stelle von :conditions
können wir jetzt die where
-Methode verwenden, der wir dieselben Argumente übergeben. Sie können als Array oder einzeln übergeben werden. Um assoziierte Einträge zu bekommen, verwendet man anstatt :include
jetzt die (um ein s erweiterte) includes
-Methode. Alle anderen Optionen, die wir bisher an find
übergeben haben, wurden zu Methoden mit den gleichen Namen wie die jeweilige Option selbst.
Unser letztes Beispiel sucht den neuesten Artikel.
Article.find(:first, :order => "published_at desc")
Unter Verwendung der neuen Rails 3 Syntax wird daraus:
Article.order("published_at desc").first()
Beachtenswert ist hier, dass wir first
erst am Ende der Methodenverkettung aufrufen.
Da wir in absteigender Reihenfolge suchen, könnten wir die Anweisung auch äquivalent umwandeln zu:
Article.order("published_at").last()
Das Ergebnis wäre die gleiche Datenbankanfrage, doch mit knapperem Code.
In Rails 3 können wir entweder die alten find
-Methoden oder die neuen Rails 3 Methoden. In Rails 3.1 jedoch werden die alten Methoden als veraltet markiert und mit Rails Version 3.2 werden sie vollends entfernt werden. Es ist daher notwendig, alle find
s in Ihren Applikationen auf die neue Syntax zu migrieren, damit sie mit zukünftigen Versionen von Rails 3 kompatibel sein werden.
An dieser Stelle werden Sie sich vielleicht fragen, was genau der Grund für diese neue Syntax ist - vor Allem, weil mit ihrer Einführung sehr viele existierende Rails-Applikationen nicht mehr funktionieren werden, wenn die Umstellung stattfindet. Der Grund für diese drastische Neuerung liegt beim mächtigen lazy loading.
Lazy Loading
Um lazy loading zu demonstrieren, verwenden wie die Konsole unserer Applikation. Wenn wir alle Artikel abfragen, bekommen wir wie erwartet ein Array mit allen Artikeln. Im Beispiel haben wir drei Artikel in unserer Datenbank.
ruby-1.9.1-p378 > Article.all => [#<Article id: 1, name: "It's Ancient", published_at: nil, hidden: false, created_at: "2010-02-22 20:35:42", updated_at: "2010-02-22 20:35:42">, #<Article id: 2, name: "Can't See Me", published_at: nil, hidden: false, created_at: "2010-02-22 20:37:03", updated_at: "2010-02-22 20:37:03">, #<Article id: 3, name: "To the Future!", published_at: nil, hidden: false, created_at: "2010-02-22 20:38:17", updated_at: "2010-02-22 20:38:17">]
Wenn wir alle Artikel alphabetisch sortiert wollen, verwenden wir die order
-Methode:
ruby-1.9.1-p378 > articles = Article.order("name") => #<ActiveRecord::Relation:0x00000101669b90 @table=#<Arel::Table:0x000001023e9af8 @name="articles", @options={:engine=>#<Arel::Sql::Engine:0x000001023a15c8 @ar=ActiveRecord::Base, @adapter_name="SQLite">}, @engine=#<Arel::Sql::Engine:0x000001023a15c8 @ar=ActiveRecord::Base, @adapter_name="SQLite">, …
Anstatt einer Liste von Artikeln haben wir diesmal ein ActiveRecord::Relation
Objekt zurückbekommen. Dieses Objekt speichert Informationen über unsere Suche, die tatsächliche Anfrage wurde aber noch nicht durchgeführt. Dies ist die Bedeutung von lazy loading in diesem Kontext: Die Daten werden erst abgefragt, wenn es sein muss! Die Anfrage an die Datenbank wird erst durchgeführt, wenn wir explizit eine Datenmenge anfordern (z.B. mit each
, wenn wir alle Artikel auf einmal oder nur den ersten Artikel anfragen würden):
ruby-1.9.1-p378 > articles.first => #<Article id: 2, name: "Can't See Me", published_at: nil, hidden: false, created_at: "2010-02-22 20:37:03", updated_at: "2010-02-22 20:37:03">
Betrachten wir nun, inwiefern sich das auf unsere Applikation anwenden lässt. Wir haben ein Scaffold für Article erzeugen lassen, also haben wir auch einen ArticlesController mit den üblichen sieben Aktionen. In der index
-Aktion wird Article.all
verwendet, um alle Artikel auf einmal abzufragen:
/app/controllers/articles_controller.rb
def index @articles = Article.all respond_to do |format| format.html # index.html.erb format.xml { render :xml => @articles } end end
Verwenden wir eine der neuen Methoden wie beispielsweise order
, um die Artikel nach dem Namen zu sortieren, wird an Stelle des Arrays mit allen Artikeln ein ActiveRecord::Relation
Objekt in @articles
gespeichert. Das hat zur Folge, dass die eigentliche Datenbankanfrage nicht im Controller, sondern im View durchgeführt wird.
/app/views/articles/index.html.erb
<% @articles.each do |article| %> <tr> <td><%= article.name %></td> <td><%= article.published_at %></td> <td><%= article.hidden %></td> <td><%= link_to 'Show', article %></td> <td><%= link_to 'Edit', edit_article_path(article) %></td> <td><%= link_to 'Destroy', article, :confirm => 'Are you sure?', :method => :delete %></td> </tr> <% end %>
Wenn wir jetzt die Index-Seite aufrufen, werden die Artikel in alphabetischer Reihenfolge dargestellt.
Sehr angenehm fällt auf, dass fragment caching mithilfe der cache
-Methode im View sich jetzt deutlicher auswirken wird, da der üblicherweise langsame Datenbankzugriff nur dann ausgeführt werden wird, wenn er wirklich notwendig ist.
Die neue Syntax macht es darüber hinaus auch einfacher, komplexere Suchbedingungen zu vereinen. Nehmen wir an, wir wollen die Artikel filtern, so dass nur die versteckten angezeigt werden, wenn wir hidden=1
als Parameter in der URL übergeben. Dies lässt sich leicht verwirklichen, indem wir den Code in der index
-Aktion wie folgt verändern:
/app/controllers/articles_controller.rb
def index @articles = Article.order('name') if params[:hidden] @articles = @articles.where(:hidden =>(params[:hidden] == "1")) end respond_to do |format| format.html # index.html.erb format.xml { render :xml => @articles } end end
Ist ein hidden
-Parameter vorhanden und hat er den Wert 1
, wird eine zusätzliche where
-Methode an unsere Suche angehängt, so dass die Suche nur noch die versteckten Artikel anzeigen wird. Sobald wir den entsprechenden Parameter an die URL anhängen und neu laden, sehen wir nur noch die versteckten Artikel.
Übergeben wir hingegen 0
, bekommen wir nur die sichtbaren Artikel zu sehen.
Die Möglichkeit, verschiedenste Suchmethoden aneinander zu hängen, eröffnet Wege, mit sehr einfachem Code deutlich komplexere Datenbankabfragen zu formulieren, die auch erst dann tatsächlich ausgeführt werden, wenn die Daten benötigt werden.
Named Scopes
Als Nächstes werden wir einige der Änderungen zu Named Scopes in Rails 3 betrachten. Als Beispiel dient uns unser Model Article
mit zwei Named Scopes, einer für die sichtbaren Artikel und einer für die veröffentlichten.
/app/models/article.rb
class Article < ActiveRecord::Base named_scope :visible, :conditions => ["hidden != ?", true] named_scope :published, lambda { {:conditions => ["published_at <= ?", Time.zone.now]} } end
Diese Scopes sind im Stil von Rails 2 formuliert, doch in Rails 3 ist die Vorgehensweise leicht unterschiedlich. Der erste Unterschied liegt in der Benennung der Funktion: Sie heißt jetzt nicht mehr named_scope
, sondern einfach nur noch scope
. Darüber hinaus werden die Optionen nicht mehr als Hash übergeben sondern genau wie beim Wechsel von find
als einzelne Methoden. Genau wie wir die neuen find
-Methoden verwendet haben, verwenden wir hier where
an Stelle von :conditions
. Die Syntax für Named Scopes in Rails 3 sieht also so aus:
/app/models/article.rb
class Article < ActiveRecord::Base scope :visible, where("hidden != ?", true) scope :published, lambda { where("published_at <= ?", Time.zone.now) } end
Ein weiteres neues Feature ist die Möglichkeit, scopes aufeinander aufzubauen. Wenn wir einen Scope mit dem Namen recent
, der die kürzlich veröffentlichten und sichtbaren Artikel nach ihrem Veröffentlichungsdatum sortiert zurückgibt, können wir einfach die beiden vorhandenen Scopes wiederverwenden:
scope :recent, visible.published.order("published_at desc")
Hier haben wir also zwei bestehende Scopes in einem neuen zusammengeführt und einen order
-Aufruf angehängt. Dieses Verketten von Scopes ist ein sehr praktisches Feature bei der Erzeugung neuer, komplexerer Scopes für unsere Models.
Probieren wir unseren neuen Scope doch einfach in der Konsole aus. Wenn wir Article.recent
aufrufen, wird uns ein ActiveRecord::NamedScope::Scope
Objekt zurückgegeben. Dieses Objekt verhält sich ganz ähnlich wie das ActiveRecord::Relation
Objekt, das wir weiter oben betrachtet haben.
ruby-1.9.1-p378 > Article.recent => #<ActiveRecord::NamedScope::Scope:0x0000010318bd08 @table=#<Arel::Table:0x00000102740ea8 @name="articles", @options={:engine=>#<Arel::Sql::Engine:0x00000102651900 @ar=ActiveRecord::Base>}, @engine=#<Arel::Sql::Engine:0x00000102651900 @ar=ActiveRecord::Base>>, …
Wenn wir all
auf dieses Scope Objekt anwenden, sehen wir die passenden Ergebnisse.
ruby-1.9.1-p378 > Article.recent.all => [#<Article id: 1, name: "It's Ancient", published_at: "2010-01-01", hidden: false, created_at: "2010-02-22 20:35:42", updated_at: "2010-02-22 23:00:16">]
Ein letzter Tipp
Beenden wir diese Episode mit einem sehr nützlichen Tipp. Wenn Sie ein Relation
oder ein Scope
Objekt haben und den SQL-Query sehen wollen, den es ausführen würde, können Sie einfach die Methode to_sql
aufrufen:
ruby-1.9.1-p378 > Article.recent.to_sql => "SELECT \"articles\".* FROM \"articles\" WHERE (hidden != 't') AND (published_at <= '2010-02-22 22:47:12.023289') ORDER BY published_at desc"
Diese Methode gibt den SQL-Code zurück, den ActiveRecord ausführen wird, um die zuletzt veröffentlichten und nicht versteckten Artikel von der Datenbank zu erhalten.
Das war's für diese Episode über ActiveRecord Queries in Rails 3! Es gibt jede Menge Neuerungen, die dabei helfen werden, den Code in Ihren Contollern kompakter und klarer zu formulieren. Es lohnt sich also, ein Wenig mit den neuen Features herumzuspielen!