---
title: "أنواع السمات المخصصة في Rails 5 وما بعده"
description: "مع صدور Rails 5 ظهرت واجهة attributes API الجديدة، وأتاحت لنا الاستفادة من آلية تحويل الأنواع التي كانت Rails تستخدمها داخليًا منذ زمن طويل."
canonical: https://oozou.com/ar/blog/custom-attribute-types-in-rails-5-77
locale: ar
date: 2020-07-28
author: "OOZOU Team"
---

# أنواع السمات المخصصة في Rails 5 وما بعده

> مع صدور Rails 5 ظهرت واجهة attributes API الجديدة، وأتاحت لنا الاستفادة من آلية تحويل الأنواع (type casting) التي كانت Rails تستخدمها داخليًا منذ زمن طويل.

Published: 2020-07-28 · Author: OOZOU Team (Engineering) · 6 min read

هل لاحظتم أن المعاملات (parameters) التي تصل إلى الـ controller تأتي على شكل نصوص، ثم تتحول إلى تواريخ وأعداد صحيحة وأعداد عشرية وغيرها بمجرد إسنادها إلى كائن من الـ model؟ يحدث كل ذلك عبر واجهة _ActiveModel::Types_. وقد ذهب فريق Rails خطوة أبعد في هذا الإصدار، إذ أصبح بإمكاننا استخدامها أيضًا في أصناف _ActiveModel_، وهي في تجربتي حالة استخدام أكثر شيوعًا بكثير.
وفي كل من _ActiveRecord_ و _ActiveModel_ تتطابق الخطوات لإنشاء أنواع مخصصة وتحويل السمات باستخدامها.
نبدأ بإنشاء نوع مخصص، ثم نسجّل النوع الجديد في ActiveRecord أو ActiveModel، وبعدها نستخدمه عند تعريف السمات.

# مقدمة عن أنواع السمات المخصصة

أنواع السمات المخصصة ميزة قوية في Rails تتيح للمطوّرين تعريف أنواع خاصة بهم لسمات الـ models. ظهرت هذه الميزة في Rails 5 وغيّرت طريقة عمل كثير من المطوّرين. فبفضلها يمكنكم التحكم في طريقة تحويل القيم من SQL وإليها عند إسنادها إلى الـ model. أي أنكم تستطيعون تغيير سلوك القيم الممررة إلى ActiveRecord::Base.where، واستخدام كائنات المجال (domain objects) في معظم أجزاء Active Record دون الاعتماد على تفاصيل التنفيذ الداخلية أو على الـ monkey patching. ومع الأنواع المخصصة تضمنون أن تُعالَج بياناتكم بالطريقة التي تحتاجونها بالضبط.

# إنشاء نوع مخصص

من المعتاد في Rails العمل بتوقيت UTC. لكن كثيرًا من واجهات API التي نتعامل معها هنا في تايلاند تتوقع قيمًا بتوقيت GMT+7. ولتلبية ذلك يمكننا إنشاء نوع جديد باسم BangkokDateTimeType يحوّل القيم إلى المنطقة الزمنية لبانكوك. يتضمن ActiveModel أصلًا النوع DateTime، فنبني فوقه ونعدّل الدالة cast لتحوّل القيمة إلى المنطقة الزمنية الصحيحة

```ruby
class BangkokDateTimeType < ActiveModel::Type::DateTime
  def cast(value)
    return nil unless value.present?

    value.in_time_zone('Bangkok')
  end
end
```

# فهم النوع

في Rails يُحدَّد نوع السمة من خلال المعامل cast_type عند تعريفها بالدالة attribute. ويمكن أن يكون cast_type رمزًا مثل :string أو :integer، أو كائن نوع (type object). وكائنات النوع هي نسخ من أصناف تنفّذ الدالتين cast وserialize. ولهاتين الدالتين دور أساسي، لأنهما تحوّلان القيم من SQL وإليها عند إسنادها إلى الـ model. وعندما تعرّفون كائنات نوع خاصة بكم، تحصلون على أنواع سمات مخصصة تستخدمونها في الـ models، فيصبح لديكم تحكم أكبر في طريقة معالجة البيانات وتخزينها.

# تسجيل الأنواع في ActiveRecord وActiveModel

نحتاج الآن إلى تسجيل هذا النوع في ActiveRecord وActiveModel، وسننشئ initializer لهذا الغرض.
الوسيط الأول هو الرمز الذي سيعرّف النوع عند تعريف السمات.

```ruby
# config/initializers/attribute_types.rb
ActiveRecord::Type.register(:bangkok_datetime, BangkokDateTimeType)
ActiveModel::Type.register(:bangkok_datetime, BangkokDateTimeType)
```

# تنسيق قيم السمات بالشكل المطلوب

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

# تحويل الـ params تلقائيًا مع Rails Attributes API

تستطيع Rails Attributes API تحويل الـ params تلقائيًا إلى النوع الصحيح عند تعريف السمات في الـ model بالدالة attribute. وتفيد هذه الميزة خصوصًا عند التعامل مع بيانات النماذج (forms) أو طلبات API، حيث تصل البيانات غالبًا على شكل نصوص. فعندما تعرّفون السمات بالنوع الصحيح، تتحول البيانات إليه تلقائيًا. وهذا لا يبسّط الكود فحسب، بل يقلّل أيضًا من الكود المتكرر (boilerplate) في form objects وreport objects وغيرها من أصناف Ruby الشبيهة بالـ models. باختصار، تسهّل Rails Attributes API التعامل مع البيانات بدقة واتساق.

# استخدام الأنواع المخصصة

بعد أن عرّفنا النوع، يمكننا استخدامه في أصناف ActiveRecord وActiveModel، وكلاهما يستخدم الصيغة نفسها تمامًا:

```ruby
class Payment < ApplicationRecord
  attribute :transaction_time, :bangkok_datetime
end
```

بعد تعريف السمة transaction_time في Payment، يمكننا تمرير قيم تاريخ أو وقت وستُحوَّل إلى منطقتنا الزمنية، مثلًا:

```ruby
Payment.new(transaction_time: '2020-01-01T00:00:00').transaction_time.iso8601
=> "2020-01-01T00:00:00+07:00"
```

لاحظوا أننا لم نحدد المنطقة الزمنية، ومع ذلك تحوّل Rails القيمة بشكل صحيح إلى توقيت بانكوك. ولا يزال بإمكاننا تمرير المنطقة الزمنية، وستعرف Rails كيف تتعامل معها

```ruby
Payment.new(transaction_time: '2020-01-01T00:00:00Z').transaction_time.iso8601
=> "2020-01-01T07:00:00+07:00"
```

هنا تحترم Rails المنطقة الزمنية الممررة، لكنها تعرض القيمة بتوقيت بانكوك.

# أفضل الممارسات واعتبارات مهمة

هناك عدة ممارسات واعتبارات ينبغي مراعاتها عند العمل مع أنواع السمات المخصصة وRails Attributes API. أولًا، من المهم تعريف أنواع السمات المخصصة في module أو صنف مستقل ليبقى الكود منظمًا وقابلًا لإعادة الاستخدام. ثانيًا، عند تنفيذ كائنات النوع المخصصة، التزموا بالإرشادات الواردة في توثيق Rails لضمان تنفيذها بشكل صحيح. وأخيرًا، اختبروا الكود جيدًا عند استخدام Rails Attributes API للتأكد من أن البيانات تتحول بشكل صحيح. وباتباع هذه الممارسات تستفيدون إلى أقصى حد من أنواع السمات المخصصة، ويتعامل تطبيقكم مع البيانات بكفاءة ودقة.

# أين تفيد هذه الأنواع في قاعدة البيانات؟

يمكننا إنشاء _DowncasedStringType_ لضمان أن تكون عناوين البريد الإلكتروني دائمًا بأحرف صغيرة، أو _ClampedPercentageType_ لقبول القيم بين 0.0 و 100.0 فقط. ويمكننا أيضًا تنفيذ أمور أعقد، مثل إعطاء بنية واضحة لعمود jsonb

```ruby
class RewardDetailType < ActiveRecord::Type::Value
  REWARD_DETAIL = Struct.new(:type, :value, :unit, keyword_init: true)

  def type
    :jsonb
  end

  def cast(value)
    sanitized = sanitize_input(value)
    REWARD_DETAIL.new(sanitized)
  end

  def deserialize(value)
    decoded = ActiveSupport::JSON.decode(value)
    sanitized = sanitize_input(decoded)
    REWARD_DETAIL.new(sanitized)
  end

  def serialize(value)
    ActiveSupport::JSON.encode(value)
  end

  private def sanitize_input(input)
    input.slice(:type, :value, :unit)
  end
end
```

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

```ruby
class Reward < ApplicationRecord
  attribute :detail, RewardDetailType.new
end
```

الآن يمكننا تمرير hash أو جلب سجل من قاعدة البيانات، ثم التعامل مع بيانات json كأنها struct، لأنها فعلًا struct، مثلًا:

```ruby
reward = Reward.new(detail: { type: :magic, value: Float::INFINITY, unit: :coffee })
reward.detail.type
=> :magic
reward.detail.unit
=> :coffee
```

واجهة attributes API قوية جدًا، وكثيرًا ما يُغفَل عنها. يمكنكم من خلالها تحويل القيم وعمل serialize وdeserialize لها، بل وتحديد قيم افتراضية للسمات أيضًا. أنصحكم بشدة بتجربتها.

---

Contact OOZOU: hello@oozou.com · +66 2544 0000 · https://oozou.com/ar/contact

More for AI agents: https://oozou.com/llms.txt · every page has a markdown twin at its URL plus ".md"
