nus nus - 4 months ago 11
Ruby Question

Ruby class constants and inheritance mystery

Why do the following two code snippets not produce the same output? The difference between

push
and
|=
is a tricky one. I suppose that
|=
being an assignment might make a difference? On top of that would constants actually be safe from change later on, I guess not?

The code comes from answers to this question. You can see it in action here.

class LibraryItem

ATTRIBUTES = ['title', 'authors', 'location']

end

class LibraryBook < LibraryItem

ATTRIBUTES.push('ISBN', 'pages']

end

puts LibraryItem::ATTRIBUTES
puts LibraryBook::ATTRIBUTES

> ["title", "authors", "location", "ISBN", "pages"]
> ["title", "authors", "location", "ISBN", "pages"]


and

class Foo

ATTRIBUTES = ['title','authors','location']

end

class Bar < Foo

ATTRIBUTES |= ['ISBN', 'pages']

end

puts Foo::ATTRIBUTES
puts Bar::ATTRIBUTES

> ["title", "authors", "location"]
> ["title", "authors", "location", "ISBN", "pages"]

Answer

Constants in ruby are a bit of a misnomer. Reassigning a constant produces a warning:

Foo=1
Foo=2
(irb):5: warning: already initialized constant Foo

But nothing stops you mutating the actual values themselves, which push does. If you want to prevent this happening, then you can freeze the array, i.e.

class LibraryItem    
  ATTRIBUTES = ['title', 'authors', 'location'].freeze
end

Attempts to mutate the array will now raise an exception. Only the array is frozen though, so you could do something like

LibraryItem::ATTRIBUTES.first.upcase!

(assuming you haven't got frozen string literals turned on) and that change will be allowed. I'm not aware of a way around that other than freezing the strings individually (or turning on frozen string literals for that file, on ruby 2.3 and above)

Comments