برنامه نویسی

چگونه نگرانی را در 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 در ریل. ما بررسی کردیم که چیست، چرا به آن نیاز داریم و مشکلاتی که می تواند حل کند. ما مزایا و معایب استفاده از این رویکرد را با در نظر گرفتن دیدگاه‌های مختلف بررسی کردیم. علاوه بر این، برای افزایش درک خود از مفهوم، سه مثال ارائه کردیم که اجرای عملی آن را نشان می‌داد.

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا