Affectation de constante dynamique

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

me donne l'erreur:

SyntaxError: erreur d'affectation de constante dynamique

Pourquoi est-ce considéré comme une constante dynamique? Je lui attribue juste une chaîne.

le miroir
la source
34
La constante dynamique est quelque chose comme l'eau sèche? :)
fl00r
39
Cela ne dit pas que la constante est dynamique. Il dit que la mission est dynamique.
sepp2k

Réponses:

141

Votre problème est que chaque fois que vous exécutez la méthode, vous attribuez une nouvelle valeur à la constante. Ceci n'est pas autorisé, car cela rend la constante non constante; même si le contenu de la chaîne est le même (pour le moment, en tout cas), l' objet chaîne lui-même est différent à chaque fois que la méthode est appelée. Par exemple:

def foo
  p "bar".object_id
end

foo #=> 15779172
foo #=> 15779112

Peut-être que si vous expliquiez votre cas d'utilisation - pourquoi vous voulez changer la valeur d'une constante dans une méthode - nous pourrions vous aider avec une meilleure implémentation.

Peut-être préférez-vous avoir une variable d'instance sur la 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"

Si vous voulez vraiment changer la valeur d'une constante dans une méthode et que votre constante est une chaîne ou un tableau, vous pouvez `` tricher '' et utiliser la #replaceméthode pour que l'objet prenne une nouvelle valeur sans changer réellement l'objet:

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
la source
19
L'OP n'a jamais dit qu'il voulait changer la valeur de la constante, mais voulait simplement attribuer une valeur. Le cas d'utilisation fréquent menant à cette erreur Ruby est lorsque vous construisez la valeur dans une méthode à partir d'autres actifs d'exécution (variables, arguments de ligne de commande, ENV), généralement dans un constructeur, par exemple def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end. C'est l'un de ces cas où Ruby n'a pas de moyen simple.
Arnaud Meuret
2
@ArnaudMeuret Dans ce cas, vous voulez une variable d'instance (par exemple @variable), pas une constante. Sinon, vous réassigneriez DBchaque fois que vous instanciez une nouvelle instance de cette classe.
Ajedi32
2
@ Ajedi32 Cette situation résulte généralement de contraintes externes et non de choix de conception comme mon exemple avec Sequel. Mon point est que l'attribution d'une valeur à une constante est autorisée par Ruby dans certaines étendues et pas dans d'autres. Auparavant, il appartenait au développeur de choisir judicieusement quand effectuer la mission. Ruby a changé à ce sujet. Pas pour le bien de tout le monde.
Arnaud Meuret
2
@ArnaudMeuret J'admets que je n'ai jamais utilisé Sequel auparavant, je ne peux donc pas le dire avec une certitude à 100%, mais en jetant un coup d'œil sur la documentation de Sequel, je ne vois rien qui indique que vous DEVEZ attribuer le résultat de Sequel.connectà une constante nommée DB . En fait, la documentation dit explicitement que ce n'est qu'une recommandation. Cela ne me semble pas une contrainte extérieure.
Ajedi32
@ Ajedi32 1) Je n'ai jamais écrit que (nom de la constante ou même que vous deviez la garder quelque part) c'est juste un exemple 2) La contrainte étant que votre logiciel peut ne pas avoir les informations nécessaires jusqu'à ce que vous soyez généralement dans un contexte dynamique .
Arnaud Meuret
69

Étant donné que les constantes de Ruby ne sont pas censées être modifiées, Ruby vous déconseille de leur attribuer des parties de code qui pourraient être exécutées plus d'une fois, comme les méthodes internes.

Dans des circonstances normales, vous devez définir la constante à l'intérieur de la classe elle-même:

class MyClass
  MY_CONSTANT = "foo"
end

MyClass::MY_CONSTANT #=> "foo"

Si, pour une raison quelconque, vous avez vraiment besoin de définir une constante à l'intérieur d'une méthode (peut-être pour un type de métaprogrammation), vous pouvez utiliser 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"

Encore une fois, ce const_setn'est pas quelque chose auquel vous devriez vraiment avoir recours dans des circonstances normales. Si vous ne savez pas si vous voulez vraiment attribuer des constantes de cette façon, vous pouvez envisager l'une des alternatives suivantes:

Variables de classe

Les variables de classe se comportent comme des constantes à bien des égards. Ce sont des propriétés sur une classe et elles sont accessibles dans les sous-classes de la classe sur laquelle elles sont définies.

La différence est que les variables de classe sont censées être modifiables et peuvent donc être affectées à des méthodes internes sans problème.

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"

Attributs de classe

Les attributs de classe sont une sorte de "variable d'instance sur une classe". Ils se comportent un peu comme des variables de classe, sauf que leurs valeurs ne sont pas partagées avec les sous-classes.

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"

Variables d'instance

Et juste pour être complet, je devrais probablement mentionner: si vous avez besoin d'attribuer une valeur qui ne peut être déterminée qu'après que votre classe a été instanciée, il y a de bonnes chances que vous recherchiez une variable d'instance ancienne.

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
la source
33

Dans Ruby, toute variable dont le nom commence par une majuscule est une constante et vous ne pouvez lui attribuer qu'une seule fois. Choisissez l'une de ces alternatives:

class MyClass
  MYCONSTANT = "blah"

  def mymethod
    MYCONSTANT
  end
end

class MyClass
  def mymethod
    my_constant = "blah"
  end
end
David Grayson
la source
2
Dieu merci, quelqu'un a mentionné que "toute variable dont le nom commence par une majuscule est une constante!"
ubienewbie
0

Vous ne pouvez pas nommer une variable avec des lettres majuscules ou Ruby assumera sa constante et voudra qu'elle garde sa valeur constante, auquel cas changer sa valeur serait une erreur une "erreur d'affectation de constante dynamique". Avec des minuscules devrait être bien

class MyClass
  def mymethod
    myconstant = "blah"
  end
end
José Paez
la source
0

Ruby n'aime pas que vous affectiez la constante à l'intérieur d'une méthode car cela risque de se réaffecter. Plusieurs réponses SO avant moi donnent l'alternative de l'attribuer en dehors d'une méthode - mais dans la classe, qui est un meilleur endroit pour l'attribuer.

John
la source
1
Weicome à SO John. Vous pouvez envisager d'améliorer cette réponse en ajoutant un exemple de code de ce que vous décrivez.
Cleptus
0

Un grand merci à Dorian et Phrogz pour m'avoir rappelé la méthode de tableau (et de hachage) #replace, qui peut "remplacer le contenu d'un tableau ou d'un hachage".

La notion selon laquelle la valeur d'un CONSTANT peut être modifiée, mais avec un avertissement ennuyeux, est l'une des rares erreurs conceptuelles de Ruby - celles-ci devraient soit être totalement immuables, soit vider complètement l'idée de constante. Du point de vue d'un codeur, une constante est déclarative et intentionnelle, un signal à l'autre que «cette valeur est vraiment immuable une fois déclarée / attribuée».

Mais parfois, une «déclaration évidente» exclut en fait d'autres opportunités utiles futures. Par exemple...

Il y a des cas d'utilisation légitimes où la valeur d'une «constante» peut vraiment avoir besoin d'être modifiée: par exemple, recharger ARGV à partir d'une boucle d'invite de type REPL, puis réexécuter ARGV via plus d'OptionParser.parse (suivant)! appels - voila! Donne aux "arguments de ligne de commande" un tout nouvel utilitaire dynamique.

Le problème pratique est soit avec l'hypothèse présumée que "ARGV doit être une constante", soit dans la propre méthode d'initialisation d'optparse, qui code en dur l'affectation d'ARGV à l'instance var @default_argv pour un traitement ultérieur - ce tableau (ARGV) devrait être un paramètre encourageant la ré-analyse et la réutilisation, le cas échéant. Un paramétrage correct, avec une valeur par défaut appropriée (par exemple, ARGV) éviterait de devoir jamais changer l'ARGV «constant». Juste quelques 2 ¢ dignes de réflexion ...

Lorin Ricker
la source