-1
class Test
  def self.states
    @@states ||= OpenStruct.new({initial: 'initial', started: 'started'}).freeze
  end
end
  1. Does above code mean @@states is frozen ?
  2. Is @@states re-assignable then how?
  3. Is writing @states and @@states same in class methods ?
  4. If @@states is not-frozen or re-assignable then how can I lock @@states once it is set so that it is not re-assignable?
Ror Roars
  • 35
  • 4
  • 2
    You freeze objects, not variables. Freezing an object prevents modifications of that object. Variables referring to that object aren't affected at all and can be re-assigned just like before. – Stefan Aug 05 '22 at 09:13
  • 2
    BTW, you probably want `@state` which is – in that context – a so-called _class instance variable_. You prevent modifications of the variable by not exposing it to the outside, e.g. by not having a setter method. Alternatively use a constant which prints a warning when being re-assigned. – Stefan Aug 05 '22 at 09:18
  • If you have 4 questions, please ask 4 questions, not one question. Thank you very much for keeping [so] clean and lean! – Jörg W Mittag Aug 06 '22 at 08:56

1 Answers1

2

Does above code mean @@states is frozen?

Yes (sort-of). You called .freeze on the object, so the object is frozen. I.e. technically the "frozen" thing is the object referenced by the variable, not the variable itself:

[1] pry(main)> x = "hello".freeze
=> "hello"
[2] pry(main)> x.frozen?
=> true
[3] pry(main)> x = "world"
=> "world"
[4] pry(main)> x.frozen?
=> false

Is @@states re-assignable then how?

There's no "public interface" to change the variable, but you can do it via meta-programming:

Test.class_variable_set('@@states', 'CHANGED!')

Is writing @states and @@states same in class methods?

No. There's a subtle difference between an instance variable in a class and a class variable. For example, see this answer for a more detailed explanation.

If @@states is not-frozen or re-assignable then how can I lock @@states once it is set so that it is not re-assignable?

You can't make it impossible to change/reassign anything in ruby; the best you can do is make it very awkward -- i.e. you have to write something "hacky" to change it.

In other words, you can remove any "public interface" and make the object frozen (which is exactly what you've done already), but - by the nature of ruby being a dynamic language with powerful meta-programming - there's always going to be some back-door way of changing a variable if you're really determined. For instance, see this library.

This isn't unique to ruby -- for example, it's possible to change constants in C/C++ by using pointers.

Tom Lord
  • 27,404
  • 4
  • 50
  • 77
  • This is an excellent explanation but I think I have one tiny nitpick. What's frozen is the instance of `OpenStruct` that's assigned to `@@states`. That instance can't be modified, but `@@states` can be reassigned. You explain it further down but I'd make it clear upfront. – Mike Szyndel Aug 05 '22 at 09:43
  • There is also the gotcha with class variables that even if your class doesn't expose the class variable a subclass could and ruin the party for everyone. A class instance variable would be a better choice. – max Aug 05 '22 at 11:46
  • @max Sure -- and you could also dynamically extend the class to define your own setter! There's probably lots of ways that you *could* change the variable via some "back door", but given the currently shared code there's no "front door" way of changing it. – Tom Lord Aug 05 '22 at 14:52
  • Thanks @TomLord and ALL, for sharing the insights. – Ror Roars Aug 23 '22 at 08:22