أساسيات النموذج الفعال في ريلز
يجب أن يوفر لك هذا الدليل كل ما تحتاج إليه للبدء في استخدام أصناف النموذج (model classes). يسمح النموذج الفعَّال (Active Model) لمساعدي الإجراء Pack
بالتفاعل مع كائنات روبي الصرفة. يساعد النموذج الفعَّال أيضًا على إنشاء قواعد بيانات ORM مخصصة للاستخدام خارج إطار ريلز.
بعد قراءة هذا الدليل، ستتعلم:
- كيف يعمل نموذج السجل الفعَّال.
- كيف تعمل ردود النداء وعمليات التحقق.
- كيف تعمل المُسَلسِلات (serializers).
- كيف يتكامل النموذج الفعال مع إطار تدويل ريلز (i18n).
مقدمة
النموذج الفعَّال هو مكتبة تحتوي على وحدات مختلفة تستخدم في تطوير الأصناف التي تحتاج إلى بعض الميزات الموجودة في السجل الفعَّال. بعض هذه الوحدات مشروحة أدناه.
توابع الخاصيات
تضيف الوحدة ActiveModel::AttributeMethods
البادئات واللاحقة المخصصة إلى توابع صنف. تُستخدَم عن طريق تحديد البادئات واللاحقات وأي توابع ستستخدمهم على الكائن.
class Person
include ActiveModel::AttributeMethods
attribute_method_prefix 'reset_'
attribute_method_suffix '_highest?'
define_attribute_methods 'age'
attr_accessor :age
private
def reset_attribute(attribute)
send("#{attribute}=", 0)
end
def attribute_highest?(attribute)
send(attribute) > 100
end
end
person = Person.new
person.age = 110
person.age_highest? # => true
person.reset_age # => 0
person.age_highest? # => false
ردود النداء
تعطي الوحدة ActiveModel::Callbacks
ردود نداء بنمط السجل الفعَّال. هذا يوفر القدرة على تحديد ردود النداء التي تعمل في الأوقات المناسبة. بعد تحديد ردود النداء، يمكنك تغليفها قبل، وبعد، وحول توابع مخصصة.
class Person
extend ActiveModel::Callbacks
define_model_callbacks :update
before_update :reset_me
def update
run_callbacks(:update) do
# على كائن update يُستدعَى هذا التابع عند استدعاء
end
end
def reset_me
# على كائن update يُستدعى هذا التابع عند استدعاء
# before_update كما يعرف رد النداء
end
end
التحويلات
إذا عرَّف صنفٌ التابعين ?persisted
و id
، فيمكنك تضمين الوحدة ActiveModel::Conversion
في ذلك الصنف، واستدعاء توابع التحويل في ريلز على كائنات ذلك الصنف.
class Person
include ActiveModel::Conversion
def persisted?
false
end
def id
nil
end
end
person = Person.new
person.to_model == person # => true
person.to_key # => nil
person.to_param # => nil
التلوث
يصبح الكائن متلوثًا عندما يمر بتغيير واحد أو أكثر لإحدى خاصياته دون حفظها. تعطي الوحدة ActiveModel::Dirty
القدرة على التحقق ما إذا تغير كائن أم لا. كما أن لديه خاصية تستند على توابع الوصول (accessor methods). دعنا نفترض وجود الصنف Person
مع الخاصيتين first_name
و last_name
:
class Person
include ActiveModel::Dirty
define_attribute_methods :first_name, :last_name
def first_name
@first_name
end
def first_name=(value)
first_name_will_change!
@first_name = value
end
def last_name
@last_name
end
def last_name=(value)
last_name_will_change!
@last_name = value
end
def save
# do save work...
changes_applied
end
end
الاستعلام عن الكائن مباشرة من أجل قائمة جميع خاصياته التي تغيرت
person = Person.new
person.changed? # => false
person.first_name = "First Name"
person.first_name # => "First Name"
# إذا كان أي من الخاصيات تحتوي على تغييرات لم تحفظ true يعيد القيمة
person.changed? # => true
# يعيد قائمة بالخاصيات التي تغيرت قبل الحفظ
person.changed # => ["first_name"]
# للخاصيات التي تغيرت مع قيمها الأصلية Hash يعيد جدول
person.changed_attributes # => {"first_name"=>nil}
# للتغييرات، مع أسماء الخاصيات كمفاتيح Hash يعيد جدول
# والقيم كمصفوفة للقيم القديمة والجديدة لهذا الحقل
person.changes # => {"first_name"=>[nil, "First Name"]}
توابع الوصول المستندة على الخاصية
تتبع ما إذا كان قد تغيرت خاصية معينة أم لا:
# attr_name_changed?
person.first_name # => "First Name"
person.first_name_changed? # => true
تتبع القيمة السابقة للخاصية:
# attr_name_was accessor
person.first_name_was # => nil
تتبع كل من القيمة السابقة والحالية للخاصية التي تغيرت. يعيد التابع مصفوفة في حالة تغييرها، وإلا يعيد القيمة nil
:
# attr_name_change
person.first_name_change # => [nil, "First Name"]
person.last_name_change # => nil
التحقق من الصحة
تضيف الوحدة ActiveModel::Validations
القدرة على التحقق من صحة الكائنات كما في السجل الفعال.
class Person
include ActiveModel::Validations
attr_accessor :name, :email, :token
validates :name, presence: true
validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i
validates! :token, presence: true
end
person = Person.new
person.token = "2b1f325"
person.valid? # => false
person.name = 'vishnu'
person.email = 'me'
person.valid? # => false
person.email = 'me@vishnuatrai.com'
person.valid? # => true
person.token = nil
person.valid? # => raises ActiveModel::StrictValidationFailed
التسمية
تضيف الوحدة ActiveModel::Naming
عددًا من توابع الأصناف التي تجعل من السهل إدارة التسمية والتوجيه. تحدد الوحدة تابع الصنف class_name
الذي سيعرِّف عددًا من توابع الوصول (accessors) باستخدام بعض توابع ActiveSupport::Inflector
.
class Person
extend ActiveModel::Naming
end
Person.model_name.name # => "Person"
Person.model_name.singular # => "person"
Person.model_name.plural # => "people"
Person.model_name.element # => "person"
Person.model_name.human # => "Person"
Person.model_name.collection # => "people"
Person.model_name.param_key # => "person"
Person.model_name.i18n_key # => :person
Person.model_name.route_key # => "people"
Person.model_name.singular_route_key # => "person"
الوحدة Model
تضيف الوحدة ActiveModel::Model
المقدرة للصنف على العمل مع الإجراء Pack
والإجراء View
مباشرةً خارج الصندوق.
class EmailContact
include ActiveModel::Model
attr_accessor :name, :email, :message
validates :name, :email, :message, presence: true
def deliver
if valid?
# توصيل بريد إلكتروني
end
end
end
عند تضمين ActiveModel::Model
، تحصل على بعض الميزات مثل:
- استبطان اسم نموذج (model name introspection).
- التحويلات.
- الترجمة.
- التحقق من الصحة.
كما يمنحك القدرة على تهيئة كائن بجدول Hash
من الخاصيات، مثل أي كائن سجل فعال:
email_contact = EmailContact.new(name: 'David',
email: 'david@example.com',
message: 'Hello World')
email_contact.name # => 'David'
email_contact.email # => 'david@example.com'
email_contact.valid? # => true
email_contact.persisted? # => false
يمكن استخدام أي صنف يتضمن ActiveModel::Model
مع form_for
، و render
، وأي تابع مساعد آخر لإجراء العرض، تمامًا مثل كائنات السجل الفعَّال.
السَلسَلة
توفر الوحدة ActiveModel::Serialization
عملية السَلسَلة الأساسية للكائن الخاص بك. يلزمك التصريح عن خاصيات Hash
تحتوي على الخاصيات التي تريد إجراء عملية سَلسَلة (serialize) لها. يجب أن تكون الخاصيات سلاسل نصية، وليست رموزًا.
class Person
include ActiveModel::Serialization
attr_accessor :name
def attributes
{'name' => nil}
end
end
الآن يمكنك الوصول إلى جدول Hash مُسَلسَل لكائنك باستخدام التابع serializable_hash
:
person = Person.new
person.serializable_hash # => {"name"=>nil}
person.name = "Bob"
person.serializable_hash # => {"name"=>"Bob"}
ActiveModel::Serializers
كما يوفر النموذج الفعال الوحدة ActiveModel::Serializers::JSON
لإجراء عملية السَلسَلة أو إلغائها مع صيغة JSON. يتضمن هذا النموذج تلقائيًا الوحدة ActiveModel::Serialization
الذي تحدثنا عنه آنفًا.
ActiveModel::Serializers::JSON
لاستخدام ActiveModel::Serializers::JSON
، تحتاج فقط إلى تغيير الوحدة التي تضمِّنها من ActiveModel::Serialization
إلى ActiveModel::Serializers::JSON
.
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name
def attributes
{'name' => nil}
end
end
التابع as_json
مشابه للتابع serializable_hash
، إذ يوفر جدول Hash يمثِّل النموذج.
person = Person.new
person.as_json # => {"name"=>nil}
person.name = "Bob"
person.as_json # => {"name"=>"Bob"}
يمكنك أيضًا تعريف الخاصيات لنموذج من سلسلة JSON نصية. ومع ذلك، تحتاج إلى تعريف التابع attributes=
في الصنف الخاصة بك:
class Person
include ActiveModel::Serializers::JSON
attr_accessor :name
def attributes=(hash)
hash.each do |key, value|
send("#{key}=", value)
end
end
def attributes
{'name' => nil}
end
end
الآن، من الممكن إنشاء نسخة من Person
وتعيين الخاصيات باستخدام from_json
.
json = { name: 'Bob' }.to_json
person = Person.new
person.from_json(json) # => #<Person:0x00000100c773f0 @name="Bob">
person.name # => "Bob"
الترجمة
توفر الوحدة ActiveModel::Translation
التكامل بين الكائن الخاص بك وإطار تدويل ريلز (i18n).
class Person
extend ActiveModel::Translation
end
باستخدام التابع human_attribute_name
، يمكنك تحويل أسماء الخاصيات إلى تنسيق أكثر قابلية للقراءة. يُعرف التنسيق القابل للقراءة في ملف(ات) المحلية الخاص بك.
- الملف config/locales/app.pt-BR.yml
pt-BR:
activemodel:
attributes:
person:
name: 'Nome'
Person.human_attribute_name('name') # => "Nome"
اختبارات الأداة Lint
يتيح لك النموذج ActiveModel::Lint::Tests
التحقق ما إذا كان كائنٌ ما متوافقًا مع واجهة النموذج الفعال البرمجية:
- الملف app/models/person.rb
class Person
include ActiveModel::Model
end
- الملف test/models/person_test.rb
require 'test_helper'
class PersonTest < ActiveSupport::TestCase
include ActiveModel::Lint::Tests
setup do
@model = Person.new
end
end
$ rails test
Run options: --seed 14596
# Running:
......
Finished in 0.024899s, 240.9735 runs/s, 1204.8677 assertions/s.
6 runs, 30 assertions, 0 failures, 0 errors, 0 skips
الكائن غير مطلوب لتنفيذ جميع واجهات برمجة التطبيقات لكي يعمل مع الإجراء Pack
. تعتزم هذه الوحدة فقط توفير الإرشاد في حال كنت تريد جميع الميزات من خارج الصندوق.
SecurePassword
توفر الوحدة ActiveModel::SecurePassword
طريقةً لتخزين أي كلمة مرور بشكل آمن في نموذج مشفر (encrypted form). عند تضمين هذه الوحدة، يُوفَّر تابع الصنف has_secure_password
الذي يعرِّف تابع الوصول password
مع بعض عمليات التحقق من الصحة عليه.
المتطلبات
تعتمد الوحدة ActiveModel::SecurePassword
على bcrypt، لذا ضمِّن هذه الجوهرة في Gemfile الخاص بك لاستخدام الوحدة ActiveModel::SecurePassword
بشكل صحيح. لجعلها تعمل، يجب أن يكون لدى النموذج تابع وصول يدعى password_digest
. سيضيف has_secure_password
عمليات التحقق التالية على تابع الوصول password
:
1 - كلمة المرور يجب أن تكون موجودة.
2 - يجب أن تكون كلمة المرور مساوية لتأكيدها (يُمرَّر password_confirmation
المعطى).
3 - الحد الأقصى لطول كلمة المرور هو 72 (مطلوب بواسطة bcrypt
التي تعتمد ActiveModel::SecurePassword
عليه).
أمثلة
class Person
include ActiveModel::SecurePassword
has_secure_password
attr_accessor :password_digest
End
person = Person.new
# عندما تكون كلمة المرور فارغة
person.valid? # => false
# عندما لا يتطابق التأكيد مع كلمة المرور
person.password = 'aditya'
person.password_confirmation = 'nomatch'
person.valid? # => false
# عندما يتجاوز طول كلمة المرور 72
person.password = person.password_confirmation = 'a' * 100
person.valid? # => false
# password_confirmation عندما تتوفر كلمة المرور فقط بدون
person.password = 'aditya'
person.valid? # => true
# عندما تُمرَّر جميع عمليات التحقق من الصحة
person.password = person.password_confirmation = 'aditya'
person.valid? # => true