Neu: Das englische Ruby on Rails 4.0 Buch.

4.4. first, last und all

In dem ein oder anderen Fall braucht man immer mal den ersten oder den letzten oder auch alle Datensätze. Deshalb gibt es für alle drei Fälle eine fertige Methode. Fangen wir mit den einfachsten an: first und last.
MacBook:europe xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > Country.first
  Country Load (0.2ms)  SELECT "countries".* FROM "countries" LIMIT 1
 => #<Country id: 1, name: "Deutschland", population: 81831000, created_at: "2012-04-26 10:26:38", updated_at: "2012-04-26 10:26:38"> 
1.9.3p194 :002 > Country.last
  Country Load (0.3ms)  SELECT "countries".* FROM "countries" ORDER BY "countries"."id" DESC LIMIT 1
 => #<Country id: 4, name: "Niederlande", population: 16680000, created_at: "2012-04-26 10:47:23", updated_at: "2012-04-26 10:47:23"> 
1.9.3p194 :003 > exit
MacBook:europe xyz$ 
Und jetzt mal alle auf einmal mit all:
MacBook:europe xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > Country.all
  Country Load (0.2ms)  SELECT "countries".* FROM "countries" 
 => [#<Country id: 1, name: "Deutschland", population: 81831000, created_at: "2012-04-26 10:26:38", updated_at: "2012-04-26 10:26:38">, #<Country id: 2, name: "Frankreich", population: 65447374, created_at: "2012-04-26 10:41:54", updated_at: "2012-04-26 10:41:54">, #<Country id: 3, name: "Belgien", population: 10839905, created_at: "2012-04-26 10:44:49", updated_at: "2012-04-26 10:44:49">, #<Country id: 4, name: "Niederlande", population: 16680000, created_at: "2012-04-26 10:47:23", updated_at: "2012-04-26 10:47:23">] 
1.9.3p194 :002 > exit
MacBook:europe xyz$ 
Die von first, last und all erzeugten Objekte sind aber unterschiedlich. Bei first und last wird ein Objekt der Klasse Country ausgegeben und bei all natürlich ein Array solcher Objekte:
MacBook:europe xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > Country.first
  Country Load (0.1ms)  SELECT "countries".* FROM "countries" LIMIT 1
 => #<Country id: 1, name: "Deutschland", population: 81831000, created_at: "2012-04-26 10:26:38", updated_at: "2012-04-26 10:26:38"> 
1.9.3p194 :002 > Country.first.class
  Country Load (0.3ms)  SELECT "countries".* FROM "countries" LIMIT 1
 => Country(id: integer, name: string, population: integer, created_at: datetime, updated_at: datetime) 
1.9.3p194 :003 > Country.all
  Country Load (0.3ms)  SELECT "countries".* FROM "countries" 
 => [#<Country id: 1, name: "Deutschland", population: 81831000, created_at: "2012-04-26 10:26:38", updated_at: "2012-04-26 10:26:38">, #<Country id: 2, name: "Frankreich", population: 65447374, created_at: "2012-04-26 10:41:54", updated_at: "2012-04-26 10:41:54">, #<Country id: 3, name: "Belgien", population: 10839905, created_at: "2012-04-26 10:44:49", updated_at: "2012-04-26 10:44:49">, #<Country id: 4, name: "Niederlande", population: 16680000, created_at: "2012-04-26 10:47:23", updated_at: "2012-04-26 10:47:23">] 
1.9.3p194 :004 > Country.all.class
  Country Load (0.3ms)  SELECT "countries".* FROM "countries" 
 => Array 
1.9.3p194 :005 > exit
MacBook:europe xyz$
Wenn County.all ein Array zurückgibt, dann müsste man doch auch Iteratoren (siehe „Iteratoren (Iterators)“ und „Iterator each“) benutzen können, oder? Ja, natürlich! Das ist ja das Schöne daran. Kleiner Versuch mit each:
MacBook:europe xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > Country.all.each do |country|
1.9.3p194 :002 >     puts country.name
1.9.3p194 :003?>   end
  Country Load (0.1ms)  SELECT "countries".* FROM "countries" 
Deutschland
Frankreich
Belgien
Niederlande
 => [#<Country id: 1, name: "Deutschland", population: 81831000, created_at: "2012-04-26 10:26:38", updated_at: "2012-04-26 10:26:38">, #<Country id: 2, name: "Frankreich", population: 65447374, created_at: "2012-04-26 10:41:54", updated_at: "2012-04-26 10:41:54">, #<Country id: 3, name: "Belgien", population: 10839905, created_at: "2012-04-26 10:44:49", updated_at: "2012-04-26 10:44:49">, #<Country id: 4, name: "Niederlande", population: 16680000, created_at: "2012-04-26 10:47:23", updated_at: "2012-04-26 10:47:23">] 
1.9.3p194 :004 > exit
MacBook:europe xyz$ 
Kann man dann auch .all.first als Alternative zu .first benutzen? Ja, aber es macht wenig Sinn. Sehen Sie selbst:
MacBook:europe xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > Country.first
  Country Load (0.2ms)  SELECT "countries".* FROM "countries" LIMIT 1
 => #<Country id: 1, name: "Deutschland", population: 81831000, created_at: "2012-04-26 10:26:38", updated_at: "2012-04-26 10:26:38"> 
1.9.3p194 :002 > Country.all.first
  Country Load (0.3ms)  SELECT "countries".* FROM "countries" 
 => #<Country id: 1, name: "Deutschland", population: 81831000, created_at: "2012-04-26 10:26:38", updated_at: "2012-04-26 10:26:38"> 
1.9.3p194 :003 > exit
MacBook:europe xyz$
Selbst bei unserer Mini-Datenbank sehen wir schon einen Geschwindigkeitsunterschied von 0.1 Millisekunden. Bei Country.first wird per SQL in der Datenbank mit einem LIMIT 1 der erste Datensatz herausgesucht und von ActiveRecord als einzelnes Objekt der Country-Klasse ausgegeben. Bei Country.all.first wird erst die ganze Tabelle mit SELECT "countries".* FROM "countries" als Array eingelesen und das erste Element dieses Arrays rausgezogen. Bei dieser kleinen Applikation kann man sich das noch leisten, aber stellen Sie sich mal vor, es würde sich um eine Datenbank mit vielen Millionen Einträgen handeln.

Warnung

SQL-Datenbanken haben normalerweise keine automatische Sortierung der Ergebnisse eines SELECT * FROM xyz. Die Datenbank kann die Reihenfolge der Datensätze selber bestimmen. Entsprechend können wir bei einem LIMIT 1 aus einer solchen Menge auch nicht 100%ig sicher sein, dass der für uns Menschen logisch erste Datensatz rauskommt. Faktisch heißt das, dass wir mit einem Country.first nicht zwingend den ersten Datensatz bekommen müssen. Der SQL-Datenbank steht es frei, z. B. aus Performance-Gründen eine andere Sortierung vorzunehmen. Wenn wir absolut sichergehen wollen, dass wir den für uns logisch ersten Datensatz bekommen, so müssen wir mit der order-Methode (siehe „order und reverse_order“) arbeiten:
MacBook:europe xyz$ rails console
Loading development environment (Rails 3.2.3)
1.9.3p194 :001 > Country.order(:id).first
  Country Load (0.6ms)  SELECT "countries".* FROM "countries" ORDER BY id LIMIT 1
 => #<Country id: 1, name: "Deutschland", population: 81831000, created_at: "2012-04-26 10:26:38", updated_at: "2012-04-26 10:26:38"> 
1.9.3p194 :002 > exit
MacBook:europe xyz$
Sie sehen im SQL, dass mit SELECT "countries".* FROM "countries" ORDER BY id LIMIT 1 erst die Tabelle nach der id sortiert und dann die erste Zeile ausgegeben wird.
Meistens arbeitet die SQL-Datenbank genau so, wie man es erwartet und man bekommt mit Country.first den ersten Datensatz geliefert. Wenn man aber ganz sicher sein will, dann sollte man immer mit einem order arbeiten.
Am einfachsten geht dies mit einem default_scope.