Ich habe kürzlich mehrere Fälle gehabt, in denen ich bestehende Ruby-Methoden von Plugins überschreiben musste, und möchte meine besten Praktiken hier teilen.
Überschreiben einer Instanzmethode
class ::TopicQuery
module BabbleDefaultResults
def default_results(options={})
super(options).where('archetype <> ?', Archetype.chat)
end
end
prepend BabbleDefaultResults
end
Hier entferne ich Chat-Themen aus einer Instanzmethode, die eine Liste von Themen zurückgibt.
Der Modulname BabbleDefaultResults kann beliebig sein; ich passe ihn normalerweise an den Methodennamen plus meinen Plugin-Namen an, um Namenskonflikte zu minimieren (obwohl diese bereits sehr gering sind).
Module#prepend ist super cool, und wenn Sie Plugins für irgendetwas in Ruby schreiben, sollten Sie damit vertraut sein. Beachten Sie, dass es die Tatsache ist, dass wir ein Modul prependen, die es uns ermöglicht, super innerhalb der überschreibenden Methode aufzurufen.
PS: Rufen Sie immer super auf! Dies macht Ihr Plugin viel weniger anfällig dafür, zu brechen, wenn die zugrunde liegende Implementierung sich ändert. Es sei denn, Sie sind wirklich, wirklich sicher, dass Ihre Funktionalität alles in der zugrunde liegenden Methode vollständig ersetzt, sollten Sie super aufrufen und die Ergebnisse von dort aus modifizieren, damit Änderungen an dieser Methode im Discourse-Kern Ihr Plugin später nicht zum Brechen bringen.
Das :: in ::TopicQuery stellt sicher, dass ich mich auf die Top-Level-Klasse TopicQuery beziehe, um sie zu überschreiben, und nicht auf eine modulare Version davon (wie Babble::TopicQuery).
Dies kann direkt so in plugin.rb eingefügt werden, oder wenn Ihr Plugin groß ist, können Sie jede Überschreibung in eine separate Datei auslagern.
Überschreiben einer Klassenmethode
class ::Topic
module BabbleForDigest
def for_digest(user)
super(user).where('archetype <> ?', Archetype.chat)
end
end
singleton_class.prepend BabbleForDigest
end
Hier nehme ich eine bestehende self.for_digest-Methode in der Topic-Klasse und entferne Chat-Themen aus dem Ergebnis.
Sehr ähnlich zur Überschreibung einer Instanzmethode, wobei der Unterschied darin besteht, dass wir singleton_class.prepend aufrufen anstatt nur prepend. singleton_class ist eine etwas seltsame Art zu sagen: „Ich möchte dies auf Klassenebene und nicht auf Instanzebene anhängen". Weitere Informationen, falls Sie nach einem ruby-bezogenen „Rabbit Hole" suchen.
Überschreiben eines Scopes
class ::Topic
@@babble_listable_topics = method(:listable_topics).clone
scope :listable_topics, ->(user) {
@@babble_listable_topics.call(user).where('archetype <> ?', Archetype.chat)
}
end
Dieser ist etwas knifflig, da Scopes nicht gut mit super funktionieren (oder zumindest konnte ich sie nicht dazu bringen). Stattdessen nehmen wir eine bestehende Methodendefinition, klonen sie, speichern sie und rufen sie später auf.
Auch hier kann @@babble_listable_topics beliebig sein, aber es ist wahrscheinlich eine gute Idee, Ihren Plugin-Namen als Namensraum zu verwenden.
Mehr zur method-Funktion, die auch super cool ist, obwohl die Fälle, in denen man sie wirklich braucht, eher selten sind. Bonus-Tipp: Beim Debuggen, wenn Sie Schwierigkeiten haben, herauszufinden, welcher Code für einen bestimmten Methodenaufruf ausgeführt wird (meistens: „Welches Gem definiert diese Methode?"), können Sie source_location verwenden, um die genaue Zeile des Quellcodes zu erhalten, in der die Methode definiert ist.
I use basically the same structure, except I tend to seperate out the module and the prepend.
As you pointed out, this pattern is “super” useful when trying to avoid overriding core logic.
module InviteMailerEventExtension
def send_invite(invite)
## stuff
super(invite)
end
end
require_dependency 'invite_mailer'
class ::InviteMailer
prepend InviteMailerEventExtension
end
One small tip here is that when overriding private or protected methods, your overriding method also needs to be private or protected, e.g.
module UserNotificationsEventExtension
protected def send_notification_email(opts)
## stuff
super(opts)
end
end
@angus@gdpelican Thanks for this. This is great stuff. . This would be really essential all the (especially newbies like me) plugin developers out there.
This is what I really really needed to be aware of. I use to think that if you override a method only to make a few changes to it, you’d probably copy the code to your new method and make changes to it which by the very thought of it sounded hacky.
Yes, indeed. Since that post, updates to Discourse’s use of rails have made require_dependency unecessary. I’m unable to edit the post to address that. See further:
Haben Sie Tipps zum Überschreiben von Modulklassen? Ich möchte einige Änderungen an GroupGuardian vornehmen (einige spezielle Bedingungen für eine spezielle Art von Gruppe).
Neu bei Discourse und Rails-Entwicklung. Ich verwende die Dev Container-Umgebung (in VS Code) lokal. Die Anleitungen und Dokumentationen waren hilfreich.
Ich habe mich gefragt, ob jemand Tipps hat, wie man Kern-Discourse-Klassen überschreiben kann, insbesondere wie man sie in einer lokalen Entwicklungsumgebung persistent macht.
In meinem Plugin versuche ich, eine Methode in der Kern-Discourse-Klasse TopicEmbed zu überschreiben (unter Verwendung des allgemeinen Ansatzes, der von @angus oben gut dokumentiert wurde). Es funktioniert einmal, wenn ich VS Code neu baue und neu lade, aber bei nachfolgenden HTTP-Anfragen wird meine Überschreibung nie aufgerufen.
Meine Überschreibung ist in /plugins/my-plugin/app/models/override.rb definiert und ich verwende require_relative, um diese Datei in mein plugin.rb einzubinden.
#override.rb:
class ::TopicEmbed
# ein Modul, das in TopicEmbed.singleton_class vorangestellt wird
module TopicEmbedOverrideModule
# Methode in TopicEmbed
def first_paragraph_from(html)
Rails.logger.info(“my override is happening! ”)
# fahre mit der ursprünglichen Implementierung von TopicEmbed fort.
super
end
end
# führe das Prepend hier aus
singleton_class.prepend TopicEmbedOverrideModule
end
Ich vermute, dass diese Herausforderung mit der Persistenz auf meine Entwicklungsumgebung und die Art und Weise, wie Ruby-Code kompiliert/gecacht wird, zurückzuführen sein könnte.
Ich habe auch rm -rf tmp; bin/ember-cli -u und bundle exec rake tmp:cache:clear versucht.
ich habe es auf diese Weise für eine Singleton-Klasse zum Laufen gebracht:
# my overrides.rb
# Ein Modul, das in die TopicEmbed.singleton_class vorangestellt wird
module TopicEmbedOverrides
# parse_html Methode überschreiben
def parse_html(html, url) # Hinweis: ich verwende hier kein self.
# mein neuer Kram hier
# dann die ursprüngliche Implementierung ausführen
super
end
end
# die Überschreibung hier durchführen
class ::TopicEmbed
singleton_class.prepend TopicEmbedOverrides
end