5 أمثلة على أفضل استخدامات روبي
هل تساءلت يومًا ما الذي يمكننا فعله مع روبي؟ حسناً، ربما تكون السماء هي الحد الأقصى، ولكن يسعدنا أن نتحدث عن بعض الحالات المعروفة بشكل أو بآخر...
 
                
سنقوم ببناء تطبيق رف كتب لسرد الكتب مع (أو بدون) بيانات المؤلفين.
سنقوم ببناء تطبيق رف كتب لسرد الكتب مع (أو بدون) بيانات المؤلفين. سيكون هناك #index وبعض البذور. سيكون هذا مثالاً على تطبيق لتوضيح كيف يمكنك منح المستخدم التحكم في الموارد الفرعية في واجهة برمجة تطبيقات REST-ish API.
تشمل معلمة الاستعلام لتحميل الموارد المرتبطة (المؤلف).تشمل تحتوي معلمة الاستعلام على تنسيق سلسلة: كلمات مفصولة بفاصلة، تمثل الموارد المتداخلة.سنستخدم بلوبرنتر كمتسلسل، لأنه لا يعتمد على التنسيق ومرن للغاية. هذه هي الجوهرة الوحيدة التي سنضيفها إلى مجموعة الأدوات القياسية للقضبان.
لننشئ مثالاً على التطبيق. لن نضيف إطار اختبار لأنه خارج نطاقنا.
قضبان رف كتب جديدة -Tأنشئ الآن المؤلف الطراز:
القضبان g اسم مؤلف النموذج: سلسلة
#=> استدعاء السجل النشط
#=> إنشاء db/migrate/2021122224084524_create_authors.rb
#=> إنشاء التطبيق/النماذج/المؤلفين.rbو كتاب:
القضبان ز نموذج كتاب نموذج القضبان المؤلف: مراجع العنوان: سلسلة
# => استدعاء السجل النشط
# => إنشاء تطبيق db/migrate/202112224084614_create_books.rb
# => إنشاء التطبيق/النماذج/كتاب.rbسنحتاج إلى بعض البذور:
# db/seeds.rb
dumas = المؤلف.create(الاسم: 'Alexandre Dumas')
lewis = Author.create(name: 'C.S. Lewis')
مارتين = المؤلف.create(الاسم: 'Robert C. Martin')
كتاب.create(المؤلف: دوماس، العنوان: 'الفرسان الثلاثة')
كتاب.create(المؤلف: لويس، العنوان: 'الأسد والساحرة وخزانة الملابس')
كتاب.create(المؤلف: مارتن، العنوان: "رمز نظيف")والآن نحن جاهزون لتشغيل عمليات الترحيل وبذر قاعدة البيانات:
قضبان قاعدة بيانات القضبان: ترحيل & وقضبان قاعدة بيانات القضبان: البذوردعنا نضيف لديه_كثير للكتب في المؤلف الطراز:
# التطبيق/النماذج/المؤلف.rb
صنف المؤلف <سجل التطبيق
  لديه_كثير :كتب
نهايةحان الوقت لكتابة وحدة تحكم ستعيد بياناتنا. سنستخدم واجهة برمجة التطبيقات مساحة الاسم، لذا دعونا أولاً نضيف اختصارًا إلى التصريفات:
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflector.inflections(:en) do |inflect|
  inflect.acronym 'API'
النهايةحسنًا، لنضف المتسلسل الخاص بنا إلى ملف الجوهرة:
# أضف إلى ملف الأحجار الكريمة
جوهرة 'blueprinter'
وبالطبع قم بتثبيته:
تثبيت الحزمةثم يمكننا بناء مخططاتنا:
# app/blueprints/author_blueprint.rb
صنف AuthorBlueprint < Blueprinter::Base
  المعرف :id
  الحقول :اسم
نهاية# app/blueprints/book_blueprint.rb
صنف BookBlueprint < Blueprinter::Base
  المعرف :معرف
  الحقول :العنوان
  الارتباط :المؤلف، المخطط: مخطط المؤلف
النهايةإضافة وحدة تحكم أساسية لـ واجهة برمجة التطبيقات:
# التطبيق/المتحكمون/API/v1/base_controller.rb
وحدة API
  الوحدة النمطية V1
    صنف BaseController < ActionController::API
    نهاية
  نهاية
النهايةومسودة نسخة مراقب الكتب:
# التطبيق/عناصر التحكم/API/v1/books_controller.rb
الوحدة النمطية API
  الوحدة النمطية V1
    صنف BooksController <المتحكم الأساسي
      تعريف الفهرس
        الكتب = جميع الكتب
        تصيير json: BookBlueprint.render(الكتب)
      النهاية
    النهاية
  النهاية
النهايةيجب علينا أيضًا تحديد التوجيه بالطبع:
# config/routes.rb
Rails.application.routes.routes.draw do
  مساحة الاسم :api do
    مساحة الاسم :v1 do
      الموارد :كتب، فقط: :فهرس
    نهاية
  النهاية
النهايةلنختبر ما قمنا به حتى الآن:
القضبان ق الضفيرة http://localhost:3000/api/v1/books
# => [{"id":1,"author":{"id":1,"id":1,"name":"Alexandre Dumas"},"title":"The Three Musketeers"},{"id":2,"author":{"id":2,"id":2,"name":"C.س. لويس"},"العنوان":"الأسد والساحرة وخزانة الملابس"},{"المعرف":3,"المؤلف":{"المعرف":3,"المؤلف":{"المعرف":3,"الاسم":"روبرت سي مارتن"},"العنوان":"نظيف الكود"}]تبدو البيانات على ما يرام، ماذا عن السجلات؟
سجلات الطلبات # (n+1)
تم بدء GET "/API/v1/books" ل 127.0.0.1 في 2021-12-24 10:19:40 +0100
تتم المعالجة بواسطة API::V1::BooksController#index ك*/*
  تحميل الكتب (0.1 مللي ثانية) حدد "الكتب".* من "الكتب"
  ↳ ↳ التطبيق/المتحكمون/مراقبو/موقع API/v1/books_controller.rb:7:في 'الفهرس'
  تحميل المؤلفين (0.1 مللي ثانية) حدد تحديد "المؤلفين".* من "المؤلفين" حيث "المؤلفون"."id" = ؟ LIMIT ?  [[["المعرف"، 1]، ["LIMIT"، 1]]
  
  تحميل المؤلفين (0.1 مللي ثانية) حدد تحديد "المؤلفين".* من "المؤلفين" حيث "المؤلفون"."id" = ؟ LIMIT ?  [[["المعرف"، 2]، ["LIMIT"، 1]]
  
  تحميل المؤلفين (0.1 مللي ثانية) حدد تحديد "المؤلفين".* من "المؤلفين" حيث "المؤلفون"."id" = ؟ LIMIT ?  [[["المعرف"، 3]، ["LIMIT"، 1]]
  
تم إكمال 200 موافق في 6 مللي ثانية (المشاهدات: 0.1 مللي ثانية | ActiveRecord: 0.4 مللي ثانية | التخصيصات: 3134)باستخدام الارتباط في المتسلسلات لدينا، قدمنا n+1 مشكلة. نريد التخلص منها بإضافة تحكم المستخدم فيما يطلبه في نقطة النهاية هذه. لذا يجب أن يكون قادرًا إما على تحميل الكتب فقط، أو تمرير معلمة التضمين والحصول على المؤلفين أيضًا، ولكن يفضل أن يكون ذلك بدون n+1.
دعونا نعرّف ثابتًا يحتفظ بمعلومات حول ما يمكن للمستخدم تضمينه في كتب#index الإجراء:
# lib/constants/books/includes.rb
وحدة الثوابت
  وحدة الكتب
    وحدة التضمينات
      مسموح = {
        فهرس %i[
          مؤلف
        ].تجميد
      }.freeze
    النهاية
  النهاية
النهايةبعد ذلك، نحدد مساحة اسم لثوابت الكائنات الفارغة:
# lib/constants/empty.rb
وحدة الثوابت
  وحدة فارغة
    HASH = {}.freeze
  النهاية
النهايةوهذه هي خدمتنا الرئيسية للسماح بالتضمين. أعتقد أن الرمز لا يحتاج إلى شرح، بعض الأجزاء من السحر يتم تخصيصها فقط في #default_resources_resources_key و #Default_purpose. تم تعريف هذه الأساليب للسماح لنا باستدعاء تصريح يتضمن تمرير البارامترات فقط في وحدات تحكم القضبان. سيكون الناتج هو التجزئة التي تخزن صحيح لكل إدراج مسموح به.
# app/services/permit_includes.rb
تتطلب 'ثوابت/فارغة'
تتطلب 'ثوابت/ثوابت/كتب/مشتملات'
فئة PermitIncludes
  فارغة = الثوابت::فارغة
  كوما = '،'
  SLASH = '/'
  INCLUDES_FORMAT = / A[a-z]+(,[a-z]+)*z/.freeze
  المسموح_بإدراجها = {
    الكتب الثوابت::كتب::يشمل::ALLOWED
  }.freeze
  def call(params, resources: default_resources_key(params), purpose: default_purpose(params))
    إرجاع فارغ::HASH ما لم يتضمن_مرسل؟ (بارامز)
    إرجاع فارغ::HASH ما لم يتضمن_صحيح؟(params)
    التضمينات_المطلوبة = تحليل_التضمينات(params)
    المسموح_بالتضمينات المسموح بها = تصفية_التضمينات(تضمينات_مطلوبة، الموارد، الغرض)
    المسموح_بإدراجها.index_with(صحيح)
  النهاية
  خاص
  def default_resources_key(params)
    رفع (ArgumentError, 'params :مفتاح وحدة التحكم يجب أن يكون سلسلة') إلا إذا كان params[:وحدة تحكم].is_a?(String)
    params[:وحدة تحكم].split(SLASH).last&.to_sym
  النهاية
  def default_purpose(params)
    رفع(ArgumentError, 'البارامز :مفتاح الإجراء يجب أن يكون سلسلة') إلا إذا كان البارامز[:إجراء].is_a?(سلسلة)
    params[:إجراء].to_sym
  إنهاء
  ديف يتضمن_الإرسال؟(params)
    params.key?(:includes)
  نهاية
  ديف includes_valid?(params)
    إرجاع خطأ ما لم يكن params[:يتضمن].is_a?(String)
    params[:includes].match؟(INCLUDES_FORMAT)
  النهاية
  تعريف parse_includes(params)
    params[:includes].split(COMMA).map(&:to_sym)
  النهاية
  def filter_includes(requested_includes, resources_key, purpose)
    المطلوب_يتضمن & ALLOWED_INCLUDES [الموارد_المفتاح] [الغرض]
  نهاية
النهايةنحتاج الآن إلى استخدام المفاتيح لتحميل التضمينات وتمرير تجزئة التضمينات نفسها إلى أداة التسلسل:
# التطبيق/عناصر التحكم/API/v1/books_controller.rb
الوحدة النمطية API
  الوحدة النمطية V1
    صنف BooksController <المتحكم الأساسي
      تعريف الفهرس
        يتضمّن = PermitIncludes.new.call(params)
        الكتب = Book.includes.includes(includes.keys).all
        تصيير json: تصيير BookBlueprint.render(كتب، يتضمن: يتضمن)
      النهاية
    النهاية
  النهاية
النهايةوهذه هي الطريقة التي يجب أن نعدّل بها أداة التسلسل لدينا - نقوم بتحميل الارتباط فقط إذا كان متضمنًا:
# app/blueprints/book_blueprint.rb
صنف BookBlueprint (_حقل_الاسم، _كتاب، خيارات) {
                         خيارات[:يتضمن] && خيارات[:يتضمن][:المؤلف]
                       }
النهايةلنختبرها مرة أخرى:
القضبان قتجعيد http://localhost:3000/api/v1/books
# => [{"المعرف":1، العنوان: "الفرسان الثلاثة"},{"المعرف":2، العنوان: "الأسد والساحرة وخزانة الملابس"},{"المعرف":3، العنوان: "الرمز النظيف"}]سجلات طلب # (نقوم بتحميل الكتب فقط)
بدأ GET "/API/v1/books" لـ ::1 في 2021-12-24 10:33:41 +0100
تتم المعالجة بواسطة API::V1::V1::BooksController#index ك*/*
   (0.1 مللي ثانية) حدد sqlite_version (*)
  ↳ التطبيق/المتحكمون/API/v1/books_controller.rb:8:في 'الفهرس'
  تحميل الكتب (0.1 مللي ثانية) حدد "الكتب".* من "الكتب"
  ↳ التطبيق/المتحكمون/واجهة المستخدم/واجهة المستخدم/v1/books_controller.rb:8:8:في 'الفهرس'
تم الإكمال 200 موافق في 9 مللي ثانية (المشاهدات: 0.1 مللي ثانية | ActiveRecord: 0.9 مللي ثانية | التخصيصات: 4548)جيد، لم نجتاز التضمينات، لذا حصلنا على الكتب فقط، دون المؤلفين. لنطلبهم الآن:
تجعيد 'http://localhost:3000/api/v1/books?includes=author'
# => [{"id":1,"author":{"id":1,"name":"Alexandre Dumas"},"title":"The Three Musketeers"},{"id":2,"author":{"id":2,"id":2,"name":"C.S. لويس"},"العنوان":"الأسد والساحرة وخزانة الملابس"},{"المعرف":3,"المؤلف":{"المعرف":3,"المؤلف":{"المعرف":3,"الاسم":"روبرت سي مارتن"},"العنوان":"الرمز النظيف"}]% سجلات الطلبات # (تم التخلص من n+1)
تم بدء GET "/API/v1/books?includes=author" لـ ::1 في 2021-12-24 10:38:23 +0100
تتم المعالجة بواسطة API::V1::BooksController#index كـ */*
  المعلمات: {"includes"=>"author"}
  تحميل الكتب (0.1 مللي ثانية) حدد "الكتب".* من "الكتب"
  
  تحميل المؤلفين (0.2 مللي ثانية) حدد "المؤلفين".* من "المؤلفين" حيث "المؤلفون"."id" في (?, ?, ?, ?) [[["id", 1]، ["id", 2]، ["id", 3]]
  
تم الإكمال 200 موافق في 17 مللي ثانية (المشاهدات: 0.1 مللي ثانية | ActiveRecord: 0.7 مللي ثانية | التخصيصات: 7373)رائع! لقد قمنا بتحميل الجمعية وإزالتها n+1 مشكلة. يمكن استخدام الخدمة لأي مورد، كل ما نريده هو إضافة الثوابت المسموح بها بالصيغة المناسبة وإضافتها إلى تصاريح التضمينات::ALLOWED_INCLUDES.
علينا أن نتذكر أنه ربما يجب استخدام هذا الأمر مع ترقيم الصفحات (والحذر) لأن تضمين الارتباطات يمكن أن "يأكل" الكثير من الذاكرة.