Dynamic constant assignment

后端 未结 7 766
眼角桃花
眼角桃花 2020-11-28 20:31
class MyClass
  def mymethod
    MYCONSTANT = \"blah\"
  end
end

gives me the error:

SyntaxError: dynamic constant assignmen

相关标签:
7条回答
  • 2020-11-28 20:41

    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"
    
    0 讨论(0)
  • 2020-11-28 20:41

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

    0 讨论(0)
  • 2020-11-28 20:43

    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
    
    0 讨论(0)
  • 2020-11-28 20:43

    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.

    0 讨论(0)
  • 2020-11-28 20:55

    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...

    0 讨论(0)
  • 2020-11-28 21:00

    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
    
    0 讨论(0)
提交回复
热议问题