Atribuição dinâmica constante

139
class MyClass
  def mymethod
    MYCONSTANT = "blah"
  end
end

me dá o erro:

SyntaxError: erro dinâmico de atribuição constante

Por que isso é considerado uma constante dinâmica? Estou apenas atribuindo uma string a ele.

o espelho
fonte
34
Constante dinâmica é algo como água seca? :)
fl00r
39
Não diz que a constante é dinâmica. Diz que a tarefa é dinâmica.
sepp2k

Respostas:

141

Seu problema é que, toda vez que você executa o método, está atribuindo um novo valor à constante. Isso não é permitido, pois torna a constante não constante; mesmo que o conteúdo da string seja o mesmo (no momento, de qualquer maneira), o próprio objeto da string é diferente sempre que o método é chamado. Por exemplo:

def foo
  p "bar".object_id
end

foo #=> 15779172
foo #=> 15779112

Talvez se você explicou seu caso de uso - por que deseja alterar o valor de uma constante em um método - poderíamos ajudá-lo com uma melhor implementação.

Talvez você prefira ter uma variável de instância na classe?

class MyClass
  class << self
    attr_accessor :my_constant
  end
  def my_method
    self.class.my_constant = "blah"
  end
end

p MyClass.my_constant #=> nil
MyClass.new.my_method

p MyClass.my_constant #=> "blah"

Se você realmente deseja alterar o valor de uma constante em um método, e sua constante é uma String ou uma Matriz, você pode 'trapacear' e usar o #replacemétodo para fazer com que o objeto adquira um novo valor sem realmente alterar o objeto:

class MyClass
  BAR = "blah"

  def cheat(new_bar)
    BAR.replace new_bar
  end
end

p MyClass::BAR           #=> "blah"
MyClass.new.cheat "whee"
p MyClass::BAR           #=> "whee"
Phrogz
fonte
19
O OP nunca disse que queria alterar o valor da constante, mas apenas queria atribuir um valor. O caso de uso frequente que leva a esse erro Ruby é quando você cria o valor em um método a partir de outros ativos em tempo de execução (variáveis, argumentos de linha de comando, ENV), geralmente em um construtor, por exemplo def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end. É um daqueles casos em que Ruby não tem um caminho simples.
Arnaud Meuret 6/03/2013
2
@ArnaudMeuret Nesse caso, você deseja uma variável de instância (por exemplo @variable), não uma constante. Caso contrário, você seria redesignado DBtoda vez que instanciasse uma nova instância dessa classe.
precisa saber é o seguinte
2
@ Ajedi32 Esta situação geralmente surge de restrições externas e não de escolhas de design, como no meu exemplo com Sequel. Meu argumento é que atribuir um valor a uma constante é permitido por Ruby em certos escopos e não em outros. Costumava ser do desenvolvedor escolher sabiamente quando executar a tarefa. Ruby mudou nisso. Não é para todo mundo.
Arnaud Meuret 06/12/2013
2
@ArnaudMeuret Admito que nunca usei Sequel antes, então não posso dizer isso com 100% de certeza, mas apenas olhando a documentação para Sequel não vejo nada que diga que você DEVE atribuir o resultado Sequel.connecta uma constante chamada DB . De fato, a documentação diz explicitamente que isso é apenas uma recomendação. Isso não parece uma restrição externa para mim.
Ajedi32
@ Ajedi32 1) Eu nunca escrevi que (nome da constante ou mesmo que você precisava mantê-la em algum lugar) é apenas um exemplo 2) A restrição é que seu software pode não ter as informações necessárias até que você esteja em um contexto dinâmico .
Arnaud Meuret
69

Como as constantes no Ruby não devem ser alteradas, o Ruby o desencoraja de atribuir a elas partes do código que podem ser executadas mais de uma vez, como métodos internos.

Sob circunstâncias normais, você deve definir a constante dentro da própria classe:

class MyClass
  MY_CONSTANT = "foo"
end

MyClass::MY_CONSTANT #=> "foo"

Se, por algum motivo, você realmente precisar definir uma constante dentro de um método (talvez para algum tipo de metaprogramação), poderá usar const_set:

class MyClass
  def my_method
    self.class.const_set(:MY_CONSTANT, "foo")
  end
end

MyClass::MY_CONSTANT
#=> NameError: uninitialized constant MyClass::MY_CONSTANT

MyClass.new.my_method
MyClass::MY_CONSTANT #=> "foo"

Novamente, porém, const_setnão é algo que você realmente precise recorrer em circunstâncias normais. Se você não tiver certeza se deseja realmente atribuir as constantes dessa maneira, considere uma das seguintes alternativas:

Variáveis ​​de classe

Variáveis ​​de classe se comportam como constantes de várias maneiras. São propriedades em uma classe e são acessíveis nas subclasses da classe em que estão definidas.

A diferença é que as variáveis ​​de classe devem ser modificáveis ​​e, portanto, podem ser atribuídas a métodos internos sem problemas.

class MyClass
  def self.my_class_variable
    @@my_class_variable
  end
  def my_method
    @@my_class_variable = "foo"
  end
end
class SubClass < MyClass
end

MyClass.my_class_variable
#=> NameError: uninitialized class variable @@my_class_variable in MyClass
SubClass.my_class_variable
#=> NameError: uninitialized class variable @@my_class_variable in MyClass

MyClass.new.my_method
MyClass.my_class_variable #=> "foo"
SubClass.my_class_variable #=> "foo"

Atributos de classe

Atributos de classe são uma espécie de "variável de instância em uma classe". Eles se comportam um pouco como variáveis ​​de classe, exceto que seus valores não são compartilhados com subclasses.

class MyClass
  class << self
    attr_accessor :my_class_attribute
  end
  def my_method
    self.class.my_class_attribute = "blah"
  end
end
class SubClass < MyClass
end

MyClass.my_class_attribute #=> nil
SubClass.my_class_attribute #=> nil

MyClass.new.my_method
MyClass.my_class_attribute #=> "blah"
SubClass.my_class_attribute #=> nil

SubClass.new.my_method
SubClass.my_class_attribute #=> "blah"

Variáveis ​​de instância

E, para completar, eu provavelmente devo mencionar: se você precisar atribuir um valor que só pode ser determinado após a instanciação da sua classe, há uma boa chance de você estar realmente procurando por uma variável de instância antiga simples.

class MyClass
  attr_accessor :instance_variable
  def my_method
    @instance_variable = "blah"
  end
end

my_object = MyClass.new
my_object.instance_variable #=> nil
my_object.my_method
my_object.instance_variable #=> "blah"

MyClass.new.instance_variable #=> nil
Ajedi32
fonte
33

No Ruby, qualquer variável cujo nome comece com uma letra maiúscula é uma constante e você pode atribuir a ela apenas uma vez. Escolha uma destas alternativas:

class MyClass
  MYCONSTANT = "blah"

  def mymethod
    MYCONSTANT
  end
end

class MyClass
  def mymethod
    my_constant = "blah"
  end
end
David Grayson
fonte
2
Graças a Deus alguém mencionou que "qualquer variável cujo nome comece com uma letra maiúscula é uma constante!"
ubienewbie
0

Você não pode nomear uma variável com letras maiúsculas ou o Ruby assumirá sua constante e desejará que ela mantenha seu valor constante; nesse caso, alterar seu valor seria um erro "erro de atribuição dinâmica constante". Com minúsculas deve ficar bem

class MyClass
  def mymethod
    myconstant = "blah"
  end
end
Jose Paez
fonte
0

Ruby não gosta que você esteja atribuindo a constante dentro de um método porque corre o risco de ser redesignada. Várias respostas do SO antes de mim oferecem a alternativa de atribuí-lo fora de um método - mas na classe, que é um lugar melhor para atribuí-lo.

John
fonte
1
Bem-vindo a SO John. Você pode melhorar essa resposta adicionando um código de amostra do que está descrevendo.
Cleptus
0

Muito obrigado a Dorian e Phrogz por me lembrar sobre o método array (e hash) #replace, que pode "substituir o conteúdo de um array ou hash".

A noção de que o valor de um CONSTANT pode ser alterado, mas com um aviso irritante, é um dos poucos erros conceituais do Ruby - eles devem ser totalmente imutáveis ​​ou despejar completamente a idéia constante. Do ponto de vista de um codificador, uma constante é declarativa e intencional, um sinal para outros de que "esse valor é realmente imutável uma vez declarado / atribuído".

Mas, às vezes, uma "declaração óbvia" na verdade exclui outras oportunidades úteis futuras. Por exemplo...

Não são casos de uso legítimos onde o valor de um "constante" pode realmente precisam ser alterado: por exemplo, re-loading ARGV partir de uma linha de circuito REPL-like, em seguida, executar novamente ARGV através de mais (subsequente) OptionParser.parse! chama - voila! Dá à "linha de comando args" um novo utilitário dinâmico.

O problema prático é tanto com a suposição presuntivo que "ARGV deve ser uma constante", ou no próprio método de inicialização do optparse, que rígidos códigos a atribuição de ARGV ao @default_argv instância var para posterior processamento - que array (ARGV) realmente deve ser um parâmetro, incentivando a re-análise e reutilização, quando apropriado. A parametrização adequada, com um padrão apropriado (por exemplo, ARGV) evitaria a necessidade de alterar o ARGV "constante". Apenas alguns 2 ¢ de pensamentos ...

Lorin Ricker
fonte