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

gives me the error:

SyntaxError: dynamic constant assignment error

Why is this considered a dynamic constant? I'm just assigning a string to it.

2
  • 46
    Dynamic Constant is something like Dry Water? :) Commented Jul 15, 2011 at 19:42
  • 52
    It doesn't say that the constant is dynamic. It says that the assignment is dynamic. Commented Jul 15, 2011 at 19:46

7 Answers 7

178

Your problem is that each time you run the method you are assigning a new value to the constant. This is not allowed, as it makes the constant non-constant; even though the contents of the string are the same (for the moment, anyhow), the actual string object itself is different each time the method is called. For example:

def foo p "bar".object_id end foo #=> 15779172 foo #=> 15779112 

Perhaps if you explained your use case—why you want to change the value of a constant in a method—we could help you with a better implementation.

Perhaps you'd rather have an instance variable on the class?

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" 

If you really want to change the value of a constant in a method, and your constant is a String or an Array, you can 'cheat' and use the #replace method to cause the object to take on a new value without actually changing the object:

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" 
Sign up to request clarification or add additional context in comments.

5 Comments

The OP never said he wanted to change the value of the constant but just wanted to assign a value. The frequent use case leading to this Ruby error is when you build the value in a method from other run-time assets (variables, command-line arguments, ENV), typically in a constructor e.g. def initialize(db,user,password) DB=Sequel.connect("postgres://#{user}:#{password}@localhost/#{db}") end. It's one of those cases where Ruby has no simple way.
@ArnaudMeuret For that case you want an instance variable (e.g. @variable), not a constant. Otherwise you'd be re-assigning DB every time you instantiated a new instance of that class.
@Ajedi32 This situation usually arises from external constraints not design choices such as my example with Sequel. My point is that assigning a value to a constant is allowed by Ruby in certain scopes and not others. It used to be up to the developer to choose wisely when to perform the assignment. Ruby changed on this. Not for everyone's good.
@ArnaudMeuret I'll admit I've never used Sequel before so I can't say this with 100% certainty, but just glancing over the documentation for Sequel I see nothing that says you HAVE to assign the result of Sequel.connect to a constant named DB. In fact, the documentation explicitly says that that's just a recommendation. That doesn't sound like an external constraint to me.
@Ajedi32 1) I never wrote that (name of the constant or even that you had to keep it somewhere) it is just an example 2) The constraint being that your software may not have the necessary information until you typically are in a dynamic context.
86

Because constants in Ruby aren't meant to be changed, Ruby discourages you from assigning to them in parts of code which might get executed more than once, such as inside methods.

Under normal circumstances, you should define the constant inside the class itself:

class MyClass MY_CONSTANT = "foo" end MyClass::MY_CONSTANT #=> "foo" 

If for some reason though you really do need to define a constant inside a method (perhaps for some type of metaprogramming), you can use 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" 

Again though, const_set isn't something you should really have to resort to under normal circumstances. If you're not sure whether you really want to be assigning to constants this way, you may want to consider one of the following alternatives:

Class variables

Class variables behave like constants in many ways. They are properties on a class, and they are accessible in subclasses of the class they are defined on.

The difference is that class variables are meant to be modifiable, and can therefore be assigned to inside methods with no issue.

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" 

Class attributes

Class attributes are a sort of "instance variable on a class". They behave a bit like class variables, except that their values are not shared with 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" 

Instance variables

And just for completeness I should probably mention: if you need to assign a value which can only be determined after your class has been instantiated, there's a good chance you might actually be looking for a plain old instance variable.

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 

Comments

47

In Ruby, any variable whose name starts with a capital letter is a constant and you can only assign to it once. Choose one of these alternatives:

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

2 Comments

Thank goodness someone mentioned that "any variable whose name starts with a capital letter is a constant!"
@ubienewbie Totally. I always assumed it had to be all caps, not just the first letter. This was useful when assigned a variable to a Struct.new. Capitalizing the first letter made it a constant and that made Ruby angry. Thanks!
18

Constants in ruby cannot be defined inside methods. See the notes at the bottom of this page, for example

1 Comment

Link is broken as of the time of this comment. (This is why link-only answers like this one are not so good in the long term!) 😅
3

You can't name a variable with capital letters or Ruby will asume its a constant and will want it to keep it's value constant, in which case changing it's value would be an error an "dynamic constant assignment error". With lower case should be fine

class MyClass def mymethod myconstant = "blah" end end 

Comments

1

Ruby doesn't like that you are assigning the constant inside of a method because it risks re-assignment. Several SO answers before me give the alternative of assigning it outside of a method--but in the class, which is a better place to assign it.

1 Comment

Weicome to SO John. Yo may consider improving this answer adding some sample code of what you are describing.
-1

Many thanks to Dorian and Phrogz for reminding me about the array (and hash) method #replace, which can "replace the contents of an array or hash."

The notion that a CONSTANT's value can be changed, but with an annoying warning, is one of Ruby's few conceptual mis-steps -- these should either be fully immutable, or dump the constant idea altogether. From a coder's perspective, a constant is declarative and intentional, a signal to other that "this value is truly unchangeable once declared/assigned."

But sometimes an "obvious declaration" actually forecloses other, future useful opportunities. For example...

There are legitimate use cases where a "constant's" value might really need to be changed: for example, re-loading ARGV from a REPL-like prompt-loop, then rerunning ARGV thru more (subsequent) OptionParser.parse! calls -- voila! Gives "command line args" a whole new dynamic utility.

The practical problem is either with the presumptive assumption that "ARGV must be a constant", or in optparse's own initialize method, which hard-codes the assignment of ARGV to the instance var @default_argv for subsequent processing -- that array (ARGV) really should be a parameter, encouraging re-parse and re-use, where appropriate. Proper parameterization, with an appropriate default (say, ARGV) would avoid the need to ever change the "constant" ARGV. Just some 2¢-worth of thoughts...

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.