Viele Leute lernen Ruby, indem sie mit dem Rails-Framework beginnen, und das ist leider die denkbar schlechteste Art, diese Sprache zu lernen. Verstehen Sie mich nicht falsch: Rails ist großartig, es hilft Ihnen, Webanwendungen schnell und effizient zu erstellen, ohne sich mit vielen technischen Details beschäftigen zu müssen.
Freut mich, Sie kennenzulernen!
Viele Leute lernen Ruby, indem sie mit dem Rails-Framework beginnen, und das ist leider die denkbar schlechteste Art, diese Sprache zu lernen. Verstehen Sie mich nicht falsch: Rails ist großartig, es hilft Ihnen, Webanwendungen schnell und effizient zu erstellen, ohne sich mit vielen technischen Details beschäftigen zu müssen. Es gibt eine Menge "Rails-Magie", mit der die Dinge einfach funktionieren. Und für einen Programmieranfänger ist das wirklich großartig, denn der schönste Moment des Prozesses ist, wenn man sagen kann: "Es lebt!", und sieht, dass alle Teile zusammenpassen und die Leute Ihre Anwendung benutzen. Wir sind gerne "Schöpfer" 🙂 Aber es gibt eine Sache, die gute Programmierer vom Durchschnitt unterscheidet: Die guten verstehen, wie die Werkzeuge funktionieren, die sie benutzen. Und mit "Ihre Werkzeuge verstehen" meine ich nicht, alle Methoden und Module zu kennen, die ein Framework bietet, sondern zu verstehen, wie es funktioniert, zu verstehen, wie die "Rails-Magie" passiert. Nur dann können Sie sich bei der Verwendung von Objekten und der Programmierung mit Rails wohl fühlen. Die Grundlage der objektorientierten Programmierung, und die Geheimwaffe, die die komplizierte Rails-Anwendung einfacher macht, ist das bereits im Titel erwähnte PORO, das heißt Plain Old Ruby Object
Was verbirgt sich wirklich hinter diesem Namen? Was ist diese große Geheimwaffe? Es handelt sich um eine einfache Ruby-Klasse, die von nichts erbt. Ja, genau das, und noch viel mehr.
Klasse AwesomePoro
end
Wie kann ich Ihnen helfen?
Sie entwickeln Ihre Anwendung ständig weiter und fügen neue Funktionen hinzu, da die Zahl der Benutzer und ihre Erwartungen steigen. Sie kommen an einen Punkt, an dem Sie auf immer mehr dunkle Stellen mit extrem verdrehter Logik stoßen, Stellen, die selbst von den mutigsten Entwicklern wie die Pest gemieden werden. Je mehr solcher Stellen, desto schwieriger wird die Verwaltung und Entwicklung der Anwendung. Ein Standardbeispiel ist die Aktion der Registrierung eines neuen Benutzers, die eine ganze Reihe anderer Aktionen auslöst, die mit diesem Ereignis verbunden sind:
- Überprüfung der IP-Adresse in einer Spam-Datenbank,
- Senden einer E-Mail an den neuen Benutzer,
- Hinzufügen einer Prämie zu einem Konto eines empfehlenden Nutzers,
- Einrichtung von Konten bei verbundenen Diensten,
- und viele mehr...
Eine Probe Code die für die Benutzerregistrierung verantwortlich ist, könnte folgendermaßen aussehen:
class RegistrationController < AnwendungsController
def create
user = User.new(registration_params)
if user.valid? && ip_valid?(registration_ip)
user.save!
user.add_bonuses
benutzer.synchronisieren_verwandte_konten
benutzer.send_email
end
end
end
Okay, Sie haben es kodiert, alles funktioniert, aber... ist der ganze Code wirklich in Ordnung? Vielleicht könnten wir ihn besser schreiben? Zunächst einmal verstößt er gegen das Grundprinzip des Programmierens - die Einzelverantwortung, also können wir ihn sicher besser schreiben. Aber wie? An dieser Stelle kommt das bereits erwähnte PORO zur Hilfe. Es genügt, eine Klasse RegistrationService abzutrennen, die nur für eine Sache zuständig ist: die Benachrichtigung aller zugehörigen Dienste. Unter Diensten verstehen wir die einzelnen Aktionen, die wir oben bereits herausgegriffen haben. In demselben Controller müssen Sie nur ein Objekt RegistrationService erstellen und die Methode "fire!" aufrufen. Der Code ist sehr viel übersichtlicher geworden, unser Controller nimmt weniger Platz in Anspruch und jede der neu erstellten Klassen ist nur noch für eine Aktion zuständig, so dass wir sie bei Bedarf leicht ersetzen können.
class RegistrierungService
def fire!(params)
user = User.new(params)
if user.valid? && ip_validator.valid?(registration_ip)
user.save!
after_registered_events(user)
end
Benutzer
end
privat
def after_registered_events(benutzer)
BonusesCreator.new.fire!(Benutzer)
AccountsSynchronizator.fire!(Benutzer)
EmailSender.fire!(Benutzer)
end
def ip_validator
@ip_validator ||= IpValidator.new
end
end
class RegistrierungsController < AnwendungsController
def erstellen
user = RegistrationService.new.fire!(registration_params)
end
end
Plain Old Ruby Object kann sich jedoch nicht nur für Controller als nützlich erweisen. Stellen Sie sich vor, dass die Anwendung, die Sie erstellen, ein monatliches Abrechnungssystem verwendet. Der genaue Tag der Erstellung einer solchen Abrechnung ist für uns nicht wichtig, wir müssen nur wissen, dass es sich um einen bestimmten Monat und ein bestimmtes Jahr handelt. Natürlich können Sie den Tag für den ersten Tag eines jeden Monats festlegen und diese Information in einem Objekt der Klasse "Datum" speichern, aber das ist weder eine echte Information, noch brauchen Sie sie in Ihrer Anwendung. Mit Hilfe von PORO können Sie eine Klasse "MonthOfYear" erstellen, deren Objekte genau die Informationen speichern, die Sie benötigen. Wenn Sie in dieser Klasse das Modul "Comparable" anwenden, können Sie die Objekte iterieren und vergleichen, genau wie bei der Klasse Date.
class MonthOfYear
include Comparable
attr_leser :jahr, :monat
def initialize(Monat, Jahr)
raise ArgumentError unless month.between?(1, 12)
@Jahr, @Monat = Jahr, Monat
end
def (andere)
[Jahr, Monat] [anderes.Jahr, anderer.Monat]
end
end
Führen Sie mich in Rails ein.
In der Rails-Welt sind wir daran gewöhnt, dass jede Klasse ein Modell, eine Ansicht oder ein Controller ist. Sie haben auch ihren genauen Platz in der Verzeichnisstruktur. Wo können Sie also unsere kleine PORO-Armee unterbringen? Ziehen Sie ein paar Optionen in Betracht. Der erste Gedanke, der mir in den Sinn kommt, ist: Wenn die erstellten Klassen weder Models, noch Views oder Controller sind, sollten wir sie alle in das Verzeichnis "/lib" legen. Theoretisch ist das eine gute Idee, aber wenn alle Ihre PORO-Dateien in einem Verzeichnis landen und die Anwendung groß ist, wird dieses Verzeichnis schnell zu einem dunklen Ort, den Sie nur ungern öffnen. Daher ist es zweifellos keine gute Idee.
AwesomeProject
├──app
│ ├─Controller
│ ├─Modelle
│ └─Ansichten
│
└─lib
└─Dienstleistungen
1TP61Hohes Poro hier
Sie können auch einige Ihrer Klassen, die keine ActiveRecord-Modelle sind, in das Verzeichnis "app/models" und die Klassen, die für die Handhabung anderer Klassen zuständig sind, in das Verzeichnis "app/services" verschieben. Das ist eine ziemlich gute Lösung, hat aber einen Nachteil: Wenn Sie ein neues PORO erstellen, müssen Sie jedes Mal entscheiden, ob es sich eher um ein Modell oder einen Dienst handelt. Auf diese Weise kann es zu einer Situation kommen, in der Sie zwei dunkle Stellen in Ihrer Anwendung haben, nur kleinere. Es gibt noch einen dritten Ansatz, nämlich die Verwendung von Klassen und Modulen mit Namensraum. Alles, was Sie tun müssen, ist, ein Verzeichnis zu erstellen, das denselben Namen wie ein Controller oder ein Modell trägt, und darin alle PORO-Dateien abzulegen, die von dem jeweiligen Controller oder Modell verwendet werden.
AwesomeProject
├──app
│ ├─Controller
│ │ ├─Registrierungscontroller
│ │ │ └─registration_service.rb
│ │ └─registration_controller.rb
│ ├─models
│ │ ├─settlement
│ │ │ └─Monat_von_Jahr.rb
│ │ └─settlement.rb
│ └─views
│
└─lib
Dank dieser Anordnung müssen Sie dem Namen einer Klasse keinen Namespace voranstellen, wenn Sie sie verwenden. Sie haben einen kürzeren Code und eine logischere Verzeichnisstruktur erhalten.
Seht mich an!
Es ist eine angenehme Überraschung, dass bei der Verwendung von PORO die Unit-Tests Ihrer Anwendung schneller und einfacher zu schreiben sind und später auch von anderen besser verstanden werden können. Da jede Klasse jetzt nur noch für eine Sache zuständig ist, können Sie die Randbedingungen früher erkennen und leicht geeignete Testszenarien zu ihnen hinzufügen.
describe MonthOfYear do
subject { MonthOfYear.new(11, 2015) }
it { sollte_kind_of Comparable sein }
describe "neue Instanz erzeugen" do
it "initialisiert mit korrektem Jahr und Monat" do
expect { described_class.new(10, 2015) }.to_not raise_error
end
it "löst einen Fehler aus, wenn der angegebene Monat falsch ist" do
erwarte { described_class.new(0, 2015) }.to raise_error(ArgumentError)
expect { described_class.new(13, 2015) }.to raise_error(ArgumentError)
end
end
end
Ich hoffe, wir sehen uns wieder!
Die vorgestellten Beispiele zeigen deutlich, dass die Verwendung von PORO die Lesbarkeit von Anwendungen verbessert und sie modularer und damit leichter zu verwalten und zu erweitern macht. Die Anwendung des Prinzips der einzigen Verantwortung erleichtert den Austausch bestimmter Klassen, falls erforderlich, ohne andere Elemente zu beeinträchtigen. Auch das Testen der Klassen wird dadurch einfacher und schneller. Außerdem ist es auf diese Weise viel einfacher, Rails-Modelle und -Controller kurz zu halten, die bekanntlich dazu neigen, im Laufe der Entwicklung unnötig groß zu werden.