لقد واجهتُ مؤخرًا عدة حالات تتطلب تجاوز (override) لطرق Ruby الموجودة مسبقًا في الإضافات، ففكرتُ في مشاركة أفضل الممارسات الخاصة بي هنا.
تجاوز طريقة مثلية (Instance Method)
class ::TopicQuery
module BabbleDefaultResults
def default_results(options={})
super(options).where('archetype <> ?', Archetype.chat)
end
end
prepend BabbleDefaultResults
end
هنا أقوم بإزالة مواضيع الدردشة من طريقة مثلية تُرجع قائمة بالمواضيع.
يمكن أن يكون اسم الوحدة النمطية BabbleDefaultResults أي شيء تريده؛ عادةً ما أجعله يتطابق مع اسم الطريقة بالإضافة إلى اسم إضافتي لتقليل مخاطر تضارب الأسماء (على الرغم من أنها منخفضة بالفعل).
Module#prepend ميزة رائعة جدًا، ويجب أن تكون على دراية بها إذا كنت تكتب إضافات لأي شيء بلغة Ruby. لاحظ أن حقيقة أننا نستخدم prepend مع وحدة نمطية هي ما يسمح لنا باستدعاء super داخل طريقة التجاوز.
ملاحظة: دائمًا استدر super! هذا يجعل إضافتك أقل عرضة للكسر عند تغيير التطبيق الأساسي. ما لم تكن متأكدًا تمامًا أن وظيفتك تحل محل كل شيء في الطريقة الأساسية، فأنت تريد استدعاء super وتعديل النتائج من هناك، بحيث لا تؤدي التغييرات في هذه الطريقة في نواة Discourse إلى كسر إضافتك لاحقًا.
الرمز :: في ::TopicQuery يضمن أنني أشير إلى فئة TopicQuery على المستوى الأعلى للتجاوز، وليس بعض النسخة الموحدة منها (مثل Babble::TopicQuery).
يمكن وضع هذا مباشرة في ملف plugin.rb كما هو، أو إذا كانت إضافتك كبيرة، يمكنك التفكير في فصل كل تجاوز إلى ملف منفصل.
تجاوز طريقة فئة (Class Method)
class ::Topic
module BabbleForDigest
def for_digest(user)
super(user).where('archetype <> ?', Archetype.chat)
end
end
singleton_class.prepend BabbleForDigest
end
هنا آخذ طريقة self.for_digest الموجودة مسبقًا في فئة Topic، وأزيل مواضيع الدردشة من النتيجة.
مشابه جدًا لتجاوز الطريقة المثلية، مع ملاحظة الفرق وهو أننا نستدعي singleton_class.prepend بدلاً من مجرد prepend. singleton_class هي طريقة غريبة بعض الشيء للقول ‘أريد إرفاق هذا على مستوى الفئة، وليس على مستوى المثيل’، مزيد من القراءة إذا كنت تبحث عن مغامرة في عالم Ruby.
تجاوز نطاق (Scope)
class ::Topic
@@babble_listable_topics = method(:listable_topics).clone
scope :listable_topics, ->(user) {
@@babble_listable_topics.call(user).where('archetype <> ?', Archetype.chat)
}
end
هذه الحالة معقدة قليلاً لأن النطاقات (scopes) لا تعمل بشكل جيد مع super (أو على الأقل، لم أستطع جعلها تعمل). لذا، بدلاً من ذلك، نأخذ تعريف طريقة موجود، ونقوم باستنساخه، وتخزينه، ثم استدعاؤه لاحقًا.
مرة أخرى، يمكن أن يكون @@babble_listable_topics أي شيء تريده، لكن استخدام اسم إضافتك كاسم فضاء (namespacer) فكرة جيدة على الأرجح.
المزيد حول دالة method، وهي أيضًا رائعة جدًا، على الرغم من أن الأوقات التي تحتاج فيها إليها حقًا نادرة جدًا. حقيقة ممتعة إضافية تتعلق بذلك؛ عند التصحيح، إذا واجهت صعوبة في معرفة الكود الذي يتم تنفيذه لاستدعاء طريقة معينة (عادةً “أي مكتبة (gem) تعرف هذه الطريقة؟”)، يمكنك استخدام source_location للحصول على السطر الدقيق من كود المصدر حيث تُعرف الطريقة.
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:
جديد في تطوير discourse و Rails. أستخدم بيئة Dev Container (في VS Code) محليًا. كانت الأدلة والوثائق مفيدة.
كنت أتساءل عما إذا كان لدى أي شخص أي نصائح حول كيفية تجاوز فئات discourse الأساسية، وتحديدًا جعلها مستمرة في بيئة تطوير محلية.
في المكون الإضافي الخاص بي، أحاول تجاوز طريقة في فئة discourse الأساسية TopicEmbed. (باستخدام النهج العام الموثق بشكل جيد من قبل @angus أعلاه.) يعمل مرة واحدة عند إعادة بناء وإعادة تحميل VS Code، ولكن في طلبات HTTP اللاحقة لا يتم استدعاء التجاوز الخاص بي أبدًا.
تم تعريف التجاوز الخاص بي في /plugins/my-plugin/app/models/override.rb وأستخدم require_relative لتضمين هذا الملف في plugin.rb الخاص بي.
#override.rb:
class ::TopicEmbed
# وحدة سيتم إلحاقها بـ TopicEmbed.singleton_class
module TopicEmbedOverrideModule
# طريقة في TopicEmbed
def first_paragraph_from(html)
Rails.logger.info(“my override is happening! ”)
# استمر في التنفيذ الأصلي المقدم من TopicEmbed.
super
end
end
# قم بالإلحاق هنا
singleton_class.prepend TopicEmbedOverrideModule
end
أشك في أن هذا التحدي المتعلق بالاستمرارية قد يكون بسبب بيئة التطوير الخاصة بي وكيفية تجميع/تخزين ذاكرة التخزين المؤقت لرمز ruby.
لقد جربت أيضًا rm -rf tmp; bin/ember-cli -u و bundle exec rake tmp:cache:clear.
لقد جعلتها تعمل لفئة مفردة (singleton class) بهذه الطريقة:
# my overrides.rb
# وحدة سيتم إلحاقها بفئة TopicEmbed المفردة (singleton_class)
module TopicEmbedOverrides
# تجاوز (Override) الدالة parse_html
def parse_html(html, url) # ملاحظة: لا أستخدم self. هنا
# محتوياتي الجديدة هنا
# ثم تشغيل التنفيذ الأصلي
super
end
end
# قم بالتجاوز هنا
class ::TopicEmbed
singleton_class.prepend TopicEmbedOverrides
end