Как исправить `Style/ClassVars` в RuboCop?

Я вижу проблему с использованием переменных класса в Ruby; однако кажется, что документации от RuboCop для решения проблемы недостаточно.

Теперь я мог просто игнорировать это. Учитывая мой проект, это не имеет значения. Но я просто хочу знать, что Рубокоп пытается мне сказать, потому что это не имеет смысла.

Выполнение предоставленного кода в irb 0.9.6 с Ruby 2.5.1 дает:

class A
  @test = 10
end
#=> 10
class A
  def test
    @@test # you can access class variable without offense
  end
end
#=> :test
A.new.test
Traceback (most recent call last):
        3: from /Users/Ricky/.rbenv/versions/2.5.1/bin/irb:11:in `<main>'
        2: from (irb):12
        1: from (irb):9:in `test'
NameError (uninitialized class variable @@test in A)
Did you mean?  @test

Итак, нет. Мы, очевидно, не можем получить доступ к переменной класса без обид. irb был очень оскорблен. Но ruby ​​предлагает использовать @test. Может, это была просто опечатка? Давай попробуем:

class A
  @test = 10
  def test
    @test # you can access class variable without offense
  end
end
#=> :test
A.new.test
#=> nil

Таким образом, переменная экземпляра никогда не была определена. Что здесь пытается сказать Рубокоп?


person RWDJ    schedule 31.12.2018    source источник


Ответы (1)


Вам не хватает разницы между областями действия переменных.

class A
  @test = 42
end

Вышеприведенное объявляет переменную экземпляра в области класса. Он доступен как

A.instance_variable_get(:@test)
#⇒ 42

Вы можете определить метод доступа для этой переменной:

class A
  @test = 42
  def self.test
    @test
  end
end

A.test #⇒ 42

Он разделяется между экземплярами, и для доступа к нему из экземпляров вы должны обратиться к классу:

#     ⇓⇓⇓⇓⇓ HERE
A.new.class.test #⇒ 42

Следующий код объявляет переменную экземпляра в экземплярах класса:

class A
  def initialize
    @test = 42
  end
end

Доступ к нему можно получить из экземпляров A:

A.new.instance_variable_get(:@test)
#⇒ 42

Переменные класса имеют некоторые недостатки при использовании внутри иерархии классов, поэтому [вероятно] поэтому Rubocop предлагает не использовать переменные класса (или что он предлагает — я, честно говоря, никогда не использовал это, так как это приносит больше вреда, чем помощи ИМСО.)

В первом фрагменте вы пропустили @. Правильный код будет:

class A
# ⇓⇓ HERE
  @@test = 10
end
class A
  def test
    @@test # you can access class variable without offense
  end
end
person Aleksei Matiushkin    schedule 31.12.2018
comment
Одна из причин, по которой Rubocop говорит не использовать переменные класса (с двумя знаками at, для ясности других читателей), заключается в том, что переменные класса передаются по ссылке при наследовании классов. Это означает, что класс может изменить переменную другого класса, и это, вероятно, не очень очевидно или преднамеренно. - person Nate; 31.12.2018
comment
@Nate хорошо, это желаемое поведение для переменных класса, в отличие от переменных экземпляра на уровне класса, поэтому Rubocop лучше заткнуться (или объяснить намерение, чтобы людям не нужно было публиковать вопросы, подобные приведенным выше, на SO.) - person Aleksei Matiushkin; 31.12.2018
comment
Переменные класса @Nate прекрасно подходят для сбора некоторой информации о базовом классе и обеспечения ее доступности из всей ветви дерева иерархии. Например, ActiveRecord::Base может иметь все экземпляры, прямые и косвенные, сохраненные в переменной класса. Именно для этого были придуманы переменные класса. - person Aleksei Matiushkin; 31.12.2018
comment
@ Нейт прав в отношении намерений из их документации, но Алексей прав во всем остальном. Для RuboCop неразумно вытеснять переменные класса и предоставлять ложное исправление. - person RWDJ; 31.12.2018
comment
@AlekseiMatiushkin, я не пропустил @. Это точный код, предоставленный RuboCop для исправления correction:Style/ClassVars в предоставленной документации. Я знал, что второй фрагмент тоже не сработает, но я добавил его для полноты картины, так как предложил Руби. Ваш ответ в основном отражает то, что я думаю, поэтому я вернусь через день или около того и приму ваш ответ, если никто не выяснит, есть ли у RuboCop опечатка в их документации или они просто ошибаются. Но, хорошее понимание метода класса. - person RWDJ; 31.12.2018
comment
@RWDJ, если вам небезразличны документы Rubocop, создайте проблему в их репозитории; это имеет больше смысла, чем сидеть здесь и ждать диких догадок незнакомцев :) - person Aleksei Matiushkin; 31.12.2018
comment
@AlekseiMatiushkin, верно. На самом деле я сделал это изначально и был направлен на SO, но я только что разместил тикет в их репозитории руководства по стилю. В любом случае, я все равно хотел бы посмотреть, что люди придумают для ее решения, поскольку в документах определенно этого нет. Ваше предложение создать метод класса для доступа к переменной экземпляра класса работает нормально, и это разумно, хотя я бы все равно никогда этого не сделал, если бы меня не заставили, поэтому я приму ваш ответ. - person RWDJ; 31.12.2018
comment
Как бы то ни было, меня годами учили не использовать переменные @@. Если они у вас есть, по-прежнему рекомендуется избавиться от них, хотя, честно говоря, у меня нет веских причин для этого, кроме как есть другие не столь хрупкие способы хранения переменных. - person Nate; 31.12.2018
comment
@Nate еще раз, нет лучшего способа хранить что-либо совместно используемое между подклассами. - person Aleksei Matiushkin; 31.12.2018
comment
Конечно есть. Используйте переменную экземпляра уровня класса, как предлагает полицейский. Это распространяется на все подклассы, но не оставляет возможности дочернему классу случайно изменить родительскую переменную. Если вам нужно, чтобы экземпляры имели доступ к вашей переменной экземпляра класса, вам, вероятно, все равно следует использовать метод чтения. Если вы используете Rails, вы также можете проверить class_attribute, в котором есть много функций наследования, которых слишком много для этого комментария. - person Nate; 31.12.2018
comment
Я рекомендую этот вопрос и ответ для понимания того, что предлагают копы: stackoverflow.com/questions/15773552/ - person Foton; 02.03.2021
comment
Итак, @Nate, каков рекомендуемый способ, если я ДЕЙСТВИТЕЛЬНО хочу, чтобы дочерний класс изменил родительскую переменную, в частности шаблон регистрации, например ActiveRecord::Base? Выключить копа? - person Honza; 10.05.2021