πŸ” ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΎ ΠΌΠ΅Ρ‚ΠΎΠ΄Π°Ρ…: ΠΊΠ°ΠΊ Ruby позволяСт ΠΈΡΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚ΡŒ ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ²

Π’ Ruby всё β€” ΠΎΠ±ΡŠΠ΅ΠΊΡ‚, Π° ΠΊΠ°ΠΆΠ΄Ρ‹ΠΉ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ ΠΎΠ±Π»Π°Π΄Π°Π΅Ρ‚ ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ΠΌ, опрСдСляСмым Π΅Π³ΠΎ ΠΌΠ΅Ρ‚ΠΎΠ΄Π°ΠΌΠΈ. Но ΠΊΠ°ΠΊ ΡƒΠ·Π½Π°Ρ‚ΡŒ, ΠΊΠ°ΠΊΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ доступны ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Ρƒ, ΠΊΠ°ΠΊ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΈΡ‚ΡŒ Π΅Π³ΠΎ возмоТности ΠΈ Π΄Π°ΠΆΠ΅ ΠΏΠΎΠ΄ΠΌΠ΅Π½ΠΈΡ‚ΡŒ ΠΈΡ…? Π Π°Π·Π±Π΅Ρ€Ρ‘ΠΌ инструмСнты интроспСкции Ruby Π½Π° практичСских ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π°Ρ… ΠΈΠ· ΠΆΠΈΠ·Π½ΠΈ Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ°.


🧠 ВСория: Π·Π°Ρ‡Π΅ΠΌ Π½ΡƒΠΆΠ½Π° интроспСкция ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ²?

Π˜Π½Ρ‚Ρ€ΠΎΡΠΏΠ΅ΠΊΡ†ΠΈΡ (рСфлСксия) β€” это Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡ‚ΡŒ ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΡ‹ ΠΈΡΡΠ»Π΅Π΄ΠΎΠ²Π°Ρ‚ΡŒ свою ΡΠΎΠ±ΡΡ‚Π²Π΅Π½Π½ΡƒΡŽ структуру Π²ΠΎ врСмя выполнСния. Π’ Ruby это особСнно Π²Π°ΠΆΠ½ΠΎ, ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ:

  • ΠžΠ±ΡŠΠ΅ΠΊΡ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ динамичСски ΠΏΠΎΠ»ΡƒΡ‡Π°Ρ‚ΡŒ Π½ΠΎΠ²Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹
  • ΠšΠ»Π°ΡΡΡ‹ ΠΌΠΎΠ³ΡƒΡ‚ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΡΡ‚ΡŒ ΠΏΠΎΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ β€œΠ½Π° лСту”
  • ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ ΠΏΡ€ΠΈΠ²Π°Ρ‚Π½Ρ‹ΠΌΠΈ, Π·Π°Ρ‰ΠΈΡ‰Ρ‘Π½Π½Ρ‹ΠΌΠΈ ΠΈΠ»ΠΈ ΠΏΡƒΠ±Π»ΠΈΡ‡Π½Ρ‹ΠΌΠΈ

Π Π°Π·Π±Π΅Ρ€Ρ‘ΠΌ ΠΊΠ»ΡŽΡ‡Π΅Π²Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ для Ρ€Π°Π±ΠΎΡ‚Ρ‹ с ΠΌΠ΅Ρ‚ΠΎΠ΄Π°ΠΌΠΈ.


1️⃣ method β€” ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ ΠΌΠ΅Ρ‚ΠΎΠ΄ ΠΊΠ°ΠΊ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚

user = User.find(1)
save_method = user.method(:save)
# => #<Method: User#save()>

save_method.call # Π°Π½Π°Π»ΠΎΠ³ΠΈΡ‡Π½ΠΎ user.save

Π–ΠΈΠ·Π½Π΅Π½Π½Ρ‹ΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€:
Π’Ρ‹ ΠΏΠΈΡˆΠ΅Ρ‚Π΅ ΡƒΠ½ΠΈΠ²Π΅Ρ€ΡΠ°Π»ΡŒΠ½Ρ‹ΠΉ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ событий, Π³Π΄Π΅ Π½ΡƒΠΆΠ½ΠΎ Π²Ρ‹Π·Ρ‹Π²Π°Ρ‚ΡŒ Ρ€Π°Π·Π½Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° Π² зависимости ΠΎΡ‚ входящих Π΄Π°Π½Π½Ρ‹Ρ…:

def handle_event(object, event_name)
  if object.respond_to?(event_name)
    object.method(event_name).call
  else
    # ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° отсутствия ΠΌΠ΅Ρ‚ΠΎΠ΄Π°
  end
end

АнтипаттСрн:
Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ method Π±Π΅Π· ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ respond_to? β€” это Π²Π΅Ρ€Π½Ρ‹ΠΉ способ ΠΏΠΎΠ»ΡƒΡ‡ΠΈΡ‚ΡŒ NameError.


2️⃣ respond_to? β€” провСряСм Π½Π°Π»ΠΈΡ‡ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Π°

"строка".respond_to?(:upcase) # => true
42.respond_to?(:split)       # => false

ΠŸΡ€Π°ΠΊΡ‚ΠΈΡ‡Π΅ΡΠΊΠΈΠΉ кСйс:
ΠŸΡ€ΠΈ Ρ€Π°Π±ΠΎΡ‚Π΅ с API ΠΎΡ‚Π²Π΅Ρ‚ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΡ€ΠΈΡ…ΠΎΠ΄ΠΈΡ‚ΡŒ Π² Ρ€Π°Π·Π½Ρ‹Ρ… Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π°Ρ…:

def parse_response(response)
  if response.respond_to?(:to_h)
    response.to_h
  elsif response.respond_to?(:to_json)
    JSON.parse(response.to_json)
  else
    raise "Unsupported response format"
  end
end

Π’Π°ΠΆΠ½ΠΎ:
respond_to? Π½Π΅ Π²ΠΈΠ΄ΠΈΡ‚ ΠΏΡ€ΠΈΠ²Π°Ρ‚Π½Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΏΠΎ ΡƒΠΌΠΎΠ»Ρ‡Π°Π½ΠΈΡŽ. НуТно ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‚ΡŒ Π²Ρ‚ΠΎΡ€ΠΎΠΉ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€:

user.respond_to?(:admin?, true) # провСряСт ΠΏΡ€ΠΈΠ²Π°Ρ‚Π½Ρ‹Π΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ Ρ‚ΠΎΠΆΠ΅

3️⃣ respond_to_missing? β€” магия method_missing

Когда вызываСтся Π½Π΅ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠΉ ΠΌΠ΅Ρ‚ΠΎΠ΄, Ruby провСряСт method_missing. Но для ΠΊΠΎΡ€Ρ€Π΅ΠΊΡ‚Π½ΠΎΠΉ Ρ€Π°Π±ΠΎΡ‚Ρ‹ respond_to? Π½ΡƒΠΆΠ½ΠΎ ΠΏΠ΅Ρ€Π΅ΠΎΠΏΡ€Π΅Π΄Π΅Π»ΠΈΡ‚ΡŒ ΠΈ respond_to_missing?:

class DynamicAttributes
  def method_missing(name, *args)
    if name.to_s.start_with?('find_by_')
      # рСализация поиска
    else
      super
    end
  end

  def respond_to_missing?(name, include_private = false)
    name.to_s.start_with?('find_by_') || super
  end
end

ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΈΠ· ΠΆΠΈΠ·Π½ΠΈ:
ORM Π²Ρ€ΠΎΠ΄Π΅ ActiveRecord ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ это для динамичСских finder’ов:

User.respond_to?(:find_by_email) # => true, хотя ΠΌΠ΅Ρ‚ΠΎΠ΄ явно Π½Π΅ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Ρ‘Π½

4️⃣ method_defined? β€” ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° Π½Π° ΡƒΡ€ΠΎΠ²Π½Π΅ класса

User.method_defined?(:save) # => true
User.method_defined?(:new)  # => false (это ΠΌΠ΅Ρ‚ΠΎΠ΄ класса)

ΠŸΡ€Π°ΠΊΡ‚ΠΈΡ‡Π΅ΡΠΊΠΎΠ΅ ΠΏΡ€ΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠ΅:
ΠŸΡ€ΠΈ создании ΠΏΠ»Π°Π³ΠΈΠ½Π° ΠΈΠ»ΠΈ модуля, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ добавляСт ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Ссли ΠΎΠ½ΠΈ Π΅Ρ‰Ρ‘ Π½Π΅ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Ρ‹:

module SafeMonkeyPatch
  def add_feature
    unless method_defined?(:original_method)
      alias_method :original_method, :method
      # ... наша пСрСопрСдСлённая рСализация
    end
  end
end

ΠžΡΡ‚ΠΎΡ€ΠΎΠΆΠ½ΠΎ:
method_defined? Π½Π΅ провСряСт ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΌΠΎΠ΄ΡƒΠ»Π΅ΠΉ, Π²ΠΊΠ»ΡŽΡ‡Ρ‘Π½Π½Ρ‹Ρ… Π² класс.


5️⃣ instance_methods β€” всС ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ экзСмпляра

User.instance_methods(false) # Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹, ΠΎΠΏΡ€Π΅Π΄Π΅Π»Ρ‘Π½Π½Ρ‹Π΅ Π² User
User.instance_methods(true)  # Π²ΠΊΠ»ΡŽΡ‡Π°Ρ унаслСдованныС

Π Π΅Π°Π»ΡŒΠ½Ρ‹ΠΉ ΠΏΡ€ΠΈΠΌΠ΅Ρ€:
ГСнСрация Π΄ΠΎΠΊΡƒΠΌΠ΅Π½Ρ‚Π°Ρ†ΠΈΠΈ ΠΈΠ»ΠΈ валидация интСрфСйса:

required_methods = [:save, :validate, :to_json]
missing = required_methods - User.instance_methods(false)

raise "Interface not implemented: #{missing}" unless missing.empty?

Π‘ΠΎΠ²Π΅Ρ‚:
Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ false ΠΊΠ°ΠΊ ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€, Ρ‡Ρ‚ΠΎΠ±Ρ‹ Π½Π΅ Π·Π°ΡΠΎΡ€ΡΡ‚ΡŒ Π²Ρ‹Π²ΠΎΠ΄ унаслСдованными ΠΌΠ΅Ρ‚ΠΎΠ΄Π°ΠΌΠΈ.


6️⃣ singleton_methods β€” ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠ³ΠΎ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°

user = User.new
def user.custom_method; end

user.singleton_methods # => [:custom_method]

КСйс ΠΈΠ· ΠΏΡ€Π°ΠΊΡ‚ΠΈΠΊΠΈ:
Π”Π΅ΠΊΠΎΡ€Π°Ρ‚ΠΎΡ€Ρ‹ ΠΈΠ»ΠΈ динамичСскоС Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠ΅ повСдСния:

def decorate_user(user, role)
  if role == :admin
    def user.admin?; true; end
  end
  user
end

admin = decorate_user(User.new, :admin)
admin.singleton_methods # => [:admin?]

АнтипаттСрн:
Π—Π»ΠΎΡƒΠΏΠΎΡ‚Ρ€Π΅Π±Π»Π΅Π½ΠΈΠ΅ синглтон-ΠΌΠ΅Ρ‚ΠΎΠ΄Π°ΠΌΠΈ услоТняСт ΠΎΡ‚Π»Π°Π΄ΠΊΡƒ ΠΈ тСстированиС.


πŸ§ͺ ВСстированиС ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ²

ΠŸΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ° наличия ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² Π² тСстах:

describe User do
  it "implements required interface" do
    expect(User.instance_methods).to include(:save, :destroy)
  end

  it "responds to dynamic finders" do
    expect(User.new).to respond_to(:find_by_email)
  end
end

🎀 Π§Ρ‚ΠΎ ΡΠΊΠ°Π·Π°Ρ‚ΡŒ Π½Π° собСсСдовании

β€” Как Π²Ρ‹ провСряСтС, Ρ‡Ρ‚ΠΎ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΠ΅Ρ‚ Π½ΡƒΠΆΠ½Ρ‹ΠΉ интСрфСйс?

β€” Π’ Ruby Π΅ΡΡ‚ΡŒ нСсколько ΡƒΡ€ΠΎΠ²Π½Π΅ΠΉ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ: respond_to? для ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠ³ΠΎ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π°, method_defined? для класса, instance_methods для ΠΏΠΎΠ»Π½ΠΎΠ³ΠΎ списка. Π’Π°ΠΆΠ½ΠΎ ΠΏΠΎΠ½ΠΈΠΌΠ°Ρ‚ΡŒ различия ΠΈ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ ΠΈΡ… Π² зависимости ΠΎΡ‚ контСкста.


🧾 Π’Ρ‹Π²ΠΎΠ΄

  1. Π˜ΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ method ΠΊΠΎΠ³Π΄Π° Π½ΡƒΠΆΠ½ΠΎ Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ с ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠΌ ΠΊΠ°ΠΊ с ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠΌ
  2. respond_to? β€” ваша пСрвая линия Π·Π°Ρ‰ΠΈΡ‚Ρ‹ ΠΎΡ‚ NoMethodError
  3. Для method_missing всСгда Ρ€Π΅Π°Π»ΠΈΠ·ΡƒΠΉΡ‚Π΅ respond_to_missing?
  4. method_defined? ΠΈ instance_methods Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‚ Π½Π° ΡƒΡ€ΠΎΠ²Π½Π΅ классов
  5. singleton_methods ΠΏΠΎΠΊΠ°ΠΆΡƒΡ‚ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ ΠΊΠΎΠ½ΠΊΡ€Π΅Ρ‚Π½ΠΎΠ³ΠΎ экзСмпляра

Π˜Π½Ρ‚Ρ€ΠΎΡΠΏΠ΅ΠΊΡ†ΠΈΡ ΠΌΠ΅Ρ‚ΠΎΠ΄ΠΎΠ² β€” это ΠΊΠ°ΠΊ Ρ€Π΅Π½Ρ‚Π³Π΅Π½ для Π²Π°ΡˆΠΈΡ… ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ²: позволяСт Π·Π°Π³Π»ΡΠ½ΡƒΡ‚ΡŒ Π²Π½ΡƒΡ‚Ρ€ΡŒ ΠΈ ΠΏΠΎΠ½ΡΡ‚ΡŒ ΠΈΡ… структуру, Π½Π΅ разбирая Π½Π° части.

πŸ—“ Π”Π°Ρ‚Π° ΠΏΡƒΠ±Π»ΠΈΠΊΠ°Ρ†ΠΈΠΈ: 23.04.2025, Π½ΠΎ это Π½Π΅ Ρ‚ΠΎΡ‡Π½ΠΎ...

Ruby ΠΌΠ΅Ρ‚Π°ΠΏΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅ рСфлСксия ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π½Π°Ρ-модСль