Asignación constante dinámica

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

me da el error:

SyntaxError: error de asignación constante dinámica

¿Por qué se considera esto una constante dinámica? Solo le estoy asignando una cadena.

el espejo
fuente
34
Dinámica constante es algo así como agua seca? :)
fl00r
39
No dice que la constante es dinámica. Dice que la asignación es dinámica.
sepp2k

Respuestas:

141

Su problema es que cada vez que ejecuta el método está asignando un nuevo valor a la constante. Esto no está permitido, ya que hace que la constante no sea constante; a pesar de que el contenido de la cadena es el mismo (por el momento, de todos modos), el objeto de la cadena en sí es diferente cada vez que se llama al método. Por ejemplo:

def foo
  p "bar".object_id
end

foo #=> 15779172
foo #=> 15779112

Quizás si explicara su caso de uso, por qué desea cambiar el valor de una constante en un método, podríamos ayudarlo con una mejor implementación.

¿Quizás prefiera tener una variable de instancia en la clase?

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 realmente desea cambiar el valor de una constante en un método, y su constante es una Cadena o una Matriz, puede 'engañar' y usar el #replacemétodo para hacer que el objeto tome un nuevo valor sin cambiar realmente el 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
fuente
19
El OP nunca dijo que quería cambiar el valor de la constante, sino que solo quería asignar un valor. El caso de uso frecuente que conduce a este error de Ruby es cuando construye el valor en un método a partir de otros activos de tiempo de ejecución (variables, argumentos de línea de comandos, ENV), generalmente en un constructor, por ejemplo def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end. Es uno de esos casos en los que Ruby no tiene una forma simple.
Arnaud Meuret
2
@ArnaudMeuret Para ese caso, desea una variable de instancia (por ejemplo @variable), no una constante. De lo contrario, volvería a asignar DBcada vez que creara una instancia nueva de esa clase.
Ajedi32
2
@ Ajedi32 Esta situación generalmente surge de restricciones externas, no de elecciones de diseño, como mi ejemplo con Sequel. Mi punto es que Ruby permite asignar un valor a una constante en ciertos ámbitos y no en otros. Solía ​​depender del desarrollador elegir sabiamente cuándo realizar la tarea. Ruby cambió en esto. No es para el bien de todos.
Arnaud Meuret
2
@ArnaudMeuret Admito que nunca he usado Sequel antes, así que no puedo decir esto con 100% de certeza, pero solo mirando la documentación de Sequel no veo nada que diga que TIENES que asignar el resultado Sequel.connecta una constante llamada DB . De hecho, la documentación dice explícitamente que eso es solo una recomendación. Eso no me parece una restricción externa.
Ajedi32
@ Ajedi32 1) Nunca escribí eso (nombre de la constante o incluso que tenía que guardarlo en algún lugar) es solo un ejemplo 2) La restricción es que su software puede no tener la información necesaria hasta que normalmente se encuentre en un contexto dinámico .
Arnaud Meuret
69

Debido a que las constantes en Ruby no están destinadas a ser cambiadas, Ruby lo desalienta de asignarles partes de código que podrían ejecutarse más de una vez, como los métodos internos.

En circunstancias normales, debe definir la constante dentro de la propia clase:

class MyClass
  MY_CONSTANT = "foo"
end

MyClass::MY_CONSTANT #=> "foo"

Si por alguna razón realmente necesita definir una constante dentro de un método (quizás para algún tipo de metaprogramación), puede 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"

Sin embargo, una vez más, const_setno es algo a lo que realmente deba recurrir en circunstancias normales. Si no está seguro de si realmente desea asignar a las constantes de esta manera, puede considerar una de las siguientes alternativas:

Variables de clase

Las variables de clase se comportan como constantes de muchas maneras. Son propiedades en una clase, y son accesibles en subclases de la clase en la que se definen.

La diferencia es que las variables de clase deben modificarse y, por lo tanto, pueden asignarse a métodos internos sin 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 clase

Los atributos de clase son una especie de "variable de instancia en una clase". Se comportan un poco como las variables de clase, excepto que sus valores no se comparten con las subclases.

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 de instancia

Y solo para completar, probablemente debería mencionar: si necesita asignar un valor que solo se puede determinar después de que se haya instanciado su clase, hay una buena probabilidad de que realmente esté buscando una variable de instancia antigua simple.

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
fuente
33

En Ruby, cualquier variable cuyo nombre comience con una letra mayúscula es una constante y solo puede asignarle una vez. Elija una de estas alternativas:

class MyClass
  MYCONSTANT = "blah"

  def mymethod
    MYCONSTANT
  end
end

class MyClass
  def mymethod
    my_constant = "blah"
  end
end
David Grayson
fuente
2
Gracias a Dios, alguien mencionó que "¡cualquier variable cuyo nombre comience con una letra mayúscula es una constante!"
ubienewbie
0

No puede nombrar una variable con letras mayúsculas o Ruby asumirá que es una constante y querrá que mantenga su valor constante, en cuyo caso cambiar su valor sería un error, un "error de asignación dinámica constante". Con minúsculas debería estar bien

class MyClass
  def mymethod
    myconstant = "blah"
  end
end
Jose paez
fuente
0

A Ruby no le gusta que esté asignando la constante dentro de un método porque se arriesga a una reasignación. Varias respuestas SO ante mí dan la alternativa de asignarlo fuera de un método, pero en la clase, que es un mejor lugar para asignarlo.

Juan
fuente
1
Weicome a SO John. Puede considerar mejorar esta respuesta agregando un código de muestra de lo que está describiendo.
Cleptus
0

Muchas gracias a Dorian y Phrogz por recordarme sobre el método de matriz (y hash) #replace, que puede "reemplazar el contenido de una matriz o hash".

La noción de que el valor de un CONSTANTE se puede cambiar, pero con una advertencia molesta, es uno de los pocos pasos erróneos conceptuales de Ruby: estos deben ser completamente inmutables o deshacerse por completo de la idea constante. Desde la perspectiva de un codificador, una constante es declarativa e intencional, una señal a otro de que "este valor es realmente inmutable una vez declarado / asignado".

Pero a veces una "declaración obvia" en realidad excluye otras oportunidades útiles futuras. Por ejemplo...

No son casos de uso legítimo en realidad podría ser necesario cambiar el valor de un "constante de": por ejemplo, volver a la carga ARGV desde un indicador de circuito REPL similar, volviendo a ejecutar a través de ARGV más OptionParser.parse (posterior)! llamadas - voila! Da a "argumentos de línea de comando" una utilidad dinámica completamente nueva.

El problema práctico es ya sea con la suposición de presunción de que "ARGV debe ser una constante", o en el propio método initialize de optparse, que duros códigos de la asignación de ARGV a la @default_argv ejemplo var para su posterior procesamiento - que array (ARGV) realmente debe ser un parámetro que fomente la repetición y reutilización, cuando corresponda. La parametrización adecuada, con un valor predeterminado apropiado (por ejemplo, ARGV) evitaría la necesidad de cambiar el ARGV "constante". Solo algunos pensamientos de 2 ¢ ...

Lorin Ricker
fuente