Доступ к пространству имен, содержащему класс, из модуля
Я работаю над модулем, который, помимо прочего, добавит некоторые общие функциональные возможности типа "искатель" к классу, в который вы его смешиваете. Проблема: по соображениям удобства и эстетики я хочу включить некоторые функциональные возможности вне класса в ту же область, что и сам класс.
Например:
class User
include MyMagicMixin
end
# Should automagically enable:
User.name('Bob') # Returns first user named Bob
Users.name('Bob') # Returns ALL users named Bob
User(5) # Returns the user with an ID of 5
Users # Returns all users
Я могу сделать функциональность в этих методах, без проблем. И случай 1 (User.name('Bob')
) это просто. Однако случаи 2–4 требуют возможности создавать новые классы и методы вне User
, Module.included
Метод дает мне доступ к классу, но не к его содержанию. Не существует простого метода "родительского" типа, который я могу увидеть ни в классе, ни в модуле. (Для пространства имен я имею в виду не суперкласс и не вложенные модули.)
Лучший способ, которым я могу думать, это сделать парсинг строк в классе #name
разбить его пространство имен, а затем превратить строку обратно в константу. Но это кажется неуклюжим, и, учитывая, что это Руби, я чувствую, что должен быть более элегантный способ.
У кого-нибудь есть идеи? Или я просто слишком умен для своего блага?
3 ответа
В вашем примере User
это просто константа, которая указывает на Class
объект. Вы можете легко создать еще один постоянный указатель, когда MyMagicMixin
Включено:
module MyMagicMixin
class <<self
def self.included(klass)
base.extend MyMagicMixin::ClassMethods
create_pluralized_alias(klass)
end
private
def create_pluralized_alias(klass)
fq_name = klass.to_s
class_name = fq_name.demodulize
including_module = fq_name.sub(Regexp.new("::#{class_name}$", ''))
including_module = including_module.blank? ? Object : including_module.constantize
including_module.const_set class_name.pluralize, klass
end
end
module ClassMethods
# cass methods here
end
end
Конечно, это не ответит, следует ли вам делать такие вещи.
Я бы склонялся к тому, чтобы быть слишком умным.
Даже если бы было элегантное решение, кажется довольно странным включать модуль внутри класса, который создает классы вне класса.
Эта проблема иногда возникает в списках рассылки. Это также проблема, которая возникает в Rails. Решение, как вы уже подозревали, в основном состоит в использовании Regexp.
Однако есть более фундаментальная проблема: в Ruby классы не имеют имени! Класс - это просто объект, как и любой другой. Вы можете назначить его переменной экземпляра, локальной переменной, глобальной переменной, константе или даже вообще не назначать ее чему-либо. Module#name
Метод в основном просто вспомогательный метод, который работает следующим образом: он просматривает список определенных констант, пока не найдет тот, который указывает на получатель. Если он находит один, он возвращает первый, который он может найти, в противном случае он возвращает nil
,
Итак, здесь есть два режима отказа:
a = Class.new
a.name # => nil
B = a
B.name # => "B"
A = B
A.name # => "B"
- класс может вообще не иметь имени
- класс может иметь более одного имени, но
Module#name
вернет только первый найденный
Теперь, если кто-то пытается позвонить As
чтобы получить список A
s, они будут очень удивлены, обнаружив, что этот метод не существует, но что они могут вызвать Bs
вместо этого, чтобы получить тот же результат.
Это действительно происходит на самом деле. В MacRuby, например String.name
возвращается NSMutableString
, Hash.name
возвращается NSMutableDictionary
а также Object.name
возвращается NSObject
, Причина этого заключается в том, что MacRuby объединяет среду выполнения Ruby и среду Objective-C в одну, и поскольку семантика изменяемой строки Objective-C идентична строке Ruby, вся реализация строкового класса Ruby по существу представляет собой одну строку: String = NSMutableString
, А поскольку MacRuby находится поверх Objective-C, это означает, что Objective-C запускается первым, что означает, что NSMutableString
вставляется в таблицу символов первым, что означает, что он будет найден первым Module#name
,