Rails 5.1 Consulting und Schulung vom Autor:
www.wintermeyer-consulting.de/rails/

2.5. Ruby ist objektorientiert

Ruby kennt nur Objekte. Alles ist ein Objekt (was ja fast schon zen-artig ist). Jedes Objekt ist eine Instanz (Instance) einer Klasse (Class). Die Klasse eines Objektes lässt sich mit der Methode .class herausfinden.
Ein Objekt in Ruby ist eingekapselt und kann von außen nur mit den Methoden des entsprechenden Objektes erreicht werden. Was das heißt? Ich kann von außen keine Eigenschaft in einem Objekt direkt verändern. Das entsprechende Objekt muss mir dafür eine Methode zur Verfügung stellen.

Anmerkung

Sie haben keine Ahnung, was eine Klasse und was ein Objekt ist? Keine Panik! Ich werde es keinem erzählen, und man kann auch ganz gut damit arbeiten, ohne sich große Gedanken darüber zu machen. Allein mit diesem Thema ließe sich ein eigenes und viel dickeres Buch füllen. Ganz grob: Ein Objekt ist ein Container für irgendetwas und eine Methode verändert etwas in diesem Container.
Lesen Sie bitte weiter und schauen Sie sich die Beispiele an. Stück für Stück wird das Puzzle dann klarer.

Methoden (Methods)

In anderen Programmiersprachen würde man für Ruby-Methoden Begriffe wie die folgenden verwenden: Funktionen, Prozeduren, Subroutinen und natürlich Methoden.

Anmerkung

Es gibt zwei Arten von Methoden (Class Methods und Instance Methods). Ich will es an dieser Stelle nicht zu kompliziert machen und übergehe diesen feinen Unterschied einfach mal.
An einer derartigen Stelle sucht man immer nach einem guten Beispiel, aber es fallen einem eigentlich nur unsinnige ein. Das Problem ist dabei die Prämisse, dass man lediglich Wissen benutzen kann/darf, das bis zu dieser Stelle bereits im Buch beschrieben wurde.
Nehmen wir also an, dass wir folgenden Code-Ablauf – aus welchem Grund auch immer – häufig verwenden:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> puts 'Hello World!'
Hello World!
=> nil
1.9.3p194 :002:0> puts 'Hello World!'
Hello World!
=> nil
1.9.3p194 :003:0> puts 'Hello World!'
Hello World!
=> nil
1.9.3p194 :004:0> exit
Wir möchten also dreimal hintereinander den String Hello World! ausgeben. Da dies unseren täglichen Arbeitsablauf stark in die Länge zieht, definieren wir jetzt eine Methode (mit dem sinnfreien Namen drei_mal), mit der sich das alles auf einmal erledigen lässt.

Wichtig

Namen von Methoden werden immer kleingeschrieben.
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> def drei_mal  # Start der Definition
1.9.3p194 :002:1>   puts 'Hello World!'
1.9.3p194 :003:1>   puts 'Hello World!'
1.9.3p194 :004:1>   puts 'Hello World!'
1.9.3p194 :005:1> end           # Ende der Definition
=> nil
1.9.3p194 :006:0> drei_mal      # Aufruf der Methode
Hello World!
Hello World!
Hello World!
=> nil
1.9.3p194 :007:0> exit
Bei der Definition einer Methode kann man benötigte Parameter definieren und diese innerhalb der Methode benutzen. Damit können wir eine Methode erstellen, der wir einen String als Parameter übergeben, und diesen dann dreimal ausgeben lassen.
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> def drei_mal(apfelmus)
1.9.3p194 :002:1>   puts apfelmus
1.9.3p194 :003:1>   puts apfelmus
1.9.3p194 :004:1>   puts apfelmus
1.9.3p194 :005:1> end
=> nil
1.9.3p194 :006:0> drei_mal('Hello World!')
Hello World!
Hello World!
Hello World!
=> nil
Die Klammern beim Aufruf können Sie übrigens weglassen.
1.9.3p194 :007:0> drei_mal 'Hello World!'
Hello World!
Hello World!
Hello World!
=> nil

Tipp

Ruby-Gurus und Möchtegern-Ruby-Gurus werden über unnötige Klammern in Ihren Programmen die Nase rümpfen und wahrscheinlich mehr oder weniger blöde Kommentare mit Vergleichen zu Java und anderen Programmiersprachen machen.
Es gilt in der Ruby-Community eine einfache Regel: Je weniger Klammern, desto cooler! ;-)
Unter uns: Für weniger Klammern gibt es keinen Orden. Entscheiden Sie selber, womit Sie sich am wohlsten fühlen.
Wenn man bei der obigen Methode keinen Parameter angibt, erscheint die Fehlermeldung: wrong number of arguments:
1.9.3p194 :008:0> drei_mal
ArgumentError: wrong number of arguments (0 for 1)
 from (irb):8
 from /usr/local/bin/irb:12:in `<main>'
1.9.3p194 :009:0> exit
Sie können der Variable apfelmus einen Default-Wert geben und dann die Methode auch ohne Parameter aufrufen:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> def drei_mal(apfelmus = 'blau')
1.9.3p194 :002:1>   puts apfelmus
1.9.3p194 :003:1>   puts apfelmus
1.9.3p194 :004:1>   puts apfelmus
1.9.3p194 :005:1> end
=> nil
1.9.3p194 :006:1 > drei_mal('Hello World!')
Hello World!
Hello World!
Hello World!
 => nil 
1.9.3p194 :007:1 > drei_mal
blau
blau
blau
 => nil 
1.9.3p194 :008:1 > exit

Klassen (Classes)

Eine Klasse ist zunächst mal eine Ansammlung von Methoden. Der Name einer Klasse beginnt immer mit einem Großbuchstaben. Nehmen wir einmal an, dass die Methode zur neuen Klasse Dies_und_das gehört. Dann würde sie in einem Ruby-Programm folgendermaßen definiert:
class Dies_und_das       # Beginn der Klassendefinition
  def drei_mal
    puts 'Hello World!'
    puts 'Hello World!'
    puts 'Hello World!'
  end
end                      # Ende der Klassendefinition
Spielen wir das mal im irb durch:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> class Dies_und_das
1.9.3p194 :002:1>   def drei_mal
1.9.3p194 :003:2>     puts 'Hello World!'
1.9.3p194 :004:2>     puts 'Hello World!'
1.9.3p194 :005:2>     puts 'Hello World!'
1.9.3p194 :006:2>   end
1.9.3p194 :007:1> end
=> nil
Jetzt probieren wir aus, die Methode drei_mal aufzurufen:
1.9.3p194 :008:0> Dies_und_das.drei_mal
NoMethodError: undefined method `drei_mal' for Dies_und_das:Class
 from (irb):8
from /usr/local/bin/irb:12:in `<main>'
Das gibt eine Fehlermeldung, denn Dies_und_das ist eine Klasse und keine Instanz. Da wir hier mit Instanz-Methoden arbeiten, funktioniert es nur, wenn wir vorher ein neues Objekt (also eine neue Instanz) der Klasse Dies_und_das mit der Klassen-Methode new erzeugt haben:
1.9.3p194 :009:0> abc = Dies_und_das.new
=> #<Dies_und_das:0x9ebb3f0>
1.9.3p194 :010:0> abc.drei_mal
Hello World!
Hello World!
Hello World!
=> nil
1.9.3p194 :011:0> exit
Auf den genauen Unterschied von Instanz- und Klassen-Methoden gehe ich in „Class Methods und Instance Methods“ ein. Wieder so ein Henne-Ei-Problem.

Private Methoden

Es ist häufig sinnvoll, eine Methode nur innerhalb der eigenen Klasse bzw. der eigenen Instanz aufzurufen. Solche Methoden nennt man private Methoden (im Gegensatz zu den öffentlichen Methoden), und sie werden innerhalb einer Klasse unterhalb des Schlüsselwortes private aufgeführt.
irb-Beispiel:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> class Beispiel         # Klasse Beispiel
1.9.3p194 :002:1>   def mach_was         # Methode mach_was
1.9.3p194 :003:2>     puts 'Was.'           
1.9.3p194 :004:2>   end
1.9.3p194 :005:1>   def mach_noch_was    # Methode mach_noch_was
1.9.3p194 :006:2>     puts 'Noch was.'
1.9.3p194 :007:2>   end
1.9.3p194 :008:1>   
1.9.3p194 :009:1*   private              # Beginn der privaten Methoden
1.9.3p194 :010:1>   def noch_mehr        # Methode noch_mehr
1.9.3p194 :011:2>     puts 'Noch was.'
1.9.3p194 :012:2>   end
1.9.3p194 :013:1> end
=> nil
1.9.3p194 :014:0> test = Beispiel.new    # Neue Instanz von Beispiel
=> #<Beispiel:0x8bb6ffc>
1.9.3p194 :015:0> test.mach_was          # Methode .mach_was aufrufen
Was.
=> nil
1.9.3p194 :016:0> test.mach_noch_was     # Methode .mach_noch_was aufrufen
Noch was.

=> nil
1.9.3p194 :017:0> test.noch_mehr   # Kann nicht von außen aufgerufen werden.
NoMethodError: private method `noch_mehr' called for #<Beispiel:0x8bb6ffc>
 from (irb):17
 from /usr/local/bin/irb:12:in `<main>'
1.9.3p194 :018:0> exit

Methode initialize()

Wird eine neue Instanz erstellt (also die Methode new aufgerufen), dann wird als Erstes und automatisch die Methode initialize abgearbeitet. Die Methode ist automatisch eine private Methode, auch wenn sie nicht explizit im Bereich private aufgeführt wird.
irb-Beispiel:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> class Beispiel
1.9.3p194 :002:1>   def initialize
1.9.3p194 :003:2>     puts 'Hello World!'
1.9.3p194 :004:2>   end
1.9.3p194 :005:1> end
=> nil
1.9.3p194 :006:0> test = Beispiel.new
Hello World!
=> #<Beispiel:0x99f1e24>
1.9.3p194 :007:0> exit
Die Instanz test wird mit Beispiel.new erstellt, und dabei wird zuerst die Methode initialize abgearbeitet. Deshalb sieht man im irb die puts-Ausgabe aus der irb-Zeile 3.
Die Methode new akzeptiert die bei der Methode initialize angegebenen Parameter:
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> class Beispiel
1.9.3p194 :002:1>   def initialize(aaa)   # Definition Parameter aaa
1.9.3p194 :003:2>     puts aaa            # Ausgabe von aaa
1.9.3p194 :004:2>   end
1.9.3p194 :005:1> end
=> nil
1.9.3p194 :006:0> test = Beispiel.new('Hello World!')
Hello World!
=> #<Beispiel:0x86bed84>
1.9.3p194 :007:0> exit

Vererbung

Eine Klasse kann von einer anderen Klasse erben. Bei der Definition der Klasse muss dabei die Eltern-Klasse mit einem < (Kleiner-als-Zeichen) angefügt werden:
class Beispiel < Elternklasse
Von dieser Möglichkeit wird in Rails sehr oft Gebrauch gemacht (ansonsten würde ich Sie damit hier nicht behelligen).
Im folgenden Beispiel definieren wir die Klasse Literatur und fügen die Methode antwort_auf_alle_fragen_des_universums hinzu. Danach definieren wir eine Klasse Allgemeinwissen und vererben dorthin die Klasse Literatur. Die Instanz egon hat Zugriff auf alle Methoden und die Instanz fritz nur auf die der Klasse Literatur.[14]
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> class Literatur
1.9.3p194 :002:1>   def antwort_auf_alle_fragen_des_universums
1.9.3p194 :003:2>     42
1.9.3p194 :004:2>   end
1.9.3p194 :005:1> end
=> nil
1.9.3p194 :006:0> class Allgemeinwissen < Literatur     
1.9.3p194 :007:1>   def pi
1.9.3p194 :008:2>     3.14
1.9.3p194 :009:2>   end
1.9.3p194 :010:1> end
=> nil
1.9.3p194 :011:0> fritz = Literatur.new
=> #<Literatur:0x90e7098>
1.9.3p194 :012:0> egon = Allgemeinwissen.new
=> #<Allgemeinwissen:0x90da0ac>
1.9.3p194 :013:0> egon.antwort_auf_alle_fragen_des_universums
=> 42
1.9.3p194 :014:0> egon.pi
=> 3.14
1.9.3p194 :015:0> fritz.antwort_auf_alle_fragen_des_universums
=> 42
1.9.3p194 :016:0> fritz.pi
NoMethodError: undefined method `pi' for #<Literatur:0x90e7098>
 from (irb):21
 from /usr/local/bin/irb:12:in `<main>'
1.9.3p194 :017:0> exit

Class Methods und Instance Methods

Es gibt zwei wichtige Arten von Methoden: Klassen-Methoden (Class Methods) und Instanz-Methoden (Instance Methods).
Was eine Klasse ist, wissen Sie jetzt. Und eine Instanz einer solchen Klasse wird mit der Klassen-Methode new erstellt. Eine Klassen-Methode kann nur im Zusammenhang mit der Klasse aufgerufen werden (die Methode new ist zum Beispiel eine Klassen-Methode). Eine Instanz-Methode ist eine Methode, die nur mit einer Instanz funktioniert. So können Sie nicht die Methode new auf eine Instanz anwenden.
Probieren wir erst mal aus, eine Instanz-Methode als Klassen-Methode aufzurufen:
sw@debian:~/sandbox$ irb
ruby-1.9.2-p0 > class Wohnung
1.9.3p194 ?>  def putzen
1.9.3p194 ?>    puts 'Wirklich?'
1.9.3p194 ?>    end
1.9.3p194 ?>  end
 => nil 
1.9.3p194 > Wohnung.putzen
NoMethodError: undefined method `putzen' for Wohnung:Class
 from (irb):15
 from /Users/stefan/.rvm/rubies/1.9.3p194/bin/irb:17:in `<main>'
1.9.3p194 > 
Das geht also nicht. Dann erstellen wir eine neue Instanz der Klasse und versuchen es erneut:
1.9.3p194 > apfelmus = Wohnung.new
 => #<Wohnung:0x00000100a06990> 
1.9.3p194 > apfelmus.putzen
Wirklich?
 => nil 
1.9.3p194 > 
Jetzt müssen wir nur noch herausfinden, wie man eine Klassen-Methode definiert. Rails Hardcore-Gurus würden Sie jetzt in die Tiefen des Quellcodes entführen und dort in ActiveRecord Beispiele heraussuchen. Das erspare ich Ihnen und zeige ein abstraktes Beispiel:
1.9.3p194 > class Apfelkuchen
1.9.3p194 ?>  def self.backen
1.9.3p194 ?>    puts 'sinnfreies Beispiel'
1.9.3p194 ?>    end
1.9.3p194 ?>  end
 => nil 
1.9.3p194 > Apfelkuchen.backen
sinnfreies Beispiel
 => nil 
1.9.3p194 > 
Und noch der Gegenbeweis:
1.9.3p194 > lieblingskuchen = Apfelkuchen.new
 => #<Apfelkuchen:0x0000010181a9c8> 
1.9.3p194 > lieblingskuchen.backen
NoMethodError: undefined method `backen' for #<Apfelkuchen:0x0000010181a9c8>
 from (irb):25
 from /Users/stefan/.rvm/rubies/1.9.3p194/bin/irb:17:in `<main>'
1.9.3p194 >
Es gibt verschiedene Schreibweisen, um Klassen-Methoden zu definieren. Die zwei häufigsten:
  • self.xyz
    # Variante 1
    # mit self.xyz
    #
    class Apfelmus
      def self.backen
        puts 'sinnfreies Beispiel'
      end
    end
  • class << self
    # Variante 2
    # mit class << self
    #
    class Apfelmus
      class << self
        def backen
          puts 'sinnfreies Beispiel'
        end
      end
    end
Das Ergebnis ist immer das gleiche.
Liste aller Instanz-Methoden
Für eine Klasse kann man mit der Methode instance_methods alle definierten Methoden auslesen. Wir probieren das mit der Klasse Dies_und_das aus (diese legen wir dazu noch mal neu im irb an):
sw@debian:~/sandbox$ irb
1.9.3p194 :001:0> class Dies_und_das
1.9.3p194 :002:1>   def drei_mal
1.9.3p194 :003:2>     puts 'Hello World!'
1.9.3p194 :004:2>     puts 'Hello World!'
1.9.3p194 :005:2>     puts 'Hello World!'
1.9.3p194 :006:2>   end
1.9.3p194 :007:1> end
=> nil
1.9.3p194 :008:0> Dies_und_das.instance_methods
=> [:drei_mal, :nil?, :===, :=~, :!~, :eql?, :class, :clone, :dup, :taint, :tainted?, :untaint, :untrust, :untrusted?, :trust, :freeze, :frozen?, :to_s, :inspect, :methods, :singleton_methods, :protected_methods, :private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :instance_of?, :kind_of?, :is_a?, :tap, :send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :define_singleton_method, :hash, :__id__, :object_id, :to_enum, :enum_for, :gem, :==, :equal?, :!, :!=, :instance_eval, :instance_exec, :__send__]
Das ist ja viel mehr, als wir definiert haben! Das liegt daran, dass jede neue Klasse von Ruby standardmäßig ein Grundgerüst an Methoden mitbekommt. Wenn wir nur die von uns definierten Methoden auflisten wollen, so geht das wie folgt:
1.9.3p194 :009:0> Dies_und_das.instance_methods(false)
=> [:drei_mal]
1.9.3p194 :010:0> 


[14] Bei dem Satz wird einem ja schwindelig. ;-)

Autor

Stefan Wintermeyer