أساسيات Active Model في ريلز

من موسوعة حسوب
مراجعة 09:54، 24 مارس 2019 بواسطة جميل-بيلوني (نقاش | مساهمات)
(فرق) → مراجعة أقدم | المراجعة الحالية (فرق) | مراجعة أحدث ← (فرق)

يجب أن يوفر لك هذا الدليل كل ما تحتاج إليه للبدء في استخدام أصناف Model. يسمح Active Model لمساعدي Action Pack بالتفاعل مع كائنات روبي الصرفة. يساعد Active Model أيضًا على إنشاء قواعد بيانات ORM مخصصة للاستخدام خارج إطار ريلز.

بعد قراءة هذا الدليل، ستتعلم:

مقدمة

Active Model هو مكتبة تحتوي على وحدات مختلفة تستخدم في تطوير الأصناف التي تحتاج إلى بعض الميزات الموجودة في Active Record. بعض هذه الوحدات مشروحة أدناه.

توابع الخاصيات

تضيف الوحدة 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 ردود نداء بنمط Active Record. هذا يوفر القدرة على تحديد ردود النداء التي تعمل في الأوقات المناسبة. بعد تحديد ردود النداء، يمكنك تغليفها قبل، وبعد، وحول توابع مخصصة.

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 القدرة على التحقق من صحة الكائنات كما في Active Record.

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 المقدرة للصنف على العمل مع Action Pack و Action 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 من الخاصيات، مثل أي كائن Active Record:

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، وأي تابع مساعد آخر Action View، تمامًا مثل كائنات Active Record.

السَلسَلة

توفر الوحدة 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

كما يوفر Active Model الوحدة 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 التحقق ما إذا كان كائنٌ ما متوافقًا مع واجهة Active Model البرمجية:

  • الملف 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

الكائن غير مطلوب لتنفيذ جميع واجهات برمجة التطبيقات لكي يعمل مع Action 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

المصادر