الفرق بين المراجعتين ل"Rails/active record validations"

من موسوعة حسوب
اذهب إلى التنقل اذهب إلى البحث
ط
ط
 
سطر 2: سطر 2:
 
[[تصنيف:Rails]]
 
[[تصنيف:Rails]]
 
[[تصنيف:Rails Models]]
 
[[تصنيف:Rails Models]]
يعلِّمك هذا الدليل كيفية التحقق من حالة الكائنات قبل إرسالها إلى قاعدة البيانات باستعمال ميزة التحققات من الصحة (validations) التي يوفرها Active Record. بعد قراءة هذا الدليل، ستتعرَّف على:
+
يعلِّمك هذا الدليل كيفية التحقق من حالة الكائنات قبل إرسالها إلى قاعدة البيانات باستعمال ميزة التحققات من الصحة (validations) التي يوفرها [[Rails/active record|Active Record]]. بعد قراءة هذا الدليل، ستتعرَّف على:
* كيفية استعمال مساعدي التحقق من الصحة (validation helpers) للسجل الفعَّال المدمجين.
+
* كيفية استعمال مساعدي التحقق من الصحة (validation helpers) لـ [[Rails/active record|Active Record]] المدمجين.
 
* كيفية إنشاء توابع مخصصة للتحقق من الصحة.
 
* كيفية إنشاء توابع مخصصة للتحقق من الصحة.
 
* كيفية العمل مع رسالة الخطأ المولدة عبر عملية التحقق.
 
* كيفية العمل مع رسالة الخطأ المولدة عبر عملية التحقق.
سطر 29: سطر 29:
  
 
=== متى يحدث التحقق؟ ===
 
=== متى يحدث التحقق؟ ===
هناك نوعان من كائنات [[Rails/active record basics|Active Record]]: الكائنات التي تتفاعل مع سجل من قاعدة بياناتك، والكائنات التي لا تتفاعل. عند إنشائك لكائن جديد، مثلًا باستخدام التابع <code>new</code>، لن يتم حفظ هذا الكائن في قاعدة البيانات بعد. بعد استدعائك للتابع <code>save</code> على هذا الكائن، سيتم حفظه في جدول قاعدة البيانات. يستخدم Active Record تابع النسخة <code>?new_record</code> للتأكّد من وجود السجل في قاعدة البيانات أم لا. لنطلع على نموذج Active Record البسيط التالي:<syntaxhighlight lang="rails">
+
هناك نوعان من كائنات [[Rails/active record basics|Active Record]]: الكائنات التي تتفاعل مع سجل من قاعدة بياناتك، والكائنات التي لا تتفاعل. عند إنشائك لكائن جديد، مثلًا باستخدام التابع <code>new</code>، لن يتم حفظ هذا الكائن في قاعدة البيانات بعد. بعد استدعائك للتابع <code>save</code> على هذا الكائن، سيتم حفظه في جدول قاعدة البيانات. يستخدم [[Rails/active record|Active Record]] تابع النسخة <code>?new_record</code> للتأكّد من وجود السجل في قاعدة البيانات أم لا. لنطلع على نموذج [[Rails/active record|Active Record]] البسيط التالي:<syntaxhighlight lang="rails">
 
class Person < ApplicationRecord
 
class Person < ApplicationRecord
 
end
 
end
سطر 72: سطر 72:
  
 
=== التابعان <code>?valid</code> و <code>?invalid</code> ===
 
=== التابعان <code>?valid</code> و <code>?invalid</code> ===
قبل حفظ كائن Active Record، يشغّل ريلز التحقُّقات الخاصة بك. في حال أثمرت هذه التحقُّقات أخطاءً، فلن يحفظ ريزل الكائن.
+
قبل حفظ كائن [[Rails/active record|Active Record]]، يشغّل ريلز التحقُّقات الخاصة بك. في حال أثمرت هذه التحقُّقات أخطاءً، فلن يحفظ ريزل الكائن.
  
 
يمكنك أيضًا تشغيل التحقُّقات يدويًا. إذ يقوم التابع <code>?valid</code> بتشغيل التحقق ويعيد القيمة <code>true</code> في حال نجاحه، والقيمة <code>false</code> عدا عن ذلك. كما رأيت أعلاه:<syntaxhighlight lang="rails">
 
يمكنك أيضًا تشغيل التحقُّقات يدويًا. إذ يقوم التابع <code>?valid</code> بتشغيل التحقق ويعيد القيمة <code>true</code> في حال نجاحه، والقيمة <code>false</code> عدا عن ذلك. كما رأيت أعلاه:<syntaxhighlight lang="rails">
سطر 81: سطر 81:
 
Person.create(name: "John Doe").valid? # => true
 
Person.create(name: "John Doe").valid? # => true
 
Person.create(name: nil).valid? # => false
 
Person.create(name: nil).valid? # => false
</syntaxhighlight>بعد قيام كائن Active Record بالتحقُّقات، يمكن الوصول لرسائل الخطأ عن طريق تابع النسخة <code>errors.messages</code>، الذي يعيد مجموعة من الأخطاء. من التعريف، يكون الكائن صحيحًا في حال كانت هذه المجموعة خالية بعد تنفيذ التحقُّق.
+
</syntaxhighlight>بعد قيام كائن [[Rails/active record|Active Record]] بالتحقُّقات، يمكن الوصول لرسائل الخطأ عن طريق تابع النسخة <code>errors.messages</code>، الذي يعيد مجموعة من الأخطاء. من التعريف، يكون الكائن صحيحًا في حال كانت هذه المجموعة خالية بعد تنفيذ التحقُّق.
  
 
تجدر الملاحظة أنَّ الكائن الذي يتم تهيئته باستخدام <code>new</code> لن يعطي أي خطأ حتّى لو كان غير صالح، لأنَّ التحقُّقات تنفّذ تلقائيًا عند حفظ الكائن، كما هو الحال في التابعين <code>create</code> و <code>save</code>.<syntaxhighlight lang="rails">
 
تجدر الملاحظة أنَّ الكائن الذي يتم تهيئته باستخدام <code>new</code> لن يعطي أي خطأ حتّى لو كان غير صالح، لأنَّ التحقُّقات تنفّذ تلقائيًا عند حفظ الكائن، كما هو الحال في التابعين <code>create</code> و <code>save</code>.<syntaxhighlight lang="rails">
سطر 138: سطر 138:
  
 
== مساعدو التحقق ==
 
== مساعدو التحقق ==
يزوّد Active Record مجموعةً معرفةً مسبقًا من المساعدين الذين يمكنك استخدامهم بشكل مباشر في تعريفات الأصناف الخاصة بك. تزوّد هؤلاء المساعدين بقواعد متعارف عليها لإجراء التحقُّقات. في كل مرة يفشل فيها تحقُّق ما، سيتم إضافة رسالة خطأ إلى المجموعة <code>errors</code> الخاصة بالكائن، وتضم هذه الرسالة اسم الحقل الذي فشل التحقُّق منه.
+
يزوّد [[Rails/active record|Active Record]] مجموعةً معرفةً مسبقًا من المساعدين الذين يمكنك استخدامهم بشكل مباشر في تعريفات الأصناف الخاصة بك. تزوّد هؤلاء المساعدين بقواعد متعارف عليها لإجراء التحقُّقات. في كل مرة يفشل فيها تحقُّق ما، سيتم إضافة رسالة خطأ إلى المجموعة <code>errors</code> الخاصة بالكائن، وتضم هذه الرسالة اسم الحقل الذي فشل التحقُّق منه.
  
 
يقبل كل مساعد مجموعة من أسماء الحقول، أي يمكنك في سطر تعريف وحيد  إضافة نفس التحقُّق لمجموعة من الحقول.
 
يقبل كل مساعد مجموعة من أسماء الحقول، أي يمكنك في سطر تعريف وحيد  إضافة نفس التحقُّق لمجموعة من الحقول.
سطر 395: سطر 395:
  
 
== خيارات عملية التحقق الشائعة ==
 
== خيارات عملية التحقق الشائعة ==
إليك مجموعة من خيارات التحقُّق الشائعة المستخدمة في Active Record:
+
إليك مجموعة من خيارات التحقُّق الشائعة المستخدمة في [[Rails/active record|Active Record]]:
  
 
=== الخيار <code>allow_nil:</code> ===
 
=== الخيار <code>allow_nil:</code> ===
سطر 416: سطر 416:
  
 
=== الخيار <code>message:</code> ===
 
=== الخيار <code>message:</code> ===
كما رأيت مسبقًا، يمكّنك الخيار <code>message:</code> من تحديد رسالة خطأ يراد إضافتها لمجموعة الأخطاء <code>errors</code> عند فشل عملية التحقُّق. عند عدم تحديد هذا الخيار، يرسل Active Record رسالة الخطأ الافتراضية من أجل كل مساعد تحقُّق. يقبل الخيار <code>message:</code> إما [[Ruby/String|سلسلة نصية]] أو كائنًا من النوع <code>[[Ruby/Proc|Proc]]</code>.
+
كما رأيت مسبقًا، يمكّنك الخيار <code>message:</code> من تحديد رسالة خطأ يراد إضافتها لمجموعة الأخطاء <code>errors</code> عند فشل عملية التحقُّق. عند عدم تحديد هذا الخيار، يرسل [[Rails/active record|Active Record]] رسالة الخطأ الافتراضية من أجل كل مساعد تحقُّق. يقبل الخيار <code>message:</code> إما [[Ruby/String|سلسلة نصية]] أو كائنًا من النوع <code>[[Ruby/Proc|Proc]]</code>.
  
 
يمكن للسلسلة النصية الممررة للخيار <code>message:</code> أن تحوي كل من <code>{value}%</code> أو <code>{attribute}%</code> أو <code>{model}%</code> التي يتم استبدالها دينياميكيًا عند فشل التحقُّق. يتم تنفيذ هذا الاستبدال باستخدام الجوهرة [[Rails/i18n|I18n]].
 
يمكن للسلسلة النصية الممررة للخيار <code>message:</code> أن تحوي كل من <code>{value}%</code> أو <code>{attribute}%</code> أو <code>{model}%</code> التي يتم استبدالها دينياميكيًا عند فشل التحقُّق. يتم تنفيذ هذا الاستبدال باستخدام الجوهرة [[Rails/i18n|I18n]].
سطر 492: سطر 492:
  
 
=== استخدام الكائن <code>Proc</code> مع الخيارين <code>if:</code> و <code>unless:</code> ===
 
=== استخدام الكائن <code>Proc</code> مع الخيارين <code>if:</code> و <code>unless:</code> ===
أخيرًا، يمكنك ربط الخيارين <code>if:</code> و <code>unless:</code> مع كائن من النوع <code>[[Ruby/Proc|Proc]]</code> يتم مناداته. باستخدام الكائن Proc، يمكنك التحقُّق شرطيًا ضمن سطر من التعليمات بدلًأ من تابع منفصل. هذا الخيار هو مفضّل لمحبّي الأسطر الواحدة.<syntaxhighlight lang="rails">
+
أخيرًا، يمكنك ربط الخيارين <code>if:</code> و <code>unless:</code> مع كائن من النوع <code>[[Ruby/Proc|Proc]]</code> يتم مناداته. باستخدام الكائن <code>[[Ruby/Proc|Proc]]</code>، يمكنك التحقُّق شرطيًا ضمن سطر من التعليمات بدلًأ من تابع منفصل. هذا الخيار هو مفضّل لمحبّي الأسطر الواحدة.<syntaxhighlight lang="rails">
 
class Account < ApplicationRecord
 
class Account < ApplicationRecord
 
   validates :password, confirmation: true,
 
   validates :password, confirmation: true,
سطر 518: سطر 518:
 
</syntaxhighlight>يتم تنفيذ هذا التحقُّق عندما تتحقق جميع الشروط في <code>if:</code>، ولا تتحقق أي من الشروط في <code>unless:</code>.
 
</syntaxhighlight>يتم تنفيذ هذا التحقُّق عندما تتحقق جميع الشروط في <code>if:</code>، ولا تتحقق أي من الشروط في <code>unless:</code>.
  
== تنفيذ تحققات مخصّصة ==
+
== تنفيذ تحققات مخصصة ==
 
عند عدم كفاية مساعدات التحقُّق المضمنة، يمكنك كتابة تحقُّقات أو توابع تحقُّق خاصة بك بناءً على حاجتك.
 
عند عدم كفاية مساعدات التحقُّق المضمنة، يمكنك كتابة تحقُّقات أو توابع تحقُّق خاصة بك بناءً على حاجتك.
  

المراجعة الحالية بتاريخ 09:23، 24 مارس 2019

يعلِّمك هذا الدليل كيفية التحقق من حالة الكائنات قبل إرسالها إلى قاعدة البيانات باستعمال ميزة التحققات من الصحة (validations) التي يوفرها Active Record. بعد قراءة هذا الدليل، ستتعرَّف على:

  • كيفية استعمال مساعدي التحقق من الصحة (validation helpers) لـ Active Record المدمجين.
  • كيفية إنشاء توابع مخصصة للتحقق من الصحة.
  • كيفية العمل مع رسالة الخطأ المولدة عبر عملية التحقق.

نظرة عامة على عمليات التحقق

الشيفرة التالية تظهر مثالًا عن تحقق بسيط جدًا:

class Person < ApplicationRecord
  validates :name, presence: true
end
 
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false

كما يمكنك أن ترى، تمكنّنا التحقُّقات من معرفة أن الكائن Person ليس صحيحًا بدون اسم؛ مما يعني أن الكائن Person الثاني لن يتم حفظه في قاعدة البيانات.

قبل أن ندخل في التفاصيل، لنتحدث عن أهمية التحقُّقات في تطبيقك.

لمَ نستخدم التحقُّقات؟

تستخدم التحقُّقات للتأكّد من أنّ البيانات التي يتم حفظها في قاعدة البيانات هي صحيحة حتمًا. على سبيل المثال، قد يكون من الضروري على تطبيقك التأكّد من أنّ كل مستخدم يزوّدك ببريده الالكتروني وعنوانه الخاص. التحقُّقات على مستوى النماذج هي الطريقة الأمثل للتأكد من أنّ البيانات الصحيحة تمامًا يتم حفظها في قاعدة البيانات. تعتبر التحقُّقات مستقلة عن قاعدة البيانات، ولا يمكن تجاوزها من المستخدم النهائي، وتتمتّع بأريحية بالصيانة والاختبار. يجعل ريلز التحقُّقات سهلة الاستخدام، ويزوّد بمساعدات مدمجة من أجل الحاجات المختلفة، ويمكّنك من كتابة توابع التحقُّق الخاصة بك أيضًا.

هناك مجموعة من الطرق للتأكّد من البيانات قبل حفظها في قاعدة البيانات، بما فيها القيود الأصلية لقاعدة البيانات، وتحقُّقات من طرف المستخدم وتحقُّقات من طرف المتحكّم. إليك شرح قصير عن إيجابيات وسلبيّات كلّ منها:

  • إن قيود قاعدة البيانات أو الإجراءات المضمَّنة (Stored Procedures) تجعل هيكلية التحقُّق معتمدة تمامًا على قاعدة البيانات، وتصعّب من عمليّة الاختبار والصيانة. لكن في حال كانت قاعدة البيانات الخاصة بك مستخدمة من قبل تطبيقات أخرى، فقد يكون من الضروري استخدام قيود قاعدة البيانات. علاوةً على ذلك، تستطيع قيود قاعدة البيانات معالجة بعض الحالات بشكل آمن وسلس (كالحقول الفريدة في الجداول كثيرة البيانات)، والتي من الصعب تنفيذها بطرق أخرى.
  • يمكن للتحقُّقات من طرف المستخدم (Client-side validations) أن تكون مفيدة، لكن لا يمكن الاعتماد عليها في حال تم تطبيقها بمفردها فقط. في حال تم تطبيقها باستخدام JavaScript، يستطيع المستخدم تجاوزها في حال قام بإلغاء JavaScript في المستعرض الخاص به. لكن في حال تم دمجها مع تقنيات أخرى، يمكن للتحقُّقات من طرف المستخدم أن تكون طريقة مريحة وسهلة لتزويد المستخدم بتغذية راجعة آنية أثناء استخدامه لموقعك.
  • يمكن للتحقُّقات التي على مستوى المتحكّم (Controller-level validations) أن تكون مشوّقة للاستخدام، لكن تصبح بعض الأوقات غير عملية وصعبة للاختبار والصيانة. عند الإمكانية، يجب على متحكّماتك أن تبقى مجرّدة ومختصرة، ممّا يجعل العمل على تطبيقك أمرًا سهلًا مع مرور الوقت.

يمكن استخدام هذه الطرق في حالات مخصصة. برأي فريق عمل ريلز، طريقة التحقُّقات في مستوى النماذج (model-level validations) هي الطريقة المثلى في معظم الحالات.

متى يحدث التحقق؟

هناك نوعان من كائنات Active Record: الكائنات التي تتفاعل مع سجل من قاعدة بياناتك، والكائنات التي لا تتفاعل. عند إنشائك لكائن جديد، مثلًا باستخدام التابع new، لن يتم حفظ هذا الكائن في قاعدة البيانات بعد. بعد استدعائك للتابع save على هذا الكائن، سيتم حفظه في جدول قاعدة البيانات. يستخدم Active Record تابع النسخة ?new_record للتأكّد من وجود السجل في قاعدة البيانات أم لا. لنطلع على نموذج Active Record البسيط التالي:

class Person < ApplicationRecord
end

يمكننا أن نرى كيف يعمل عن طريق الاطلاع على خرج الأمر rails console:

$ bin/rails console
>> p = Person.new(name: "John Doe")
=> #<Person id: nil, name: "John Doe", created_at: nil, updated_at: nil>
>> p.new_record?
=> true
>> p.save
=> true
>> p.new_record?
=> false

إنّ إنشاء وحفظ سجل جديد سيرسل العملية SQL INSERT لقاعدة البيانات. وإن تعديل سجل موجود مسبقًا سيرسل العملية SQL UPDATE بدلًا من ذلك. تُشغَّل التحقُّقات عادةً قبل إرسال هذه الأوامر إلى قاعدة البيانات. في حال فشل أحد التحقُّقات، سيتم تعيين الكائن ككائن غير صحيح ولن يتم إرسال الاستعلام INSERT أو UPDATE. يحدّ هذا من حفظ كائنات غير صحيحة في قاعدة البيانات. يمكنك تحديد فيما إذا أردت تنفيذ تحقُّقات معينة عند إنشاء كائن أو تعديله أو حفظه.

تحذير: هناك طرائق عدَّة لتغيير حالة كائن ما في قاعدة البيانات. تشغّل بعض التوابع عمليات التحقق، والبعض الآخر لا يفعل ذلك. ممّا يعني أنّه يمكنك حفظ كائن ما في قاعدة البيانات في حالة غير صحيحة إن لم تكن على دراية.

تشغّل التوابع التالية عمليات التحقق، وتحفظ الكائن في قاعدة البيانات فقط عند كونه صحيحًا:

  • create
  • create!‎
  • save
  • save!‎
  • update
  • update!‎

ترمي التوابع ذات الإصدار المُغيِّر (bang version مثل التابع !save) استثناءات عند عدم صحة الكائنات. بينما لا تفعل ذلك التوابع ذات الإصدار غير المُغيِّر (non-bang versions) مثل التابعان save و update اللذان يعيدان القيمة false، والتابع create يعيد الكائن نفسه.

تجاوز التحقُّقات

تتجاوز التوابع التالية عمليات التحقق، وتحفظ الكائن في قاعدة البيانات بغض النظر عن صحته. بناءً على ذلك، يجب استخدام هذه التوابع بحذر:

  • decrement!‎
  • decrement_counter
  • increment!‎
  • increment_counter
  • toggle!‎
  • touch
  • update_all
  • update_attribute
  • update_column
  • update_columns
  • update_counters

تجب الملاحظة أنّ للتابع save إمكانية تجاوز التحقُّقات أيضًا، عن طريق تمرير القيمة false للوسيط validate. بالمثل، يجب استخدام هذه الطريقة بحذر:

  • save(validate: false)‎

التابعان ?valid و ?invalid

قبل حفظ كائن Active Record، يشغّل ريلز التحقُّقات الخاصة بك. في حال أثمرت هذه التحقُّقات أخطاءً، فلن يحفظ ريزل الكائن.

يمكنك أيضًا تشغيل التحقُّقات يدويًا. إذ يقوم التابع ?valid بتشغيل التحقق ويعيد القيمة true في حال نجاحه، والقيمة false عدا عن ذلك. كما رأيت أعلاه:

class Person < ApplicationRecord
  validates :name, presence: true
end
 
Person.create(name: "John Doe").valid? # => true
Person.create(name: nil).valid? # => false

بعد قيام كائن Active Record بالتحقُّقات، يمكن الوصول لرسائل الخطأ عن طريق تابع النسخة errors.messages، الذي يعيد مجموعة من الأخطاء. من التعريف، يكون الكائن صحيحًا في حال كانت هذه المجموعة خالية بعد تنفيذ التحقُّق. تجدر الملاحظة أنَّ الكائن الذي يتم تهيئته باستخدام new لن يعطي أي خطأ حتّى لو كان غير صالح، لأنَّ التحقُّقات تنفّذ تلقائيًا عند حفظ الكائن، كما هو الحال في التابعين create و save.

class Person < ApplicationRecord
  validates :name, presence: true
end
 
>> p = Person.new
# => #<Person id: nil, name: nil>
>> p.errors.messages
# => {}
 
>> p.valid?
# => false
>> p.errors.messages
# => {name:["can't be blank"]}
 
>> p = Person.create
# => #<Person id: nil, name: nil>
>> p.errors.messages
# => {name:["can't be blank"]}
 
>> p.save
# => false
 
>> p.save!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank
 
>> Person.create!
# => ActiveRecord::RecordInvalid: Validation failed: Name can't be blank

التابع ?invalid هو ببساطة عكس التابع ?valid، إذ ينفّذ التحقُّق، ويعيد القيمة true في حال وجود أخطاء في الكائن، و false فيما عدا ذلك.

التابع []errors

للتأكد من صحة حقل ما في كائن، يمكنك استخدام التابع [errors[:attribute، حيث attribute: هو الحقل المطلوب التحقق منه. يعيد هذا التابع مصفوفةً من الأخطاء من أجل الحقل المطلوب، وفي حال عدم وجود أخطاء فيتم إعادة مصفوفة فارغة.

يفيد التابع هذا فقط بعد تنفيذ التحقُّقات، لأنّه فقط يقوم بقراءة مصفوفة الأخطاء ولا ينفّذ التحقُّقات بحد ذاتها. ويعتبر مختلفًا عن التابع ActiveRecord::Base.invalid الموضح أعلاه لأنّه لا يعبّر عن صحّة كائن كامل، بل يحتوي فقط على أخطاء الحقل المطلوب من الكائن.

class Person < ApplicationRecord
  validates :name, presence: true
end
 
>> Person.new.errors[:name].any? # => false
>> Person.create.errors[:name].any? # => true

سنقوم بتغطية أخطاء التحقُّقات بشكل كامل في قسم العمل مع أخطاء التحقُّقات.

الخاصية errors.details

للتأكد من التحقُّقات التي فشلت على حقل ما، يمكنك استخدام [errors.details[:attribute. يعيد هذا التابع مصفوفة من القيم hash مع المفتاح error: لجلب رمز التحقُّق:

class Person < ApplicationRecord
  validates :name, presence: true
end
 
>> person = Person.new
>> person.valid?
>> person.errors.details[:name] # => [{error: :blank}]

إن استخدام details مع التحقُّقات اليدوية مغطّى بشكل كامل في قسم العمل مع أخطاء التحقُّقات.

مساعدو التحقق

يزوّد Active Record مجموعةً معرفةً مسبقًا من المساعدين الذين يمكنك استخدامهم بشكل مباشر في تعريفات الأصناف الخاصة بك. تزوّد هؤلاء المساعدين بقواعد متعارف عليها لإجراء التحقُّقات. في كل مرة يفشل فيها تحقُّق ما، سيتم إضافة رسالة خطأ إلى المجموعة errors الخاصة بالكائن، وتضم هذه الرسالة اسم الحقل الذي فشل التحقُّق منه.

يقبل كل مساعد مجموعة من أسماء الحقول، أي يمكنك في سطر تعريف وحيد إضافة نفس التحقُّق لمجموعة من الحقول.

جميعهم يقبلون الخيارين on: و message:، اللذَين يحددان متى يتم تشغيل التحقُّقات وما الرسالة الواجب إضافتها في حال فشل التحقُّق. يقبل الخيار on: إحدى القيمتين create: أو update:. هناك رسائل افتراضية لكل نوع من أنواع التحقُّقات، والتي يتم استخدامها عند عدم تحديد رسالة بشكل يدوي في الخيار message:. لنطلّع على كل مساعد من هؤلاء المساعدين.

المساعد acceptance

يأكّد هذا المساعد أن صندوق الاختيار (checkbox) تم تحديده على واجهة المستخدم عند إرسال النموذج. يستخدم هذا المساعد بشكل تقليدي عند حاجة المستخدم للموافقة على شروط استخدام التطبيق مثلَا، أو للتأكد من قراءة نص معين، أو أي شيء مماثل.

class Person < ApplicationRecord
  validates :terms_of_service, acceptance: true
end

يُنفّذ هذا التحديد فقط عندما يكون terms_of_service ليس nil. إن رسالة الخطأ الافتراضية لهذا المساعد هي "must be accepted". يمكنك تمرير رسالة يدوية عن طريق الخيار message:.

class Person < ApplicationRecord
  validates :terms_of_service, acceptance: { message: 'must be abided' }
end

يمكن أيضًا تمرير الخيار accept: الذي يحدد القيم التي يجب اعتبارها خيارًا مقبولًا. القيمة الافتراضية لهذا الخيار هي [1,true] ويمكن تغييرها بسهولة:

class Person < ApplicationRecord
  validates :terms_of_service, acceptance: { accept: 'yes' }
  validates :eula, acceptance: { accept: ['TRUE', 'accepted'] }
end

هذا التحقُّق مخصَّص لتطبيقات الويب وليس من الضروري تخزين هذا القبول في أي مكان في قاعدة البيانات. في حال لم تكن تملك هذا الحقل في قاعدة البيانات، يقوم المساعد بإنشائه لك بشكل افتراضي. في حال عدم وجود الحقل في قاعدة البيانات، يجب ضبط الخيار accept أو أن يحوي القيمة true، وإلا لن يتم تشغيل التحقُّق.

المساعد validates_associated

يمكنك استخدام هذا المساعد في حال كان نموذجك يملك ارتباطات مع نماذج أخرى تحتاج للتحقُّق أيضًا. عند محاولتك حفظ السجل، سيتم استدعاء التابع ?valid على كل كائن من الكائنات المرتبطة أيضًا.

class Library < ApplicationRecord
  has_many :books
  validates_associated :books
end

سيعمل هذا التحقُّق على جميع الأنواع المرتبطة.

تحذير: لا تستخدم المساعد validates_associated على طرفي الارتباط، إذ سيؤدي ذلك إلى استدعاء أحدهما للآخر مما يؤدي إلى الدخول في حلقة لا نهائية.

إن رسالة الخطأ الافتراضية لهذا التحقُّق هي "is invalid". تجب الملاحظة أن لكل كائن من الكائنات المرتبطة مجموعة الأخطاء الخاصة به، ولن يتم حفظ مجموعة الأخطاء للكائنات الأبناء في الكائن المستدعي.

المساعد confirmation

يجب أن تستخدم هذا المساعد عند وجود حقلين نصيَّين يجب على كل منهما أن يرسلا نفس القيمة تمامًا. مثلًا، قد تحتاج إلى تحقُّق بريد الكتروني أو كلمة مرور. ينشئ هذا التحقُّق حقلًا افتراضيًا ويسمّيه بنفس اسم الحقل الأساسي، يليه العبارة "confirmation_".

class Person < ApplicationRecord
  validates :email, confirmation: true
end

ويجب أن يبدو نموذج العرض الخاص بك كالتالي:

<%= text_field :person, :email %>
<%= text_field :person, :email_confirmation %>

ينفّذ هذا التحقُّق فقط عند كون email_confirmation ليس nil. لطلب التحقُّق بشكل قسري، لا تنسَ إضافة التحقُّق presence للخاصية confirmation (سنطلع على التحقُّق presence لاحقًا في هذا التوثيق):

class Person < ApplicationRecord
  validates :email, confirmation: true
  validates :email_confirmation, presence: true
end

يمكنك أيضًا تمرير الخيار case_sensitive: لتحديد فيما إذا كان القيد confirmation  حساسًا لحالة الأحرف أم لا. القيمة الافتراضية لهذا الخيار هي true.

class Person < ApplicationRecord
  validates :email, confirmation: { case_sensitive: false }
end

رسالة الخطأ الافتراضية لهذا المساعد هي "doesn't match confirmation".

المساعد exclusion

يتأكّد هذا المساعد من أن قيم الحقل المعطى ليست موجودة ضمن المجموعة المحددة. يمكن أن تكون هذه المجموعة أيًّا من الكائنات القابلة للتعداد (enumerable object).

class Account < ApplicationRecord
  validates :subdomain, exclusion: { in: %w(www us ca jp),
    message: "%{value} is reserved." }
end

يملك المساعد exclusion الخيار in: الذي يقبل مجموعة من القيم التي لن يتم قبولها لخاصيات المتحقق منها. يمكن كتابة in: أيضًا بالشكل within:. يستخدم هذا المثال الخيار message: ليظهر كيف يمكنك دمج قيمة الحقل في رسالة التحقُّق. لعرض كافة خيارات الرسائل اليدوية يمكنك الاطلاع على قسم الرسائل في الأسفل.

رسالة الخطأ الافتراضية لهذا المساعد هي "is reserved".

المساعد format

يؤكد هذا المساعد أن قيمة الحقل المعطى توافق التعبير النمطي المحدد بالخيار with: كالتالي:

class Product < ApplicationRecord
  validates :legacy_code, format: { with: /\A[a-zA-Z]+\z/,
    message: "only allows letters" }
end

بالمثل، يمكنك تحديد أن قيمة الحقل المعطى لا توافق التعبير النمطي المحدد، وذلك بالخيار without:.

رسالة الخطأ الافتراضية لهذا المساعد هي "is invalid".

المساعد inclusion

يتأكد هذا المساعد من أن قيم الحقل المعطى موجودة ضمن المجموعة المحددة:

class Coffee < ApplicationRecord
  validates :size, inclusion: { in: %w(small medium large),
    message: "%{value} is not a valid size" }
end

يملك المساعد inclusion الخيار in: الذي يستقبل مجموعة من القيم المراد قبولها فقط. يمكن كتابة in: أيضًا بالشكل within:. يستخدم هذا المثال الخيار message: ليظهر كيف يمكنك دمج قيمة الحقل في رسالة التحقُّق. لعرض كافة خيارات الرسائل اليدوية، يمكنك الاطلاع على قسم الرسائل في الأسفل.

رسالة الخطأ الافتراضية لهذا المساعد هي "is not included in the list".

المساعد length

يتأكد هذا المساعد من طول قيم الحقل المعطى. يزوّد بمجموعة من الخيارات التي تمكّنك من تحديد قيود الطول بأشكال مختلفة:

class Person < ApplicationRecord
  validates :name, length: { minimum: 2 }
  validates :bio, length: { maximum: 500 }
  validates :password, length: { in: 6..20 }
  validates :registration_number, length: { is: 6 }
end

الخيارات الممكنة لقيود الطول هي:

  • minimum:: لا يمكن للحقل أن يحوي عدد محارف أقل من الطول المعطى.
  • maximum:: لا يمكن للحقل أن يحوي عدد محارف أكثر من الطول المعطى.
  • in: (أو within:): يمكن للحقل أن يحوي عدد محارف بطول موجود ضمن المجال المعطى.
  • is:: يجب على الحقل أن يحوي عدد محارف يساوي الطول المعطى.

تعتمد رسالة الخطأ الافتراضية على نوع تحقُّق الطول المستخدم. يمكنك تعديل هذه الرسائل عن طريق الخيارات wrong_length:، و too_long:، و too_short:، واستخدام {count}% لاستيفاء طول المحارف المستخدم. يمكنك استخدام الخيار message: لتحديد رسالة خطأ.

class Person < ApplicationRecord
  validates :bio, length: { maximum: 1000,
    too_long: "%{count} characters is the maximum allowed" }
end

تجب الملاحظة أن رسائل الخطأ الافتراضية هي في صيغة الجمع مثل:

"is too short (minimum is %{count} characters)"

لهذا السبب، عند كون minimum: تساوي 1، يجب تزويد رسالة معدلة أو استخدام الخيار presence: true بدلًا من ذلك. عندما يملك الخيار in: أو within: حدًا أصغريًا مساويًا للواحد، يجب عليك تزويد رسالة معدلة أو استخدام الخيار presence قبل length.

المساعد numericality

يتأكد هذا المساعد من أن الحقل يحتوي فقط على قيم رقمية. افتراضيًا، يطابق هذا المساعد إشارة الرقم الاختيارية، متبوعةً بعدد صحيح أو عشري. لتحديد قبول الأعداد الصحيحة فقط، يمكن تعيين الخيار only_integer: إلى true.

وفي حال ضبط only_integer: إلى القيمة true، فسيتم استخدام التعبير النمطي التالي:

/\A[+-]?\d+\z/

وذلك للتحقُّق من قيمة الخاصية. فيما عدا ذلك، سيحاول تحويل قيمة الحقل إلى عدد باستخدام Float.

class Player < ApplicationRecord
  validates :points, numericality: true
  validates :games_played, numericality: { only_integer: true }
end

إلى جانب only_integer:، يقبل هذا المساعد مجموعة من الخيارات لإضافة قيود على القيم المقبولة:

  • الخيار greater_than:: يحدد أن القيمة يجب أن تكون أكبر تمامًا من القيمة المعطاة. رسالة الخطأ الافتراضية لهذا الخيار هي "must be greater than %{count}‎".
  • الخيار greater_than_or_equal_to:: يحدد أن القيمة يجب أن تكون أكبر أو تساوي القيمة المعطاة. رسالة الخطأ الافتراضية لهذا الخيار هي "must be greater than or equal to %{count}‎".
  • الخيار equal_to:: يحدد أن القيمة يجب أن تكون تساوي القيمة المعطاة. رسالة الخطأ الافتراضية لهذا الخيار هي "must be equal to %{count}‎"
  • الخيار less_than:: يحدد أن القيمة يجب أن تكون أصغر تمامًا من القيمة المعطاة. رسالة الخطأ الافتراضية لهذا الخيار هي "must be less than %{count}‎".
  • الخيار less_than_or_equal_to:: يحدد أن القيمة يجب أن تكون أصغر أو تساوي القيمة المعطاة. رسالة الخطأ الافتراضية لهذا الخيار هي "must be less than or equal to %{count}‎".
  • الخيار other_than:: يحدد أن القيمة يجب أن تكون غير مساوية للقيمة المعطاة. رسالة الخطأ الافتراضية لهذا الخيار هي "must be other than %{count}‎"
  • الخيار odd:: يحدد أن القيمة يجب أن تكون عددًا فرديًّا. رسالة الخطأ الافتراضية لهذا الخيار هي "must be odd".
  • الخيار even:: يحدد أن القيمة يجب أن تكون عددًا زوجيًّا. رسالة الخطأ الافتراضية لهذا الخيار هي "must be even".

ملاحظة: افتراضيًا، لا يسمح المساعد numericality بالقيم nil. يمكنك استخدام الخيار allow_nil: للسماح بها.

الرسالة الافتراضية لهذا المساعد هي "is not a number".

المساعد presence

يتحقق هذا المساعد من أن عدم خلو خاصيات محدَّدة من أية قيمة. يستخدم هذا المساعد التابع ?blank للتأكد من أن القيمة هي nil، أو سلسلة محرفية فارغة، التي هي سلسلة محرفية تحوي فراغًا واحدًا أو أكثر، أو لا تحوي محارف على الإطلاق.

class Person < ApplicationRecord
  validates :name, :login, :email, presence: true
end

إذا أردت أن تكون متأكدًا من وجود ارتباط ما، يجب أن تتحقّق من وجود الكائن المرتبط بنفسه، وليس المفتاح الأجنبي المستخدم للدلالة على الارتباط.

class LineItem < ApplicationRecord
  belongs_to :order
  validates :order, presence: true
end

من أجل تحقُّق السجلات المرتبطة الضروري وجودها، يجب تحديد الخيار inverse_of: من أجل الارتباط:

class Order < ApplicationRecord
  has_many :line_items, inverse_of: :order
end

في حال قمت بالتحقُّق من وجود كائن مرتبط عن طريق العلاقة has_one أو has_many، سيتفقّد من كون الكائن غير محقق للتابع ?blank ولا التابع ?marked_for_destruction. لمَّا كان ?false.blank يعيد القيمة true، فإذا أردت التحقُّق من وجود حقل من النوع Boolean، فيجب عليك القيام بأحد التحققين التاليين:

validates :boolean_field_name, inclusion: { in: [true, false] }
validates :boolean_field_name, exclusion: { in: [nil] }

باستخدام أحد هذين التحقُّقين، يمكنك التأكد من عدم استقبال الحقل المنطقي القيمة nil.

المساعد absence

يؤكد هذا المساعد غياب وعدم وجود قيم خاصية محددة. يستخدم هذا المساعد التابع ?present للتأكد من أن القيمة هي ليست nil، أو سلسلة محرفية فارغة، التي هي سلسلة محرفية تحوي فراغًا واحدًا أو أكثر، أو لا تحوي محارف على الإطلاق.

class Person < ApplicationRecord
  validates :name, :login, :email, absence: true
end

إذا أردت أن تكون متأكدًا من غياب ارتباط ما، يجب أن تتحقّق من غياب الكائن المرتبط نفسه، وليس المفتاح الأجنبي المستخدم للدلالة على الارتباط.

class LineItem < ApplicationRecord
  belongs_to :order
  validates :order, absence: true
end

من أجل تحقُّق السجلات المرتبطة الضروري غيابها، يجب تحديد الخيار inverse_of: من أجل الارتباط:

class Order < ApplicationRecord
  has_many :line_items, inverse_of: :order
end

في حال قمت بالتحقُّق من غياب كائن مرتبط عن طريق علاقة has_one أو has_many، سيتفقّد من أنَّ الكائن غير محقق للتابع ?present ولا التابع ?marked_for_destruction. لما كان ?false.present يعيد القيمة false، فإذا أردت التحقُّق من غياب حقل من النوع المنطقي، فيجب عليك استعمال:

validates :field_name, exclusion: { in: [true, false] }

رسالة الخطأ الافتراضية لهذا المساعد هي "must be blank".

المساعد uniqueness

يتحقق هذا المساعد من أنَّ قيمة الحقل فريدة وغير مكررة قبل حفظ الكائن. لا ينشئ هذا المساعد قيد التميز في قاعدة البيانات، لذلك قد تحدث حالة إضافة سجلّين بقيمتين متساويتين في حقل واحد تريده أن يكون فريدًا، وذلك من قبل اتصالين منفصلين مع قاعدة البيانات. لتفادي هذا، يجب عليك إنشاء فهرس فريد لهذا الحقل في قاعدة البيانات.

class Account < ApplicationRecord
  validates :email, uniqueness: true
end

يجري تنفيذ التحقُّق عن طريق تنفيذ استعلام SQL على جدول النموذج، بحثًا عن سجل موجود يملك ذات القيمة في الحقل المعطى. يمكن استخدام الخيار scope: لتحديد خاصية أو أكثر مستخدمة للتحقق uniqueness:

class Holiday < ApplicationRecord
  validates :name, uniqueness: { scope: :year,
    message: "should happen once per year" }
end

إذا أردت إنشاء قيدٍ على قاعدة البيانات لتفادي تجاوز التحقُّقات uniqueness المستخدمة للخيار scope:، يجب أن تنشئ فهرسًا فريدًا في كلا الحقلين في قاعدة البيانات. اقرأ دليل MySQL للمزيد من المعلومات حول إنشاء فهرس لحقول متعددة أو دليل PostgreSQL لأمثلة حول قيود التميز المستخدمة على نطاق مجموعة من الحقول في آن معًا. هناك أيضًا الخيار case_sensitive: لتحديد فيما إذا كان القيد uniqueness يراعي حالة الأحرف أم لا. القيمة الافتراضية لهذا الخيار هي true.

class Person < ApplicationRecord
  validates :name, uniqueness: { case_sensitive: false }
end

ملاحظة: بعض قواعد البيانات مهيّئة لتنفيذ عمليات البحث غير الحساسة لحالة الأحرف في كلتا الحالتين.

رسالة الخطأ الافتراضية لهذا المساعد هي "has already been taken".

المساعد validates_with

يمرر هذا المساعد السجل الواجب التحقق منه إلى صنف آخر منفصل للقيام بعملية التحقُّق.

class GoodnessValidator < ActiveModel::Validator
  def validate(record)
    if record.first_name == "Evil"
      record.errors[:base] << "This person is evil"
    end
  end
end
 
class Person < ApplicationRecord
  validates_with GoodnessValidator
end

ملاحظة: الأخطاء المضافة إلى [record.errors[:base تتعلق بحالة السجل ككل، وليس بحقل معين.

يقبل المساعد validates_with صنفًا، أو مجموعة من الأصناف المستخدمة للتحقُّق. لا يوجد رسالة خطأ افتراضية لهذا المساعد. يمكنك تحديد رسائل الخطأ يدويًا عن طريق الصنف validator.

لكتابة تابع التحقُّق، يجب أن تستقبل وسيطًا يمثل السجل المطلوب تحقُّقه.

كما هو الحال في كل عمليات التحقُّق، يقبل هذا المساعد الخيارات if: و unless: و on:. إذا مرّرت خيارات إضافية، فسيتم تمريرها إلى الصنف validator ضمن الوسيط options:

class GoodnessValidator < ActiveModel::Validator
  def validate(record)
    if options[:fields].any?{|field| record.send(field) == "Evil" }
      record.errors[:base] << "This person is evil"
    end
  end
end
 
class Person < ApplicationRecord
  validates_with GoodnessValidator, fields: [:first_name, :last_name]
end

من الجدير بالملاحظة أنّه يتم تهيئة الصنف validator مرّة واحدة فقط ضمن دورة حياة التطبيق ككل، وليس في كل مرة يتم تنفيذ تحقُّق معيّن؛ لذلك كن حذرًا أثناء التعامل مع متغيرات النسخة داخله. إذا كان صنف التحقُّق validator الخاص بك معقدًا لدرجة حاجته لمتغيّرات نسخة، فيمكنك استخدام كائنات روبي البسيطة بدلًا من ذلك:

class Person < ApplicationRecord
  validate do |person|
    GoodnessValidator.new(person).validate
  end
end
 
class GoodnessValidator
  def initialize(person)
    @person = person
  end
 
  def validate
    if some_complex_condition_involving_ivars_and_private_methods?
      @person.errors[:base] << "This person is evil"
    end
  end
 
  # ...
end

المساعد validates_each

يتحقَّق هذا المساعد من مجموعة من الخاصيات مقابل كتلة من التعليمات البرمجية، إذ لا يملك تابع تحقُّق معرف مسبقًا. يجب عليك إنشاء هذا التابع باستخدام كتلة، وكل خاصية ممرّرة للمساعد validates_each سيتم اختبارها في هذه الكتلة. في المثال التالي، لا نريد من الأسماء الأولى والكنيات أن تبدأ بأحرف صغيرة.

class Person < ApplicationRecord
  validates_each :name, :surname do |record, attr, value|
    record.errors.add(attr, 'must start with upper case') if value =~ /\A[[:lower:]]/
  end
end

تستقبل الكتلة السجل، واسم الخاصية وقيمتها. يمكنك القيام بأي عملية داخل الكتلة للتأكد من صحة البيانات المستقبلة. في حال فشل التحقُّق، يجب عليك إضافة رسالة خطأ إلى النموذج، موضّحًا سبب فشل عملية التحقق من صحّة البيانات.

خيارات عملية التحقق الشائعة

إليك مجموعة من خيارات التحقُّق الشائعة المستخدمة في Active Record:

الخيار allow_nil:

يتجاوز الخيار allow_nil: التحقُّق عندما تكون القيمة الواجب التحقق منها هي nil.

class Coffee < ApplicationRecord
  validates :size, inclusion: { in: %w(small medium large),
    message: "%{value} is not a valid size" }, allow_nil: true
end

لقراءة كل الخيارات حول الوسيط message، اطّلع على التوثيق الخاص به.

الخيار allow_blank:

يشابه هذا الخيار الخيار allow_nil:، إذ يتجاوز التحقُّق في حال كانت قيمة الخاصية هي ?blank، مثل القيمة nil أو سلسلة نصية فارغة.

class Topic < ApplicationRecord
  validates :title, length: { is: 5 }, allow_blank: true
end
 
Topic.create(title: "").valid?  # => true
Topic.create(title: nil).valid? # => true

الخيار message:

كما رأيت مسبقًا، يمكّنك الخيار message: من تحديد رسالة خطأ يراد إضافتها لمجموعة الأخطاء errors عند فشل عملية التحقُّق. عند عدم تحديد هذا الخيار، يرسل Active Record رسالة الخطأ الافتراضية من أجل كل مساعد تحقُّق. يقبل الخيار message: إما سلسلة نصية أو كائنًا من النوع Proc.

يمكن للسلسلة النصية الممررة للخيار message: أن تحوي كل من {value}% أو {attribute}% أو {model}% التي يتم استبدالها دينياميكيًا عند فشل التحقُّق. يتم تنفيذ هذا الاستبدال باستخدام الجوهرة I18n.

أمَّا في حال استعمال الكائن Proc مع الخيار message:، يتم تمرير وسيطين هما: الكائن المراد التحقق منه، ومجموعة بأزواج المفاتيح والقيم التالية: model: و attribute: و value:.

class Person < ApplicationRecord
  # رسالة ثابتة
  validates :name, presence: { message: "must be given please" }
 
  # %{value} رسالة مع قيمة ديناميكية.  يتم استبدال
  # متاحتان أيضًا %{model} و %{attribute} بالقيمة الحقيقية للحقل. إن
  validates :age, numericality: { message: "%{value} seems wrong" }
 
  # Proc
  validates :username,
    uniqueness: {
      # object = الكائن المراد التحقق منه
      # data = { model: "Person", attribute: "Username", value: <username> }
      message: ->(object, data) do
        "Hey #{object.name}!, #{data[:value]} is taken already! Try again #{Time.zone.tomorrow}"
      end
    }
end

الخيار on:

يمكّنك هذا الخيار من تحديد مكان التحقُّق. السلوك الافتراضي لجميع مساعدات التحقُّق المضمنة هو تشغيل التحقُّق عند الحفظ (أي عند إنشاء أو تحديث السجلات). إذا أردت تغيير هذا السلوك، فيمكنك تحديد الخيار on: على create: لتشغيل التحقُّق فقط عند إنشاء كائن جديد، أو على update: لتشغيل التحقُّق فقط عند تعديل كائن موجود مسبقًا.

class Person < ApplicationRecord
  # سيكون بالإمكان تحديث  عنوان البريد الإلكتروني مع قيمة مكررة
  validates :email, uniqueness: true, on: :create
 
  # سيكون بالإمكان إنشاء سجل  مع قيمة غير عددية للعمر
  validates :age, numericality: true, on: :update
 
  # القيمة الافتراضية هي التحقق عند الإنشاء والتحديث
  validates :name, presence: true
end

يمكنك أيضًا استخدام الخيار on: لتعريف سياق مخصّص. يجب تشغيل السياق المخصص بشكل ظاهري عن طريق تمرير اسم السياق إلى التوابع ?valid أو ?invalid أو save.

class Person < ApplicationRecord
  validates :email, uniqueness: true, on: :account_setup
  validates :age, numericality: true, on: :account_setup
end
 
person = Person.new

يتم تنفيذ (person.valid?(:account_setup عند التحقُّقين دون حفظ السجل. ويقوم التابع (person.sav(context: :account_setup بالتحقُّق من الكائن person في السياق account_setup قبل الحفظ. عند التشغيل بشكل ظاهري، يتم التحقُّق من السجلات عن طريق عمليات التحقق المحددة فقط لسجلات هذا السياق أو التي لا تملك سياقًا مطلقًا.

التحقُّقات الصارمة

يمكنك تحديد فيما إذا كان تحقُّق ما صارم (strict) أم لا ورفع الاستثناء ActiveModel::StrictValidationFailed عند فشل عملية التحقق.

class Person < ApplicationRecord
  validates :name, presence: { strict: true }
end
 
Person.new.valid?  # => ActiveModel::StrictValidationFailed: Name can't be blank

يمكنك أيضًا تمرير استثناءات مخصّصة للخيار strict:.

class Person < ApplicationRecord
  validates :token, presence: true, uniqueness: true, strict: TokenGenerationException
end
 
Person.new.valid?  # => TokenGenerationException: Token can't be blank

التحققات الشرطية

في بعض الأحيان، قد يكون من المنطقي التحقُّق من سجل ما فقط عند تحقق شرط معيّن. يمكنك إنجاز ذلك عن طريق الخيارين if: و unless:، اللذَين يستقبلان رمزًا، أو كائنًا من النوع Proc، أو مصفوفة. يمكنك استخدام الخيار if: لتحديد متى يجب تنفيذ عملية التحقُّق. وفي حال أردت تحديد متى لا يجب تنفيذ التحقُّق، يمكنك استخدام الخيار unless:.

استخدام رمز مع الخيارين if: و unless:

يمكنك ربط الخيارين if: و unless: مع رمز موافق لاسم التابع المستدعى قبل عملية التحقُّق. هذا الخيار هو الخيار الأكثر استخدامًا.

class Order < ApplicationRecord
  validates :card_number, presence: true, if: :paid_with_card?
 
  def paid_with_card?
    payment_type == "card"
  end
end

استخدام الكائن Proc مع الخيارين if: و unless:

أخيرًا، يمكنك ربط الخيارين if: و unless: مع كائن من النوع Proc يتم مناداته. باستخدام الكائن Proc، يمكنك التحقُّق شرطيًا ضمن سطر من التعليمات بدلًأ من تابع منفصل. هذا الخيار هو مفضّل لمحبّي الأسطر الواحدة.

class Account < ApplicationRecord
  validates :password, confirmation: true,
    unless: Proc.new { |a| a.password.blank? }
end

تجميع التحقُّقات الشرطية

قد يكون من المفيد أحيانًا أن تستخدم مجموعة من التحقُّقات شرطًا واحدًا. يمكن تنفيذ ذلك عن باستخدام with_options.

class User < ApplicationRecord
  with_options if: :is_admin? do |admin|
    admin.validates :password, length: { minimum: 10 }
    admin.validates :email, presence: true
  end
end

جميع التحقُّقات الواقعة داخل الكتلة التي تخص with_options سيمرّر لها الشرط if: :is_admin?‎ تلقائيًّا.

دمج شروط التحقُّق

وفي الطرف الآخر، عندما تحدد مجموعة من الشروط متى يجب تنفيذ تحقُّق معيّن، يمكن استخدام مصفوفة. أي يمكنك استخدام الخيارين if: و unless: على التحقُّق نفسه.

class Computer < ApplicationRecord
  validates :mouse, presence: true,
                    if: [Proc.new { |c| c.market.retail? }, :desktop?],
                    unless: Proc.new { |c| c.trackpad.present? }
end

يتم تنفيذ هذا التحقُّق عندما تتحقق جميع الشروط في if:، ولا تتحقق أي من الشروط في unless:.

تنفيذ تحققات مخصصة

عند عدم كفاية مساعدات التحقُّق المضمنة، يمكنك كتابة تحقُّقات أو توابع تحقُّق خاصة بك بناءً على حاجتك.

عمليات التحقق المخصصة

عمليات التحقق المخصصة هي أصناف ترث من الصنف الأب ActiveModel::Validator. يجب أن تعرّف هذه الأصناف التابع validate الذي يقبل تمرير السجل المطلوب التحقُّق منه وسيطًا له ثم يجري عملية التحقق عليه. تستدعى علمية التحقق المخصصة باستخدام التابع validates_with.

class MyValidator < ActiveModel::Validator
  def validate(record)
    unless record.name.starts_with? 'X'
      record.errors[:name] << 'Need a name starting with X please!'
    end
  end
end
 
class Person
  include ActiveModel::Validations
  validates_with MyValidator
end

أسهل طريقة لإضافة تحقُّق مخصص للتحقُّق من خاصيات فردية هو عن طريق الصنف المريح ActiveModel::EachValidator. في هذه الحالة، يجب أن يعرِّف صنف التحقُّق التابع validate_each الذي يقبل ثلاث وسائط هي: سجل، وخاصية، وقيمة. تكون هذه الوسائط مرتبطة بالكائن، والخاصية المراد التحقُّق منها، وقيمة الخاصية المُمرَّرة للكائن.

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors[attribute] << (options[:message] || "is not an email")
    end
  end
end
 
class Person < ApplicationRecord
  validates :email, presence: true, email: true
end

كما هو ظاهر في المثال، يمكنك أيضًا دمج التحقُّقات القياسية المُعرَّفة مسبقًا مع تحقُّقات مخصصة.

التوابع المخصصة

يمكنك أيضًا تعريف توابع تتحقق من حالة السجلات وتضيف رسائل خطأ إلى المجموعة errors عند فشل عملية التحقق. يجب عليك بعد ذلك تسجيل هذه التوابع باستخدام تابع الصنف validate، ممرّرًا رموزًا لأسماء هذه التوابع.

يمكنك تمرير أكثر من رمز لكل تابع صنف، مسبوقًا بالتحقُّق المستخدم بنفس ترتيب تسجيله.

يتحقق التابع ?valid من خلو مجموعة الأخطاء (errors collection) من الأخطاء، لتتمكّن توابع التحقُّق المخصصة من إضافة الأخطاء إلى المجموعة عند فشل التحقُّق:

class Invoice < ApplicationRecord
  validate :expiration_date_cannot_be_in_the_past,
    :discount_cannot_be_greater_than_total_value
 
  def expiration_date_cannot_be_in_the_past
    if expiration_date.present? && expiration_date < Date.today
      errors.add(:expiration_date, "can't be in the past")
    end
  end
 
  def discount_cannot_be_greater_than_total_value
    if discount > total_value
      errors.add(:discount, "can't be greater than total value")
    end
  end
end

افتراضيًا، يتم تشغيل التحقُّقات المشابهة عند كل استدعاء للتابع ?valid أو عند حفظ الكائن. لكن من الممكن أيضًا التحكم بوقت تنفيذ هذه التحقُّقات المخصصة عن طريق تحديد الخيار on: مع التابع validate، مستخدمًا إما create: أو update:.

class Invoice < ApplicationRecord
  validate :active_customer, on: :create
 
  def active_customer
    errors.add(:customer_id, "is not active") unless customer.active?
  end
end

العمل مع أخطاء التحققات

إضافةً إلى التابعين ?valid و ?invalid الموضّحين أعلاه، يزوّد ريلز بمجموعة من التوابع المستخدمة للعمل مع مجموعة الأخطاء errors والحصول على معلومات حول صحة الكائنات.

إليك قائمة بأكثر التوابع استخدامًا. الرجاء الاطلاع على توثيق ActiveModel::Errors لقائمة بالتوابع المتاحة.

Errors

يعيد كائنًا من النوع ActiveModel::Errors يحوي جميع الأخطاء. كل مفتاح هو اسم الخاصية، وكل قيمة هي مصفوفة برسائل الخطأ المرتبطة بالخاصية.

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
 
person = Person.new
person.valid? # => false
person.errors.messages
 # => {:name=>["can't be blank", "is too short (minimum is 3 characters)"]}
 
person = Person.new(name: "John Doe")
person.valid? # => true
person.errors.messages # => {}

[]errors

يُستخدَم []errors عندما تريد التحقق من رسائل الخطأ لخاصية محددة. يعيد هذا التابع مصفوفة من رسائل الخطأ الموافقة للخاصية المعطاة. في حال لم يكن هنالك أخطاء للحقل المعطى، فيعيد التابع مصفوفةً فارغةً.

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
 
person = Person.new(name: "John Doe")
person.valid? # => true
person.errors[:name] # => []
 
person = Person.new(name: "JD")
person.valid? # => false
person.errors[:name] # => ["is too short (minimum is 3 characters)"]
 
person = Person.new
person.valid? # => false
person.errors[:name]
 # => ["can't be blank", "is too short (minimum is 3 characters)"]

errors.add

يمكّنك التابع add من إضافة رسائل الخطأ إلى خاصية محددة. يستقبل هذا التابع في وسائطه اسم الخاصية الواجب إضافة رسالة الخطأ عليها، ورسالة الخطأ المطلوبة.

يعيد التابع errors.full_messages (أو مثيله errors.to_a) رسائل الخطأ بنمط سهل القراءة للمستخدم، مع اسم الخاصية السابق لرسالة الخطأ، كما هو ظاهر في المثال التالي.

class Person < ApplicationRecord
  def a_method_used_for_validation_purposes
    errors.add(:name, "cannot contain the characters !@#%*()_-+=")
  end
end
 
person = Person.create(name: "!@#")
 
person.errors[:name]
 # => ["cannot contain the characters !@#%*()_-+="]
 
person.errors.full_messages
 # => ["Name cannot contain the characters !@#%*()_-+="]

مثيل عن التابع errors.add هو استخدام >> لإضافة رسالة إلى المصفوفة errors.messages لخاصية:

class Person < ApplicationRecord
  def a_method_used_for_validation_purposes
    errors.messages[:name] << "cannot contain the characters !@#%*()_-+="
  end
end
 
person = Person.create(name: "!@#")
 
person.errors[:name]
 # => ["cannot contain the characters !@#%*()_-+="]
 
person.errors.to_a
 # => ["Name cannot contain the characters !@#%*()_-+="]

errors.details

يمكنك تحديد نوع التحقُّق لكائن معلومات الخطأ المعاد عن طريق التابع errors.add.

class Person < ApplicationRecord
  def a_method_used_for_validation_purposes
    errors.add(:name, :invalid_characters)
  end
end
 
person = Person.create(name: "!@#")
 
person.errors.details[:name]
# => [{error: :invalid_characters}]

لتحسين كائن معلومات الخطأ بحيث يحوي المحارف غير المسموحة على سبيل المثال، يمكنك تمرير مفاتيح إضافية للتابع errors.add.

class Person < ApplicationRecord
  def a_method_used_for_validation_purposes
    errors.add(:name, :invalid_characters, not_allowed: "!@#%*()_-+=")
  end
end
 
person = Person.create(name: "!@#")
 
person.errors.details[:name]
# => [{error: :invalid_characters, not_allowed: "!@#%*()_-+="}]

جميع التحقُّقات المدمجة في ريلز تملأ كائنات معلومات الخطأ بمجموعات متوافقة مع نوع التحقُّق المستخدم.

[errors[:base

يمكنك إضافة رسائل خطأ متعلقة بحالة الكائن ككل، بدلًا من أن تكون متعلقة بخاصية وحيدة. يمكنك استخدام هذا التابع عند الحاجة للقول أن الكائن ككل هو غير صحيح، بغض النظر عن قيم خاصياته. بما أن [errors[:base هي مصفوفة، يمكنك إضافة سلسلة نصية لها وسيتم استخدامها كرسالة خطأ.

class Person < ApplicationRecord
  def a_method_used_for_validation_purposes
    errors[:base] << "This person is invalid because ..."
  end
end

errors.clear

يُستخدَم التابع clear عندما تود بإرادتك أن تزيل جميع رسائل الخطأ من المجموعة errors. بالطبع، استدعاء التابع clear مع كائن غير صحيح لن يجعله صحيحًا: أي ستكون المجموعة errors فارغة، لكن عند استدعاء التابع ?valid في المرة القادمة أو أي تابع يحاول حفظ الكائن في قاعدة البيانات، فسيتم تنفيذ التحقُّق من جديد. وفي حال فشله، ستمتلئ المجموعة errors من جديد.

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
 
person = Person.new
person.valid? # => false
person.errors[:name]
 # => ["can't be blank", "is too short (minimum is 3 characters)"]
 
person.errors.clear
person.errors.empty? # => true
 
person.save # => false
 
person.errors[:name]
# => ["can't be blank", "is too short (minimum is 3 characters)"]

errors.size

يعيد التابع size عدد رسائل الخطأ للكائن.

class Person < ApplicationRecord
  validates :name, presence: true, length: { minimum: 3 }
end
 
person = Person.new
person.valid? # => false
person.errors.size # => 2
 
person = Person.new(name: "Andrea", email: "andrea@example.com")
person.valid? # => true
person.errors.size # => 0

عرض أخطاء التحقق في الواجهات

متى ما أنشأت نموذجًا وأضفت تحقُّقات له، ففي حال تم إنشاء هذا النموذج (model) عن طريق نموذج ويب (web form)، فقد يكون من الضروري إظهار رسالة الخطأ عند فشل التحقُّق.

لأن كل تطبيق يراعي هذه الحالة بطريقة مختلفة، لا يزود ريلز بأي مساعد واجهة للمساعدة بتوليد هذه الرسائل مباشرةً. لكن نظرًا للعدد الكبير من التوابع المزودة من ريلز للتفاعل مع التحقُّقات بشكل عام، من السهل بناء هذه التوابع يدويًّا. علاوةً على ذلك، عند توليد scaffold (هو جزء من النمط البرمجي MVC حيثُ يُمكنك إختيار جزء مُعين من قاعدة البيانات للعمل عليه)، يقوم ريلز بوضع تعليمات ERB في الملف form.html.erb_ المولّد، والذي يظهر القائمة الكاملة بأخطاء هذا النموذج.

بفرض أنه لدينا نموذجًا تم حفظه في متغير نسخة تسمى article@، يبدو الأمر كذلك:

<% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited this article from being saved:</h2>
 
    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

من ناحية أخرى، في حال كنت تستخدم مساعدات ريلز لإنشاء نماذج الويب، عند فشل تحقُّق على حقل معين، ستولّد هذه المساعدات عنصر <div> إضافي حول الحقل.

<div class="field_with_errors">
 <input id="article_title" name="article[title]" size="30" type="text" value="">
</div>

يمكنك بناءً على ذلك تعديل شكل هذا العنصر كما تشاء. الشكل الافتراضي المضاف من vdg. مثلًا، يقوم بإنشاء قواعد CSS التالية:

.field_with_errors {
  padding: 2px;
  background-color: red;
  display: table;
}

ممّا يعني أن أي حقل يملك أخطاءً سيتم تلوين حدوده باللون الأحمر بسماكة 2 بكسل.

مصادر