Неоднозначность в разрешении имен классов в Ruby - PullRequest
0 голосов
/ 18 января 2019

Недавно я провел некоторый рефакторинг кода в кодовой базе Rails, в котором я преобразовал некоторый код, который определил некоторые классы следующим образом (тела классов исключены):

foo/user_bar.rb * * 1004

module Foo
  class UserBar; end
end

foo/sub_user_bar.rb

module Foo
  class SubUserBar < UserBar; end
end

... для:

В foo/bar/user.rb:

module Foo
  module Bar
    class User; end
  end
end

В foo/bar/sub_user.rb:

module Foo
  module Bar
    class SubUser < User; end
  end
end

Это внесло небольшую проблему в то, что уже существовал класс верхнего уровня с именем User (модель Rails), и из него был получен SubUser, а не User в том же модуле. Я думаю, это потому, что порядок загрузки классов в Rails непредсказуем ...?

В любом случае, я обнаружил, что могу устранить неоднозначность, объявив класс SubUser следующим образом:

class SubUser < self::User

... что, похоже, работает, хотя я не смог найти ни одного примера в скромном поиске. Хотя это выглядит немного странно. И теперь я обеспокоен тем, что мне действительно следует делать это для каждого класса в моем модуле, на случай, если когда-нибудь будет введен класс верхнего уровня с тем же именем.

В кодовой базе моей компании есть два стиля объявлений классов в модуле:

class A::B::Foo; end

И

module A; module B; class Foo; end; end; end

Я всегда предпочитал последний метод, так как он разрешает доступ к другим именам в модуле без полностью определенных имен, но теперь я обнаружил, что при его использовании потенциально возможна ловушка. Так какая же здесь лучшая практика?

  • Не объявлять классы в пределах module; всегда используйте полные имена (class A::B::C::Foo < A::B::C::Bar)
  • Объявлять классы в module, но всегда полностью указывать имена суперклассов (module A; module B; module C; class Foo < A::B::C::Bar)
  • Объявите классы в module и используйте неквалифицированные имена, но всегда используйте self:: при расширении класса в том же модуле
  • Объявляйте классы в module и используйте неквалифицированные имена, но помните о возможных конфликтах имен в пространстве имен верхнего уровня (кажется рискованным)

Или Ruby и / или Rails предлагают какой-то другой, более чистый способ решения проблемы?

Ответы [ 2 ]

0 голосов
/ 19 января 2019

Модули идентифицируются константами. 1 Таким образом, разрешение поиска модулей определяется процедурой поиска константных ссылок. По словам "Ruby Programming Language", Дэвид Фланаган и Юкихеро Мацумото, 1-е изд. 2008 (стр. 261-262), поиск констант выполняется в следующем порядке, где mod - самый внутренний модуль, который содержит ссылку на константу:

  • mod.
  • следующий включающий модуль, продолжающийся до тех пор, пока не будет больше вложенных модулей. (Верхние / глобальные константы на этом этапе не рассматриваются). Метод класса Module :: nesting возвращает массив модулей, в которых выполняется такой поиск, в порядке их поиска.
  • элементы mod.ancestors в порядке индекса (то есть иерархия наследования).
  • определения констант верхнего уровня.
  • значение, возвращаемое методом Module # const_missing , где mod - получатель, а константа, выраженная в виде символа - аргумент, при условии, что метод был определен.

Посмотрим, что мы можем узнать, вставив в код операторы puts Module.nesting.inspect и puts ancestors.

module Foo
  class UserBar
    puts "nesting for UserBar=#{Module.nesting.inspect}"
    puts "#{self}.ancestors=#{ancestors}"
  end
end
  # nesting for UserBar=[Foo::UserBar, Foo]
  # Foo::UserBar.ancestors=[Foo::UserBar, Object, Kernel,
  #   BasicObject]

module Foo
  class SubUserBar < UserBar
    puts "nesting for SubUserBar=#{Module.nesting.inspect}"
    puts "#{self}.ancestors=#{ancestors}"
  end
end
  # nesting for SubUserBar=[Foo::SubUserBar, Foo]
  # Foo::SubUserBar.ancestors=[Foo::SubUserBar,
  #   Foo::UserBar, Object, Kernel, BasicObject]

module Foo
  module Bar
    class User
      puts "nesting for User=#{Module.nesting.inspect}"
      puts "#{self}.ancestors=#{ancestors}"
    end
  end
end
  # nesting for User=[Foo::Bar::User, Foo::Bar, Foo]
  # Foo::Bar::User.ancestors=[Foo::Bar::User, Object, Kernel,
  #   BasicObject]

module Foo
  module Bar
    class SubUser < User
      puts "nesting for SubBar=#{Module.nesting.inspect}"
      puts "#{self}.ancestors=#{ancestors}"
    end
  end
end
  # nesting for SubBar=[Foo::Bar::SubUser, Foo::Bar, Foo]
  # Foo::Bar::SubUser.ancestors=[Foo::Bar::SubUser,
  #   Foo::Bar::User, Object, Kernel, BasicObject]

Это говорит нам о том, что если существует класс User, который не содержится в Foo (как в Rails), этот класс будет объектом ссылок на User в классах Foo::UserBar и Foo::SubUserBar , но не в Foo::Bar::User или Foo::Bar::SubUser. Это понимание должно помочь в организации модулей, чтобы избежать проблем с пространством имен.

1 Так как классы являются модулями, всякий раз, когда я ссылаюсь на «модуль» ниже, я имею в виду классы, а также модули, которые не являются классами.

0 голосов
/ 18 января 2019

Как правило, при наличии неоднозначности вы указываете полные пути к пространствам имен:

module Foo
  module Bar
    class SubUser < Foo::Bar::User; end
  end
end

Это может показаться многословным, но оно также конкретное и однозначное.

Где вы хотите обратиться к буквальному верхнему уровню User, вы бы указали ::User.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...