Вложенные классы против компактных в Ruby

#ruby-on-rails #ruby #rubocop

#ruby-on-rails #ruby #rubocop

Вопрос:

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

Ответ №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 from within MembersController , то это может иметь значение для вас. Тонкий, но, возможно, важный, и о нем полезно знать. Я столкнулся с этим в реальной жизни, когда у меня был Util модуль с String подмодулем. Я переместил метод в Util , и он сломался, потому String что внутри этого метода теперь упоминается Util::String . После этого я изменил некоторые соглашения об именах.

Ваш Tenant модуль всегда будет иметь MembersController вложенный класс. Вы можете ссылаться на любое другое место в вашей кодовой AppCore::Tenant::MembersController базе. Если вы хотите лучшего разделения, вам следует назвать свои классы моделей по-другому или поместить их в модуль, такой как AppCore::Model или аналогичный. Если вы используете Rails, вам придется отказаться от некоторых соглашений, но конфигурация, необходимая для этого, не так уж плоха.

Комментарии:

1. Мне нравится идея поместить мои модели в модуль модели (это полный рот). Хороший совет.

2. OTOH компактная форма не создает родительский модуль / класс, в то время как вложенная форма создает, что также может привести к труднодоступным ошибкам.

Ответ №2:

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

Есть ли какая-то конкретная причина, почему вы этого хотите…

  1. … ввести иерархию, подобную «пути»?
  2. … поместить контроллер внутри класса модели?

Если бы я почувствовал необходимость в 1), у меня, вероятно, были бы простые «контейнерные» модули, повторяющие реальные пути. То есть app/model/tenant.rb => Model::Tenant и app/controller/members_controller.rb => Controller::MembersController .

Но, честно говоря, я действительно не вижу причин, стоящих за этим. Контроллеры уже легко распознаются XyzController соглашением. Модели (в большинстве случаев, я думаю) довольно легко распознаются по их доменному характеру. Поскольку ruby не требует и даже не предлагает сопоставлять имена путей с именами классов (в отличие, например, от Java), для меня было бы более полезным четкое соглашение об именовании 1 уровня.

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

2) (контроллер внутри модели) в корне неверен. Контроллеры очень сильно отличаются от моделей и, конечно, не живут внутри одного.

Ответ №3:

Если вы используете nested и хотите вернуться к nampspace верхнего уровня, вы можете использовать :: .

 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