البحث الفوري عن Rails وActiveRecord باستخدام عروض SQL المتحققة.
انظر التطبيق التجريبي (الرمز موجود في مجلد demo_app/
):
أضف إمكانات البحث السريع إلى تطبيقات Rails الخاصة بك دون الحاجة إلى أنظمة خارجية مثل ElasticSearch. أصبح الآن من السهل جدًا صياغة تعبيرات ActiveRecord/Arel التي نعرفها ونحبها بالفعل، وتحويلها إلى عروض SQL متحققة: جاهزة للاستعلام عنها وتكوينها باستخدام ActiveRecord. كل ما تحبه في Rails، ولكن بشكل أسرع.
ما الذي يجعل Rails بطيئًا في البحث؟ جداول كبيرة، والكثير من الصلات، والاستعلامات الفرعية، والفهارس المفقودة أو غير المستخدمة، والاستعلامات المعقدة. بطيئة أيضا؟ تنسيق البيانات من أنظمة خارجية متعددة من خلال روبي لإنتاج نتائج البحث.
يجعل SearchCraft من السهل كتابة واستخدام طرق عرض SQL قوية لحساب نتائج استعلامات البحث وإعداد التقارير مسبقًا. إنه مثل فهرس قاعدة البيانات، ولكن للاستعلامات المعقدة.
تعد طرق العرض المادية ميزة رائعة في PostgreSQL وOracle وSQL Server*. وهي عبارة عن جدول بنتائج استعلام محسوبة مسبقًا. إنهم سريعون في الاستعلام. إنهم رائعون. مثل أنظمة البحث الأخرى، يمكنك التحكم عندما تريد تحديثها ببيانات جديدة.
داخل Rails وActiveRecord، يمكنك الوصول إلى عرض مادي للقراءة فقط كما تفعل مع أي جدول عادي. يمكنك حتى الانضمام إليهم معًا. يمكنك استخدامها في نماذج ونطاقات وارتباطات ActiveRecord الخاصة بك.
class ProductSearch < ActiveRecord :: Base
include SearchCraft :: Model
end
منتهي. مهما كانت الأعمدة التي تصفها في طريقة العرض الخاصة بك، فسوف تصبح سمات في النموذج الخاص بك.
إذا كان العرض الأساسي يحتوي على أعمدة product_id
و product_name
و reviews_count
و reviews_average
، فيمكنك الاستعلام عنه مثل أي نموذج ActiveRecord آخر:
ProductSearch . all
[ #<ProductSearch product_id: 2, product_name: "iPhone 15", reviews_count: 5, reviews_average: 0.38e1>,
#<ProductSearch product_id: 1, product_name: "Laptop 3", reviews_count: 5, reviews_average: 0.28e1>,
#<ProductSearch product_id: 4, product_name: "Monopoly", reviews_count: 3, reviews_average: 0.2e1>]
ProductSearch . order ( reviews_average : :desc )
[ #<ProductSearch product_id: 2, product_name: "iPhone 15", reviews_count: 5, reviews_average: 0.38e1>,
#<ProductSearch product_id: 1, product_name: "Laptop 3", reviews_count: 5, reviews_average: 0.28e1>,
#<ProductSearch product_id: 4, product_name: "Monopoly", reviews_count: 3, reviews_average: 0.2e1>]
إذا قمت بتضمين مفاتيح خارجية، فيمكنك استخدام اقترانات belongs_to
. يمكنك إضافة نطاقات. يمكنك إضافة طرق. يمكنك استخدامه كنقطة بداية للاستعلامات مع بقية قاعدة بيانات SQL الخاصة بك. إنه مجرد نموذج ActiveRecord عادي.
كل هذا ممكن بالفعل مع Rails وActiveRecord. إن إنجاز SearchCraft هو جعل التعايش مع وجهات نظرك المادية أمراً تافهاً. تافهة لتحديثها وكتابتها.
يعرض كل تطبيق SearchCraft لقطة من نتائج الاستعلام في وقت إنشائه أو تحديثه آخر مرة. إنه يشبه الجدول الذي يتم اشتقاق محتوياته من استعلام.
إذا تغيرت البيانات الأساسية في عرض SearchCraft الخاص بك وتريد تحديثها، فاتصل refresh!
في صفك النموذجي. يتم توفير ذلك من خلال مزيج SearchCraft::Model
.
ProductSearch . refresh!
يمكنك تمرير علاقة/مصفوفة ActiveRecord هذه إلى عروض Rails الخاصة بك وعرضها. يمكنك ضمها إلى جداول أخرى وتطبيق المزيد من النطاقات.
لكن الميزة الأعظم في SearchCraft هي مساعدتك في كتابة وجهات نظرك المتجسدة ، ومن ثم تكرارها.
قم بتصميمها في تعبيرات ActiveRecord، أو تعبيرات Arel، أو حتى SQL عادي. لا توجد عمليات ترحيل للتراجع وإعادة التشغيل. لا يمكنك تتبع ما إذا كان عرض SQL في قاعدة بياناتك يتطابق مع كود SearchCraft في تطبيق Rails الخاص بك. سيقوم SearchCraft تلقائيًا بإنشاء وتحديث طرق العرض الفعلية الخاصة بك.
قم بتحديث طريقة عرض SearchCraft الخاصة بك، وقم بإجراء اختباراتك، وهي تعمل. قم بتحديث عرض SearchCraft الخاص بك، وقم بتحديث تطبيق التطوير الخاص بك، وسيعمل. افتح rails console
وسيعمل؛ ثم قم بتحديث العرض الخاص بك، اكتب reload!
ويعمل. انشره في الإنتاج في أي مكان، وسيعمل.
كيف يبدو تصميم عرض ملموس باستخدام SearchCraft؟ بالنسبة لنموذج ProductSearch
أعلاه، قمنا بإنشاء فئة ProductSearchBuilder
التي ترث من SearchCraft::Builder
وتوفر إما طريقة view_scope
أو طريقة view_select_sql
.
class ProductSearchBuilder < SearchCraft :: Builder
def view_scope
Product . where ( active : true )
. select (
"products.id AS product_id" ,
"products.name AS product_name" ,
"(SELECT COUNT(*) FROM product_reviews WHERE product_reviews.product_id = products.id) AS reviews_count" ,
"(SELECT AVG(rating) FROM product_reviews WHERE product_reviews.product_id = products.id) AS reviews_average"
)
end
end
يجب أن تقوم طريقة view_scope
بإرجاع علاقة ActiveRecord. يمكن أن تكون بسيطة أو معقدة كما تريد. يمكنه استخدام الصلات والاستعلامات الفرعية وأي شيء آخر يمكنك القيام به باستخدام ActiveRecord. في المثال أعلاه نحن:
id
name
من جدول products
؛ حيث يمكننا لاحقًا استخدام product_id
كمفتاح خارجي للانضمام إلى نموذج Product
في تطبيقناreviews_count
و reviews_average
الجديدة باستخدام استعلامات SQL الفرعية التي تقوم بحساب ومتوسط عمود rating
من جدول product_reviews
. سيقوم SearchCraft بتحويل هذا إلى عرض ملموس، وإنشائه في قاعدة البيانات الخاصة بك، وسيبدأ نموذج ProductSearch
أعلاه في استخدامه عند إعادة تحميل تطبيق التطوير الخاص بك أو تشغيل اختباراتك في المرة التالية. إذا قمت بإجراء تغيير، فسوف يقوم SearchCraft بإسقاط العرض وإعادة إنشائه تلقائيًا.
عندما نقوم بتحميل تطبيقنا إلى وحدة تحكم Rails، أو نجري اختباراتنا، أو نقوم بتحديث تطبيق التطوير، سيتم تحديث نموذج ProductSearch
تلقائيًا لمطابقة أي تغييرات في ProductSearchBuilder
.
ProductSearch . all
[ #<ProductSearch product_id: 2, product_name: "iPhone 15", reviews_count: 5, reviews_average: 0.38e1>,
#<ProductSearch product_id: 1, product_name: "Laptop 3", reviews_count: 5, reviews_average: 0.28e1>,
#<ProductSearch product_id: 4, product_name: "Monopoly", reviews_count: 3, reviews_average: 0.2e1>]
ProductSearch . order ( reviews_average : :desc )
[ #<ProductSearch product_id: 2, product_name: "iPhone 15", reviews_count: 5, reviews_average: 0.38e1>,
#<ProductSearch product_id: 1, product_name: "Laptop 3", reviews_count: 5, reviews_average: 0.28e1>,
#<ProductSearch product_id: 4, product_name: "Monopoly", reviews_count: 3, reviews_average: 0.2e1>]
إذا كنت تريد كتابة SQL، فيمكنك استخدام طريقة view_select_sql
بدلاً من ذلك.
class NumberBuilder < SearchCraft :: Builder
# Write SQL that produces 5 rows, with a 'number' column containing the number of the row
def view_select_sql
"SELECT generate_series(1, 5) AS number;"
end
end
class Number < ActiveRecord :: Base
include SearchCraft :: Model
end
Number . all
[ #<Number number: 1>, #<Number number: 2>, #<Number number: 3>, #<Number number: 4>, #<Number number: 5>]
الميزة الرائعة لطرق العرض المتحققة هي أنه يمكنك إضافة فهارس إليها؛ حتى الفهارس الفريدة.
تتمثل آلية إضافة الفهارس حاليًا في إضافة طريقة view_indexes
إلى فئة الإنشاء الخاصة بك.
على سبيل المثال، يمكننا إضافة فهرس فريد في عمود number
الخاص بـ NumberBuilder
:
class NumberBuilder < SearchCraft :: Builder
def view_indexes
{
number : { columns : [ "number" ] , unique : true }
}
end
أو عدة فهارس في ProductSearchBuilder
من وقت سابق:
class ProductSearchBuilder < SearchCraft :: Builder
def view_indexes
{
id : { columns : [ "product_id" ] , unique : true } ,
product_name : { columns : [ "product_name" ] } ,
reviews_count : { columns : [ "reviews_count" ] } ,
reviews_average : { columns : [ "reviews_average" ] }
}
end
end
بشكل افتراضي using: :btree
. يمكنك أيضًا استخدام طرق الفهرسة الأخرى المتوفرة في Rails، مثل :gin
، :gist
، أو إذا كنت تستخدم ملحق trigram
فيمكنك استخدام :gin_trgm_ops
. يمكن أن تكون هذه مفيدة عندما تفكر في إعداد البحث عن النص، كما هو موضح أدناه.
فائدة أخرى لطرق العرض المتحققة هي أنه يمكننا إنشاء أعمدة مُحسّنة للبحث. على سبيل المثال أعلاه، نظرًا لأننا قمنا بحساب reviews_average
مسبقًا في ProductSearchBuilder
، فيمكننا بسهولة العثور على المنتجات ذات متوسط تقييم معين.
ProductSearch . where ( "reviews_average > 4" )
الميزة الرائعة لـ ActiveRecord هي القدرة على ضم الاستعلامات معًا. نظرًا لأن عروضنا المتحققة هي نماذج ActiveRecord أصلية، فيمكننا دمجها مع الاستعلامات الأخرى.
لنقم بإعداد ارتباط بين ProductSearch#product_id
الخاص بـ MV والمفتاح الأساسي Product#id
في الجدول:
class ProductSearch < ActiveRecord :: Base
include SearchCraft :: Model
belongs_to :product , foreign_key : :product_id , primary_key : :id
end
يمكننا الآن ضم الجداول أو تحميلها مع استعلامات ActiveRecord. إلى ما يلي يُرجع علاقة كائنات ProductSearch
، مع تحميل كل اقتران ProductSearch#product
الخاص بها مسبقًا.
ProductSearch . includes ( :product ) . where ( "reviews_average > 4" )
تقوم الإرجاعات التالية بإرجاع كائنات Product
، استنادًا إلى البحث في العرض الفعلي ProductSearch
:
class Product
has_one :product_search , foreign_key : :product_id , primary_key : :id , class_name : "ProductSearch"
end
Product . joins ( :product_searches ) . merge (
ProductSearch . where ( "reviews_average > 4" )
)
يأتي PostgreSQL مزودًا بحل للبحث عن النص باستخدام مجموعة من الوظائف مثل to_tsvector
و ts_rank
و websearch_to_tsquery
.
في انتظار المزيد من المستندات، راجع test/searchcraft/builder/test_text_search.rb
للحصول على مثال حول كيفية استخدام هذه الوظائف في طرق العرض الفعلية الخاصة بك.
ما زلت أعمل على استخراج هذا الحل من الكود الخاص بنا في Store Connect.
بمجرد حصولك على طريقة عرض واحدة مجسدة لـ SearchCraft، قد ترغب في إنشاء طريقة عرض أخرى تعتمد عليها. يمكنك القيام بذلك أيضًا باستخدام الطريقة depends_on
.
class SquaredBuilder < SearchCraft :: Builder
depends_on "NumberBuilder"
def view_select_sql
"SELECT number, number * number AS squared FROM #{ Number . table_name } ;"
end
end
class Squared < ActiveRecord :: Base
include SearchCraft :: Model
end
إذا قمت بإجراء تغيير على NumberBuilder
، فسيقوم SearchCraft تلقائيًا بإسقاط وإعادة إنشاء كل من العرضين الماديين Number
Squared
.
Squared . all
[ #<Squared number: 1, squared: 1>,
#<Squared number: 2, squared: 4>,
#<Squared number: 3, squared: 9>,
#<Squared number: 4, squared: 16>,
#<Squared number: 5, squared: 25>]
لست واثقًا من كتابة تعبيرات SQL أو Arel المعقدة؟ أنا أيضاً. أطلب من GPT4 أو GitHub Copilot. أشرح طبيعة المخطط والجداول الخاصة بي، وأطلب منه كتابة بعض SQL، ثم أطلب تحويله إلى Arel. أو أعطيه مقتطفًا صغيرًا من SQL وأطلب منه تحويله إلى Arel. ثم أقوم بنسخ/لصق النتائج في فئة منشئ SearchCraft الخاصة بي.
من المفيد للغاية أن تتعلم كيفية التعبير عن استعلامات البحث الخاصة بك في SQL أو Arel، ووضعها في طريقة عرض SearchCraft الفعلية. سيكون لدى المستخدمين لديك تجربة سريعة للغاية.
داخل تطبيق Rails، أضف الجوهرة إلى ملف Gemfile الخاص بك:
bundle add searchcraft
سيقوم SearchCraft تلقائيًا بإنشاء جدول قاعدة بيانات داخلي يحتاجه، لذلك لا يوجد ترحيل لقاعدة البيانات للتشغيل. وبطبيعة الحال، فإنه سيتم تلقائيا إنشاء وإعادة إنشاء وجهات نظرك المادية.
يمكنك متابعة هذا البرنامج التعليمي داخل أي تطبيق Rails. إذا لم يكن لديك تطبيق Rails، فاستخدم التطبيق الموجود في مجلد demo_app
الخاص بهذا المشروع.
تثبيت الجوهرة:
bundle add searchcraft
اختر أحد نماذج التطبيقات الموجودة لديك، على سبيل المثال Product
، وسوف نقوم بإنشاء عرض مادي تافه له. لنفترض أننا نريد طريقة سريعة للحصول على أفضل 5 منتجات مبيعًا وبعض التفاصيل التي سنستخدمها في عرض HTML الخاص بنا.
قم بإنشاء ملف نموذج ActiveRecord جديد app/models/product_latest_arrival.rb
:
class ProductLatestArrival < ActiveRecord :: Base
include SearchCraft :: Model
end
من خلال اصطلاحات ريلز، سيبحث هذا النموذج عن جدول SQL أو عرض يسمى product_latest_arrivals
. هذا غير موجود بعد
يمكننا تأكيد ذلك عن طريق فتح rails console
ومحاولة الاستعلام عنها:
ProductLatestArrival . all
# ActiveRecord::StatementInvalid ERROR: relation "product_latest_arrivals" does not exist
يمكننا إنشاء فئة منشئ SearchCraft جديدة لتحديد وجهة نظرنا المادية. قم بإنشاء ملف جديد app/searchcraft/product_latest_arrival_builder.rb
.
أقترح app/searchcraft
للمنشئين لديك، لكن يمكنهم الدخول إلى أي app/
مجلد فرعي يتم تحميله تلقائيًا بواسطة Rails.
class ProductLatestArrivalBuilder < SearchCraft :: Builder
def view_scope
Product . order ( created_at : :desc ) . limit ( 5 )
end
end
داخل rails console``, run
إعادة التحميل! وتحقق من استعلامك مرة أخرى:
reload!
ProductLatestArrival . all
ProductLatestArrival Load ( 1.3 ms ) SELECT "product_latest_arrivals" . * FROM "product_latest_arrivals"
=>
[ #<ProductLatestArrival:0x000000010a737d18
id : 1 ,
name : "Rustic Wool Coat" ,
active : true ,
created_at : Fri , 25 Aug 2023 07 :15 :16 . 995228000 UTC + 00 : 00 ,
updated_at : Fri , 25 Aug 2023 07 :15 :16 . 995228000 UTC + 00 : 00 ,
image_url : "https://loremflickr.com/g/320/320/coat?lock=1" > ,
...
إذا كانت لديك جوهرة annotate
مثبتة في Gemfile
الخاص بك، فستلاحظ أيضًا أنه تم تحديث نموذج product_latest_arrival.rb
ليعكس الأعمدة في العرض الفعلي.
# == Schema Information
#
# Table name: product_latest_arrivals
#
# id :bigint
# name :string
# active :boolean
# created_at :datetime
# updated_at :datetime
# image_url :string
#
class ProductLatestArrival < ActiveRecord :: Base
include SearchCraft :: Model
end
إذا كان التطبيق الخاص بك تحت التحكم بالمصادر، فيمكنك أيضًا ملاحظة أنه تم تحديث db/schema.rb
ليعكس أحدث تعريف للعرض. تشغيل git diff db/schema.rb
:
create_view "product_latest_arrivals" , materialized : true , sql_definition : <<-SQL
SELECT products.id,
products.name,
products.active,
products.created_at,
products.updated_at,
products.image_url
FROM products
LIMIT 5;
SQL
يمكنك الآن الاستمرار في تغيير view_scope
في منشئك، وتشغيل reload!
في وحدة التحكم Rails لاختبار التغيير.
على سبيل المثال، يمكنك select()
فقط الأعمدة التي تريدها باستخدام تعبير SQL لكل منها:
class ProductLatestArrivalBuilder < SearchCraft :: Builder
def view_scope
Product
. order ( created_at : :desc )
. limit ( 5 )
. select (
"products.id as product_id" ,
"products.name as product_name" ,
"products.image_url as product_image_url" ,
)
end
end
أو يمكنك استخدام تعبيرات Arel لإنشاء SQL:
class ProductLatestArrivalBuilder < SearchCraft :: Builder
def view_scope
Product
. order ( created_at : :desc )
. limit ( 5 )
. select (
Product . arel_table [ :id ] . as ( "product_id" ) ,
Product . arel_table [ :name ] . as ( "product_name" ) ,
Product . arel_table [ :image_url ] . as ( "product_image_url" ) ,
)
end
end
ماذا عن تحديثات البيانات؟ لنقم بإنشاء المزيد من Products
:
Product . create! ( name : "Starlink" )
Product . create! ( name : "Fishing Rod" )
إذا قمت بفحص ProductLatestArrival.all
، فلن تجد هذه المنتجات الجديدة. وذلك لأن العرض الفعلي عبارة عن لقطة من البيانات في وقت إنشائها أو تحديثها آخر مرة.
لتحديث العرض:
ProductLatestArrival . refresh!
بدلاً من ذلك، لتحديث كافة طرق العرض:
SearchCraft :: Model . refresh_all!
والتأكد من أن أحدث الوافدين الجدد أصبحوا الآن في العرض الفعلي:
ProductLatestArrival . pluck ( :name )
=> [ "Fishing Rod" , "Starlink" , "Sleek Steel Bag" , "Ergonomic Plastic Bench" , "Fantastic Wooden Keyboard" ]
إذا كنت ترغب في إزالة القطع الأثرية من هذا البرنامج التعليمي. أولاً، قم بإسقاط العرض الفعلي من مخطط قاعدة البيانات الخاصة بك:
ProductLatestArrivalBuilder . new . drop_view!
ثم قم بإزالة الملفات و git checkout .
للتراجع عن أي تغييرات أخرى.
rm app/searchcraft/product_latest_arrival_builder.rb
rm app/models/product_latest_arrival.rb
git checkout db/schema.rb
يوفر SearchCraft مهمتين أشعل النار:
rake searchcraft:refresh
- تحديث جميع طرق العرض المتحققةrake searchcraft:rebuild
- تحقق مما إذا كانت هناك حاجة إلى إعادة إنشاء أي طرق عرض لإضافة هذه العناصر إلى تطبيق Rails الخاص بك، أضف ما يلي إلى أسفل Rakefile
الخاص بك:
SearchCraft . load_tasks
Builder
، وتكتشف التغيير تلقائيًا لتجسيد مخطط العرض وإعادة إنشائهrefresh!
لمحتويات العرض المتحققةdb/schema.rb
كلما تم تحديث العرض الفعليannotate
rake searchcraft:refresh
وتحقق مما إذا كانت هناك حاجة إلى إعادة إنشاء أي طرق عرض rake searchcraft:rebuild
بعد التحقق من الريبو، قم بتشغيل bin/setup
لتثبيت التبعيات. ثم قم بتشغيل rake test
لإجراء الاختبارات. يمكنك أيضًا تشغيل bin/console
للحصول على مطالبة تفاعلية تسمح لك بالتجربة.
لتثبيت هذه الجوهرة على جهازك المحلي، قم بتشغيل bundle exec rake install
. لإصدار إصدار جديد، قم بتحديث رقم الإصدار في version.rb
، ثم قم بتشغيل bundle exec rake release
، الذي سينشئ علامة git للإصدار، ويدفع التزامات git والعلامة التي تم إنشاؤها، ويدفع ملف .gem
إلى Rubygems. ORG.
لرفع رقم الإصدار:
gem bump
، على سبيل المثال gem bump -v patch
demo_app/Gemfile.lock
، على سبيل المثال (cd demo_app; bundle)
git add demo_app/Gemfile.lock; git commit --amend --no-edit
rake release
gem bump -v patch
(cd demo_app; bundle)
git add demo_app/Gemfile.lock; git commit --amend --no-edit
git push
rake release
نرحب بتقارير الأخطاء وطلبات السحب على GitHub على https://github.com/drnic/searchcraft. يهدف هذا المشروع إلى أن يكون مساحة آمنة ومرحبة للتعاون، ومن المتوقع أن يلتزم المساهمون بمدونة قواعد السلوك.
الجوهرة متاحة كمصدر مفتوح بموجب شروط ترخيص MIT.
من المتوقع أن يتبع كل شخص يتفاعل في قواعد التعليمات البرمجية لمشروع Searchcraft ومتتبعي المشكلات وغرف الدردشة والقوائم البريدية قواعد السلوك.
rails db:rollback
، وإعادة بناء ترحيل SQL، rails db:migrate
، ثم الاختبار - أصبح بطيئًا. لقد أدى أيضًا إلى ظهور أخطاء - كنت أنسى تشغيل الخطوات ثم أرى سلوكًا غريبًا. إذا كانت لديك طرق عرض ثابتة نسبيًا أو طرق عرض متحققة، وترغب في استخدام عمليات ترحيل Rails، فيرجى تجربة الجوهرة scenic
. لا تزال جوهرة searchcraft
هذه تعتمد على scenic
لميزة refresh
العرض وإضافة طرق العرض إلى schema.rb
.