Динамический вызов методов — одна из ключевых возможностей Ruby, которая делает язык таким гибким и выразительным. Рассмотрим механизмы и особенности этой функциональности.
Базовый способ вызова метода по имени:
class User
def greet(name)
"Hello, #{name}!"
end
end
user = User.new
method_name = :greet
user.send(method_name, "Alice") # => "Hello, Alice!"
Особенности:
user.public_send(:greet, "Bob") # работает только с public методами
user.public_send(:initialize, "Eve") # => NoMethodError (private method)
Получение объекта Method и его последующий вызов:
greeter = user.method(:greet)
greeter.call("Charlie") # => "Hello, Charlie!"
Преимущества:
to_proc
class DynamicCalculator
OPERATIONS = %i[add subtract multiply divide]
OPERATIONS.each do |op|
define_method(op) do |a, b|
a.send(op, b)
end
end
end
calc = DynamicCalculator.new
calc.add(5, 3) # => 8
class SmartHash
def initialize(hash)
@hash = hash
end
def method_missing(name, *)
@hash[name.to_s] || @hash[name] || super
end
end
data = SmartHash.new("name" => "John", age: 30)
data.name # => "John"
data.age # => 30
def dynamic_with_block(method_name, &block)
define_method(method_name) do |*args|
block.call(*args)
end
end
dynamic_with_block(:say_hi) { |name| "Hi, #{name}" }
say_hi("Dave") # => "Hi, Dave"
Поиск метода:
Кэширование методов:
send
использует тот же механизм кэшированияВыполнение:
Варианты вызовов по скорости (от быстрого к медленному):
obj.method
)send
с существующим методомpublic_send
method_missing
method(...).call
require 'benchmark'
Benchmark.bm do |x|
x.report("direct") { 1_000_000.times { "test".upcase } }
x.report("send") { 1_000_000.times { "test".send(:upcase) } }
x.report("public_send") { 1_000_000.times { "test".public_send(:upcase) } }
end
Проблемы:
Защита:
public_send
вместо send
# Безопасный подход
SAFE_METHODS = %w[upcase downcase capitalize]
def safe_dynamic_call(str, method_name)
if SAFE_METHODS.include?(method_name)
str.send(method_name)
else
raise "Unsafe method attempted"
end
end
class ApiAdapter
def call(endpoint, params = {})
method = "call_#{endpoint}"
respond_to?(method) ? send(method, params) : call_default(params)
end
end
class Delegator
def initialize(target)
@target = target
end
def method_missing(name, *args, &block)
@target.respond_to?(name) ? @target.send(name, *args, &block) : super
end
end
class Configuration
def self.define_setters(*attrs)
attrs.each do |attr|
define_method("#{attr}=") { |value| instance_variable_set("@#{attr}", value) }
end
end
end
Динамический вызов методов в Ruby:
send
, public_send
и method_missing
Ключевые правила:
public_send
вместо send
когда возможноДинамические методы — мощный инструмент, который делает Ruby уникальным, но требует ответственного использования.