1

I am struggling a bit with subclassing and class variables. I was expecting the class variables set by a class method call of the subclass to be specific to that subclass, but I see them being set in the super class.

For example, if I run the following code:

class Obj
    @@can_say = []
    def self.says word
        @@can_say.push(word)
    end

    def self.listens_to calling
        define_method calling do
            "I say #{@@can_say}"
        end
    end
end

class Dog < Obj
    says "woof"
    says "bark bark"
    listens_to "goodboy"
end

class Cat < Obj
    says "meaow"
    says "purr"
    listens_to "lord"
end

cat = Cat.new
dog = Dog.new

puts("Dog: #{dog.goodboy}")
puts("Cat: #{cat.lord}")

I get:

ruby/test$ ruby test.rb 
Dog: I say ["woof", "bark bark", "meaow", "purr"]
Cat: I say ["woof", "bark bark", "meaow", "purr"]

I was expecting:

ruby/test$ ruby test.rb 
Dog: I say ["woof", "bark bark"]
Cat: I say ["meaow", "purr"]

Is there a way to achieve that?

599644
  • 561
  • 2
  • 15
  • This is a super common misconception about @@ vars ... see https://stackoverflow.com/questions/1251352/ruby-inherit-code-that-works-with-class-variables – max pleaner Apr 16 '18 at 02:44
  • From the [docs](http://ruby-doc.org/core-2.5.0/doc/syntax/assignment_rdoc.html#label-Class+Variables): _"Class variables are shared between a class, its subclasses and its instances."_ – Stefan Apr 16 '18 at 08:04

1 Answers1

0

I kind of found a way:

class Obj
    class << self
        attr_accessor :can_say
    end

    def self.says word
        if @can_say.nil?
            @can_say = Array.new
        end
        @can_say.push(word)
    end

    def self.listens_to calling
        define_method calling do
            "I say #{self.class.can_say}"
        end
    end
end

class Dog < Obj
    says "woof"
    says "bark bark"
    listens_to "goodboy"
end

class Cat < Obj
    says "meaow"
    says "purr"
    listens_to "lord"
end

cat = Cat.new
dog = Dog.new

puts("Dog: #{dog.goodboy}")
puts("Cat: #{cat.lord}")

Now if I can somehow get rid of:

if @can_say.nil?
    @can_say = Array.new
end
599644
  • 561
  • 2
  • 15
  • 1
    you can use this `(@can_say ||= []) << word` to get rid of that, it is just saying the same thing but in a single line, it'll just create an array if not found or append to the existing one if found – Subash Apr 16 '18 at 01:03
  • Thanks @Subash, that's helpful to know! Is there any other way to somehow declare and initialize the `@cas_say` beforehand? – 599644 Apr 16 '18 at 01:39
  • `(@can_say ||= [])` this line here without appending `word` is doing the initializing of the empty array – Subash Apr 16 '18 at 01:59