0

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"]
  • In the first example, `ATTRIBUTES` refers to the **same** array in both cases, and modifying it in one place means the other will see the changes too. In the second one, you are creating a new array for `Bar` instead of changing the shared one, which results in the behavior you want. – Gosha A Jul 09 '16 at 08:24
  • So it's assigning that makes all the difference? That means if my class get's subclassed, any subclass can change a 'constant'? –  Jul 09 '16 at 08:30
  • This code doesn't compile. – Nic Jul 09 '16 at 16:52
  • @QPaysTaxes Ruby is an interpreted language. – Ryan Bemrose Jul 09 '16 at 18:00
  • @RyanBemrose Whoops, yeah. I meant to say the first raises a syntax error. – Nic Jul 09 '16 at 18:01

2 Answers2

4

In first example, ATTRIBUTES refer to the same array and you are modifying it. Hence,

puts LibraryItem::ATTRIBUTES
puts LibraryBook::ATTRIBUTES

produce same results.

While in second case, you are doing a |= b which is a shorthand for a = a | b. This will create a new array called ATTRIBUTES for class Bar. Hence,

puts Foo::ATTRIBUTES
puts Bar::ATTRIBUTES

produce different result.

You can read more about Ruby Assignment Operator in this question. Ruby |= assignment operator

EDIT

Ruby arrays implements a small collection of set operation with &, | operators.

Single Pipe, | perform union operation i.e. adds only unique elements.

a = [:foo, :bar, :baz]
a |= [:baz, :buz] # => [:foo, :bar, :baz, :buz]
Community
  • 1
  • 1
Pramod
  • 1,376
  • 12
  • 21
  • Yeah, my bad. I don't need brushing up on assignment operators, however on the concept of _constants_ I do apparently... –  Jul 09 '16 at 10:10
3

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)

Frederick Cheung
  • 83,189
  • 8
  • 152
  • 174