Рефлексия (reflection) — это способность программы исследовать и модифицировать свою собственную структуру и поведение во время выполнения. Ruby как динамический язык предоставляет богатые возможности для рефлексии.
class User
attr_accessor :name, :email
def initialize(name, email)
@name = name
@email = email
end
def send_email
# отправка email
end
end
# Получение информации о классе
User.class # => Class
User.methods # список методов класса
User.instance_methods # список методов экземпляра
User.ancestors # цепочка наследования
# Динамическое создание метода
class User
define_method :greet do |name|
"Hello, #{name}!"
end
end
user = User.new
user.greet("Alice") # => "Hello, Alice!"
# Динамический вызов метода
method_name = :send_email
user.send(method_name) # вызовет метод send_email
user = User.new("Bob", "bob@example.com")
# Получение списка переменных экземпляра
user.instance_variables # => [:@name, :@email]
# Чтение/запись переменных
user.instance_variable_get(:@name) # => "Bob"
user.instance_variable_set(:@name, "Robert")
class Repository
def self.method_missing(method, *args)
if method.to_s.start_with?('find_by_')
attribute = method.to_s[8..-1]
->(value) { all.find { |item| item.send(attribute) == value } }
else
super
end
end
end
class ObjectSerializer
def serialize(obj)
obj.instance_variables.each_with_object({}) do |var, hash|
hash[var.to_s.delete('@')] = obj.instance_variable_get(var)
end
end
end
module Loggable
def self.included(base)
base.instance_methods(false).each do |method|
base.send(:define_method, "#{method}_with_logging") do |*args|
puts "Calling #{method} with #{args.inspect}"
send("#{method}_without_logging", *args)
end
base.alias_method "#{method}_without_logging", method
base.alias_method method, "#{method}_with_logging"
end
end
end
Производительность:
send
на ```40% медленнее прямого вызоваБезопасность:
send
/public_send
Читаемость кода:
obj = Object.new
# Добавление метода только к конкретному экземпляру
class << obj
def special_method
"I'm special!"
end
end
С использованием RubyVM::AbstractSyntaxTree
:
ast = RubyVM::AbstractSyntaxTree.parse(<<```RUBY)
def hello(name)
puts "Hello, \#{name}!"
end
RUBY
ast.children # анализ структуры метода
module Trace
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def method_added(method)
puts "Method #{method} added to #{self}"
end
end
end
Рефлексия в Ruby позволяет:
Основные методы:
send
/public_send
- динамический вызов методовdefine_method
- создание методов на летуinstance_variable_get/set
- работа с переменнымиmethod_missing
- обработка "потерянных" методовИспользуйте рефлексию:
Избегайте: