چگونه نگرانی را در Ruby on Rails اضافه کنیم؟

بررسی اجمالی:
در این مقاله به استفاده از آن می پردازیم ActiveSupport::Concern
. ما به این خواهیم پرداخت که چیست، چرا به آن نیاز داریم و مشکلاتی که می تواند حل کند. علاوه بر این، مزایا و معایب مربوط به استفاده از آن را بررسی خواهیم کرد. برای نشان دادن اجرای عملی آن، سه مثال ارائه می دهیم.
تعریف:
به زبان ساده، Concern
به شما امکان می دهد منطق مشترک را از مدل های مختلف در یک ماژول قابل استفاده مجدد استخراج کنید. ممکن است بپرسید، “چرا نمی توانیم فقط از یک ماژول استفاده کنیم؟ چرا باید چیز دیگری تعریف کنیم؟” در اینجا تفاوت های اصلی وجود دارد: اساسا، Concern
یک ماژول معمولی است، اما به شما امکان می دهد هر کدام را اضافه کنید ActiveRecord
رفتارهایی که می توانید بر روی مدل تعریف کنید. بنابراین، می توانیم آن را به عنوان یک ماژول برای کپسوله سازی و استفاده مجدد در نظر بگیریم ActiveRecord
مواد و روش ها.
مزایای:
- قابلیت استفاده مجدد کد: ActiveSupport Concern به ما اجازه می دهد تا با استخراج منطق مشترک در ماژول های قابل استفاده مجدد، از اصل DRY پیروی کنیم.
چالش ها و مسائل:
- وابستگی های پیچیده: استفاده بیش از حد از نگرانی ها می تواند منجر به شبکه پیچیده ای از وابستگی ها شود که درک و نگهداری کد را سخت تر می کند.
- مشکل در جستجوی کد: یافتن تعریف یک رفتار یا عملکرد خاص به دلیل ماهیت پراکنده نگرانی ها می تواند چالش برانگیزتر باشد.
در اصل، نگرانی ها برای ذخیره سازی اجزای فنی مناسب هستند، مانند:
- افزودن کمک کننده های فایل برای تسهیل مدیریت فایل ها با یک رابط واضح.
- ایجاد پوششهایی برای ویژگیها و مقادیر موجود برای افزایش وضوح رابط.
- پیادهسازی ویژگیهای فنی مانند ردیابی تاریخچه تغییرات، ورود به سیستم و موارد دیگر.
- و غیره…
با این حال، نگرانی ها برای ذخیره منطق تجاری مناسب نیستند.
پیاده سازی
در بخش بعدی، عناصر کلیدی نگرانی ها را مورد بحث قرار خواهیم داد. برای درک نگرانیها، فقط باید بدانیم که ماژول چیست و چیست included
و class_methods
روش ها از ActiveSupport::Concern
.
مدول
یک ماژول در Ruby محفظهای است که متدهای مرتبط، ثابتها و سایر تعاریف ماژول یا کلاس را در کنار هم قرار میدهد. این روشی را برای سازماندهی و استفاده مجدد کد به صورت مدولار فراهم می کند. و Сoncern فقط یک ماژول است، بنابراین باید بدانیم چگونه آن را ایجاد کنیم.
# app/models/concerns/concern_sample.rb
# frozen_string_literal: true
module ConcernSample
def hello_world
puts 'Hello World!'
end
end
ActiveSupport::روش های نگرانی
بنابراین، تفاوت اصلی بین یک ماژول ساده روبی و یک نگرانی چیست؟ تفاوت های اصلی این است که وقتی شما را شامل می شود ActiveSupport::Concern
در یک ماژول، دو روش جدید اضافه می شود. این روش ها عبارتند از:
این روش ما را قادر می سازد منطق Active Record را مستقیماً به ماژول اضافه کرده و در سطح مدل مجدداً از آن استفاده کنیم. مثلا:
# app/models/concerns/concern_sample.rb
# frozen_string_literal: true
module ConcernSample
extend ActiveSupport::Concern
included do
# Define class methods here
def self.my_class_method
# Class method logic
end
# Define instance methods here
def my_instance_method
# Instance method logic
end
# Define associations here
has_many :comments
belongs_to :category
# Define validations here
validates_presence_of :name
validates_uniqueness_of :email
# Define callbacks here
before_save :do_something
after_create :do_something_else
# Define scopes here
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc) }
end
end
این متد به ما اجازه می دهد تا متدهایی را اضافه کنیم که می توانند از خود کلاس فراخوانی شوند:
# app/models/concerns/concern_sample.rb
# frozen_string_literal: true
module ConcernSample
extend ActiveSupport::Concern
class_methods do
def my_class_method
# Class method logic
end
end
end
بیایید سه مثال را برای درک بهتر این مفهوم در نظر بگیریم.
مثال 1: توضیحات کامل.
در مثال اول، ما یک سناریوی بسیار اساسی را برای به دست آوردن درک درستی از نحوه عملکرد واقعی آن در نظر خواهیم گرفت. به عنوان مثال، فرض کنید مدل زیر را داریم:
# app/models/animal.rb
class Animal < ApplicationRecord
end
سپس تصمیم گرفتیم روشی را به مدل اضافه کنیم که شرح کامل این حیوان را ارائه دهد.
# app/models/animal.rb
class Animal < ApplicationRecord
def full_description
"Name: #{name}. Kind: #{kind}. Status: #{status}."
end
end
در اینجا نحوه کار آن آمده است:
animal = Animal.first
animal.full_description
# => "Name: Simba. Kind: Cat. Status: Adopted"
animal = Animal.second
animal.full_description
# => "Name: Rocky. Kind: Rabbit. Status: Available"
اگر بخواهیم این منطق را به سمت نگرانی سوق دهیم، باید چه کار کنیم؟ اول از همه، باید یک نگرانی جدید ایجاد کنیم:
# app/models/concerns/full_description.rb
# frozen_string_literal: true
module FullDescription
def full_description
"Name: #{name}. Kind: #{kind}. Status: #{status}"
end
end
و این نگرانی را در سطح مدل لحاظ کنید:
# app/models/animal.rb
class Animal < ApplicationRecord
include FullDescription
end
بنابراین همان رابط کاربری هنوز در دسترس است:
animal = Animal.first
animal.full_description
# => "Name: Simba. Kind: Cat. Status: Adopted"
ما درج نکردیم ActiveSupport::Concern
در اینجا فقط برای برجسته کردن آن برای روش های ساده بدون ActiveRecord
منطقی است، هنوز هم خوب کار خواهد کرد. با این حال، با آن نیز کار خواهد کرد ActiveSupport::Concern
:
# app/models/concerns/full_description.rb
# frozen_string_literal: true
module FullDescription
extend ActiveSupport::Concern
included do
def full_description
"Name: #{name}. Kind: #{kind}. Status: #{status}."
end
end
end
مثل این:
animal = Animal.first
animal.full_description
# => "Name: Simba. Kind: Cat. Status: Adopted."
و اگر می خواهید متدهای کلاس اضافه کنید، می توانیم موارد زیر را انجام دهیم:
# app/models/concerns/full_description.rb
# frozen_string_literal: true
module FullDescription
extend ActiveSupport::Concern
class_methods do
def full_description
all.map { _1.full_description }.join(' ')
end
end
end
در حال حاضر full_description
روش در دسترس است Animal
کلاس:
Animal.full_description
# => "Name: Simba. Kind: Cat. Status: Adopted. Name: Rocky. Kind: Rabbit. Status: Available."
مثال 2: UUID
در مثال دوم، نگرانی ایجاد می کنیم که یک مقدار UUID منحصر به فرد را قبل از ایجاد یک رکورد پایگاه داده جدید ایجاد و ذخیره می کند. برای رسیدن به این هدف، نگرانی جدیدی را به صورت زیر تعریف می کنیم:
# app/models/concerns/uuid.rb
# frozen_string_literal: true
module UUID
extend ActiveSupport::Concern
included do
before_create :set_uuid
private
def set_uuid
self.uuid ||= SecureRandom.uuid
end
end
end
و ما آن را در سطح مدل قرار خواهیم داد:
# app/models/animal.rb
class Animal < ApplicationRecord
include UUID
end
سپس، هر بار که یک رکورد Animal جدید در پایگاه داده ایجاد می کنیم، UUID به طور خودکار برای ما ایجاد می شود:
animal = Animal.create
animal.uuid
# => '7f934646-bc67-446c-920d-ca5415d28b94'
PS برای استفاده از مخفف UUID، باید کد زیر را به آن اضافه کنید config/initializers/inflections.rb
:
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
inflect.acronym 'UUID'
end
مثال 3: Predicate Methods
در آخرین مثال، بیایید اضافه کردن نگرانی را در نظر بگیریم که به ما امکان میدهد رابط را برای ویژگیهایی با تعداد ثابتی از مقادیر موجود ساده کنیم. به عنوان مثال، اگر یک animal
رکورد دارای یک status
ویژگی با مقادیر موجود زیر: ['available', 'adopted', 'pending']
، سپس ما می خواهیم رابط زیر را داشته باشیم:
animal = Animal.create(status: 'available')
animal.available?
# => true
animal.adopted?
# => false
animal.pending?
# => false
# or with prexix
animal.status_available?
# => true
animal.status_adopted?
# => flase
# etc ..
برای اجرای آن چه کاری باید انجام دهیم؟ بیایید یک نگرانی جدید به نام ایجاد کنیم PredicateMethods
و روش آن را به مدل اضافه کنید:
# app/models/animal.rb
class Animal < ApplicationRecord
include PredicateMethods
define_predicate_methods attribute: :status,
available_values: ['available', 'adopted', 'pending'],
prefix: false
end
پس این نگرانی چگونه اجرا می شود؟ این کاملا ساده است. فقط باید یک متد جدید به نام تعریف کنیم define_predicate_methods
و این روش برای هر مقدار موجود که به عنوان آرگومان ارسال می کنید، متدهای جدیدی را در مدل ایجاد می کند. در داخل این روش بررسی می کنیم که آیا مقدار وضعیت فعلی برابر با مقدار داده شده است یا خیر. در اینجا پیاده سازی است:
# app/models/concerns/predicate_methods.rb
# frozen_string_literal: true
module PredicateMethods
extend ActiveSupport::Concern
MethodAlreadyDefined = Class.new(ArgumentError)
class_methods do
def define_predicate_methods(attribute:, available_values:, prefix: false)
available_values.each do |value|
method_name = [(prefix ? "#{attribute}_" : ''), value, '?'].join.to_sym
raise MethodAlreadyDefined, "#{method_name} already defined" if instance_methods.include?(method_name)
define_method(method_name) do
public_send(attribute) == value
end
end
end
end
end
اکنون رابط ما مطابق دلخواه کار می کند:
animal = Animal.last
animal.available?
# => true
animal.adopted?
# => false
اگر می خواهید اضافه کنید status
پیشوند، فقط باید مقدار the را تغییر دهید prefix
صفت از false
به true
:
animal = Animal.last
animal.status_available?
# => true
animal.status_adopted?
# => false
خودشه! اکنون می توانیم این نگرانی را به هر یک از مدل های خود اضافه کنیم و رابط های آنها را خواناتر و ساده تر کنیم.
نتیجه
در پایان، در این مقاله به کاربرد آن پرداختیم ActiveSupport::Concern
در ریل. ما بررسی کردیم که چیست، چرا به آن نیاز داریم و مشکلاتی که می تواند حل کند. ما مزایا و معایب استفاده از این رویکرد را با در نظر گرفتن دیدگاههای مختلف بررسی کردیم. علاوه بر این، برای افزایش درک خود از مفهوم، سه مثال ارائه کردیم که اجرای عملی آن را نشان میداد.