In my Ruby application, I want to clone a class so that I can make some slight changes to the clone without affecting the original class (see the note below for details). Unfortunately, the cloned class isn’t behaving the way I would expect. Specifically, class methods of the cloned class seem to have trouble accessing constants and class variables. Observe:
irb(main):001:0> class Foo
irb(main):002:1> HELLO = "Hello, world!"
irb(main):003:1> def self.say_hello
irb(main):004:2> HELLO
irb(main):005:2> end
irb(main):006:1> def self.cls_var=(val)
irb(main):007:2> @@cls_var = val
irb(main):008:2> end
irb(main):009:1> def self.cls_var
irb(main):010:2> @@cls_var
irb(main):011:2> end
irb(main):012:1> end
=> nil
irb(main):013:0> Foo.say_hello
=> "Hello, world!"
irb(main):014:0> Foo.cls_var = "Test"
=> "Test"
irb(main):015:0> Foo.cls_var
=> "Test"
irb(main):016:0> Bar = Foo.clone
=> Bar
irb(main):017:0> Bar.say_hello
NameError: uninitialized constant Class::HELLO # ???
from (irb):4:in `say_hello`
from (irb):17
from C:/Ruby193/bin/irb:12:in `<main>`
irb(main):018:0> Bar.cls_var = "Another test"
(irb):7: warning: class variable access from toplevel # Say what?
=> "Another test"
irb(main):019:0> Bar.cls_var
(irb):10: warning: class variable access from toplevel
=> "Another test"
irb(main):020:0> Foo.cls_var
=> "Another test" # Why???
What’s going on here, and how do I fix this so that Bar works exactly the same as Foo does after I clone it?
Note: This question is a follow up to In Ruby, is there a way to ‘override’ a constant in a subclass so that inherited methods use the
new constant instead of the old?
Update: Sorry guys, I guess I wasn’t very clear about why I want to do this. So in my case, Foo is a class in a gem which has functionality thats nearly identical to what I want for one of my classes. In fact, the only difference between Foo and what I want is that pesky HELLO constant. I want MyClass.say_hello to return “Hello, Bob!” instead of “Hello, World!”. (And before you suggest just overriding say_hello, in my case Foo has lots of other methods that use HELLO and say_hello is much more complicated than it is in my example.)
Now I could just change Foo::HELLO with Foo::HELLO.slice!(0, 7) << "Bob!", but that changes the behavior of the gem, which I don’t want. So how would I create an exact duplicate of Foo that has a different value for HELLO?
TLDR: Foo is part of a gem so I don’t want to edit the source. I want a class that behaves exactly the same way Foo does, except with HELLO set to a different value.
So it seems the consensus here is that there’s no easy way of making clone work the way you might expect in this context. There are a lot of alternative solutions, but none of them work exactly the way you might expect
cloneto.First of all, it’s possible to fix the problem with the constants by editing the original class to refer to
self::HELLOin place of justHELLO:Unfortunately, this solution doesn’t resolve the problems with class variables, and it requires you to edit the source of Foo, which might not be desirable if Foo is part of a gem or other external library.
Another solution is to subclass Foo instead of cloning it:
The problem with this is that redefining Bar::HELLO won’t affect the result of
Bar.say_hello, as you might expect from a cloned class:All in all, the most effective solution is probably to just copy the source code of
Foointo another class manually. This isn’t dynamic, but the result is exactly the same as what you might expect fromclone.