Доступ к пространству имен, содержащему класс, из модуля - PullRequest
3 голосов
/ 20 марта 2010

Я работаю над модулем, который, помимо прочего, добавит некоторую универсальную функциональность типа 'искатель' к классу, в который вы его смешиваете. Проблема: по соображениям удобства и эстетики я хочу включить некоторые функции вне класса, в ту же область, что и сам класс.

Например:

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 класса, чтобы разбить его пространство имен, а затем превратить строку обратно в константу. Но это кажется неуклюжим, и, учитывая, что это Ruby, я чувствую, что должен быть более элегантный способ.

У кого-нибудь есть идеи? Или я просто слишком умен для своего блага?

Ответы [ 3 ]

3 голосов
/ 20 марта 2010

Я бы склонялся к тому, чтобы быть слишком умным.

Даже если бы существовало элегантное решение, кажется довольно странным включать модуль внутри класса, который создает классы вне класса.

2 голосов
/ 20 марта 2010

Эта проблема иногда возникает в списках рассылки. Это также проблема, которая возникает в 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 с, он будет очень удивлен, обнаружив, что этот метод не существует, но вместо этого он может вызвать 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 вставляется в таблицу символов первым, что означает, что сначала он получает found на Module#name .

1 голос
/ 20 марта 2010

В вашем примере 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

Конечно, это не отвечает на вопрос, должны ли вы делать такие вещи.

...