Вложенные классы по сравнению с компактными в Ruby
Работа над исходным проектом Rails и использование Rubocop для анализа стиля кода. Это привело меня к вопросу о том, как работают вложенные классы Ruby в контексте Rails. Например, в моем движке у меня есть модель:
# app/models/app_core/tenant.rb
module AppCore
class Tenant < ActiveRecord::Base
end
end
и контроллер:
# app/controllers/app_core/tenant/members_controller.rb
module AppCore
class Tenant::MembersController < ApplicationController
end
end
В модельном случае модуль такой же, как и путь, а имя класса совпадает с именем файла. В случае контроллеров вторая часть пути "арендатор" является частью имени класса.
Rubocop сообщает мне, что я должен "использовать вложенные определения классов вместо компактного стиля" в строке Tenant::MembersController
, поэтому, если я правильно понял...
module AppCore
class Tenant
class MembersController < ApplicationController
end
end
end
... это не должно меняться.
Теперь, мой вопрос: у меня есть AppCore:: Tenant как модель, но затем AppCore:: Tenant выглядит вновь открывшимся, и класс MembersController добавляется к нему как вложенный класс. Означает ли это, что мой класс Tenant всегда будет иметь этот вложенный класс? Нужно ли мне называть мои модели и маршруты контроллеров чем-то по-другому? Это все в порядке и не о чем беспокоиться? Не совсем уверен, что это значит.
Ответы
Ответ 1
Одно тонкое различие заключается в том, что ваш объем отличается, и это может привести к ошибкам. В первом случае константы будут искать в AppCore
, тогда как во втором случае константы будут искать в AppCore::Tenant
. Если вы полностью квалифицируете постоянные имена, это не имеет значения.
Foo = :problem
module A
Foo = 42
# looks up A::Foo because of lexical scope
module B
def self.foo
Foo
end
end
end
# looks up ::Foo because of lexical scope
module A::C
def self.foo
Foo
end
end
# Looks up A::Foo, fully qualified ... ok technically ::A::Foo is fully qualified, but meh.
module A::D
def self.foo
A::Foo
end
end
A::B.foo # => 42
A::C.foo # => :problem
A::D.foo # => 42
Если вы имеете в виду константы, определенные в AppCore::Tenant
из MembersController
, то это может повлиять на вас. Тонкий, но, возможно, важный, и хорошо знать. Я ударил это в реальной жизни, когда у меня был модуль Util
с подмодулем String
. Я переместил метод в Util
, и он сломался, потому что String
внутри этого метода теперь ссылается на Util::String
. После этого я изменил некоторые соглашения об именах.
В вашем модуле Tenant
всегда будет MembersController
как вложенный класс. В любом месте в вашей кодовой базе вы можете обратиться к AppCore::Tenant::MembersController
. Если вы хотите улучшить разделение, вы должны назвать свои классы моделей по-разному или поместить их в модуль, например AppCore::Model
или аналогичный. Если вы используете Rails, вам придется использовать несколько условных обозначений, но для этого требуется нестандартная конфигурация.
Ответ 2
Я знаю, что вы спрашиваете о технических особенностях, и Сами ответил на это. Но я не могу помочь себе и должен спросить:
Есть ли какая-то особая причина, в первую очередь, почему вы хотите...
- ... ввести "путь" как иерархия?
- ... поместите контроллер внутри класса модели?
Если бы я почувствовал необходимость в 1), у меня, вероятно, были бы простые "контейнерные" модули, отражающие реальные пути. То есть app/model/tenant.rb
= > Model::Tenant
и app/controller/members_controller.rb
= > Controller::MembersController
.
Но, честно говоря, я действительно не вижу причин для этого. Контроллеры уже легко распознаются соглашением XyzController
. Модели (чаще всего, я думаю) довольно легко распознаются их доменной природой. Поскольку ruby не требует или даже не предлагает сопоставить имена пути с именами классов (например, в отличие от Java), ясное соглашение об именовании с 1 уровнем будет более полезным для меня.
Иерархии подмодулей/подклассов очень полезны или, скорее, необходимы для драгоценных камней, где они функционируют как пространства имен, чтобы избежать столкновений.
2) (Контроллер внутри модели) в корне неверен. Контроллеры очень сильно отличаются от моделей и, конечно же, не живут внутри одного.
Ответ 3
Если вы используете вложенный и хотите вернуться в верхнее пространство верхнего уровня, вы можете использовать ::
.
def class user < ActiveRecord::Base
NAME = "Real User"
end
module SomeModule
def class User
Name = "Fake User"
end
module InnerModule
class MyClass
puts User.NAME # "Fake User"
puts ::User.Name # "Real User"
end
end
end