هل أنت غاضب في كل مرة ترى فيها متغيرات المثيل المتغيرة في وحدة تحكم القضبان لتصفية البيانات؟ هذه المقالة لك 🙂
الفلاتر
ربما تكون قد رأيت هذا من قبل:
# التطبيق/المتحكمون/API/v1/things_controller.rb
وحدة API
الوحدة النمطية V1
صنف ThingsController <المتحكم الأساسي
تعريف الفهرس
@الأشياء = الشيء.الكل
@الأشياء = @things.where(size: params[:size]) إذا كان params[:size]
@الأشياء = @things.where('name ILIKE ?', "%#{params[:name_contains] }%") إذا كان params[:name_contains]
جعل json: @things
النهاية
النهاية
النهاية
النهاية
لماذا أعتبرها سيئة الكود? لأنه ببساطة يجعل وحدة التحكم لدينا سمينة. IMHO يجب أن نستخلص أكبر قدر ممكن من المنطق من وحدات التحكم ونستخدم غرضًا مرتبطًا الأدوات أو الخدمات. في هذه الحالة سنقوم بتنفيذ مرشح عام سنكون قادرين على لاستخدامها عبر العديد من وحدات التحكم.
لكن انتظر، دعنا أولاً نحلل الكود الحالي. قد يكون سيئاً ولكنه يعمل لدينا بعض النطاق الأولي (كل شيء) ومن ثم يتم تقييدها إذا تجاوز المستخدم ذات الصلة. لكل مرشح نتحقق فعليًا مما إذا تم تمرير المعلمة وإذا كان الأمر كذلك, نطبق مرشحًا. الأمر الثاني هو أننا لا نحتاج إلى استخدام ivar، يمكننا استخدام المتغيرات المحلية القديمة العادية.
حسنًا إذًا. ألا يمكننا استخدام كائن خدمة ما لتغيير النطاق الأولي؟ يمكن أن يبدو التنفيذ على هذا النحو:
# التطبيق/المتحكمون/API/v1/things_controller.rb
وحدة API
الوحدة النمطية V1
صنف ThingsController <المتحكم الأساسي
تعريف الفهرس
النطاق = الشيء.الكل
أشياء = أشياء::فهرس الفهرس.new.call(نطاق، بارامز)
تصيير json: الأشياء
النهاية
النهاية
النهاية
النهاية
يبدو أفضل بكثير الآن، ولكن بالطبع علينا تنفيذ الفلتر بعد. لاحظ أن توقيع الاستدعاء سيكون هو نفسه لجميع الموارد، لذلك يمكننا الحصول على بعض الفئات العامة لهذه المهمة.
# app/services/generic/index_filter.rb
وحدة عامة
صنف IndexFilter
EMPTY_HASH = {}.freeze
تعريف self.filters.filters
EMPTY_HASH
نهاية
تعريف استدعاء (نطاق، بارامز)
apply_filters!(self.class.filters.keys, scope, params)
ينتهي
خاص
def apply_filters!(filter_keys, scope, params)
filter_keys.inject.inject(scope.dup) do |current_scope, filter_key|
تطبيق_فلتر!(|مفتاح_التصفية، النطاق_الحالي، البارامز)
النهاية
النهاية
ديف apply_filter!(مفتاح_مفتاح التصفية، النطاق، البارامز)
فلتر = fetch_filter(فلتر_مفتاح التصفية)
إرجاع النطاق ما لم يكن تطبيق_فلتر؟ (فلتر، بارامز)
مرشح[:تطبيق].call(نطاق، بارامز)
إنهاء
ديف apply_filter?(فلتر، بارامز)
مرشح [:تطبيق؟].استدعاء(بارامز)
نهاية
تعريف fetch_filter(filter_key)
self.class.filters.fetch(filter_key) { رفع ArgumentError, 'مرشح غير معروف'}
نهاية
النهاية
النهاية
هل يبدو الأمر معقداً؟ ليس في الحقيقة - كل السحر يحدث في #P63Tapply_filters!. نأخذ نسخة مكررة من النطاق الأولي ونطبق كل مرشح عليه.
عندما نطبق النطاق فهذا يعني أننا نغير نسخة مكررة من النطاق الأولي. ونتوقع أن يتم تنفيذ المرشحات على شكل تجزئة في المرشحات الذاتية الطريقة لفئة الأطفال لنفعلها
# app/services/things/index_filter.rb
الوحدة النمطية أشياء
صنف IndexFilter (بارامز) {
params[:size].is_a؟ (سلسلة)
},
تطبيق: ->(نطاق، بارامز) {
scope.where(size: params[:size])
}
...},
الاسم_يحتوي_على_فلتر: {
تطبيق؟: ->(بارامز) {
params[:name_contains].is_a؟ (سلسلة)
},
تطبيق: ->(نطاق، بارامز) {
scope.where('name ILIKE ?"، "%1T#{params[:name_contains]}%")
}
}.freeze
}.freeze
تعريف self.filters
المرشحات
النهاية
النهاية
النهاية
هذا كل شيء! لقد كتبنا المزيد من التعليمات البرمجية، ولكن ستبدو المرشحات البسيطة كما هي طريقة لجميع الموارد. لقد قمنا بتنظيف وحدة التحكم من الرمز المسؤول من التصفية وتوفير فئة "متخصصة" لهذا الغرض تتبع اصطلاح واضح.