barsoom / attr_extras

Takes some boilerplate out of Ruby with methods like attr_initialize.
MIT License
560 stars 31 forks source link

Default array values results in shared values #45

Closed frank-west-iii closed 1 year ago

frank-west-iii commented 2 years ago

Using the latest version attr_extras (6.2.5), if you define a default value in an initializer that is an array value (i.e. []), each instance of the class shares the same value of that array.

I thought these were roughly equivalent in function, but the results are not what I expected. This is a simplified example of what I am getting in our current implementation.

class Foo
  aattr_initialize [:name, items: []]
end

data = [
  { name: "One", items: [1, 2, 3] },
  { name: "Two", items: [4, 5, 6] },
]

data.each_with_object([]) do |datum, results|
  name, items = datum.values_at(:name, :items)
  foo = Foo.new(name: name)

  items.each do |n|
    foo.items << n
  end

  results << foo
end

=> [#<Foo @items=[1, 2, 3, 4, 5, 6], @name="One">, #<Foo @items=[1, 2, 3, 4, 5, 6], @name="Two">]
class Bar
  attr_accessor :name, :items

  def initialize(name:, items: [])
    @name = name
    @items = items
  end
end

data = [
  { name: "One", items: [1, 2, 3] },
  { name: "Two", items: [4, 5, 6] },
]

data.each_with_object([]) do |datum, results|
  name, items = datum.values_at(:name, :items)
  bar = Bar.new(name: name)

  items.each do |n|
    bar.items << n
  end

  results << bar
end

=> [#<Bar @items=[1, 2, 3], @name="One">, #<Bar @items=[4, 5, 6], @name="Two">]

If I initialize the object with an empty array it is fine.

foo = Foo.new(name: name)
# vs
foo = Foo.new(name: name, items: [])
class Foo
  aattr_initialize [:name, items: []]
end

data = [
  { name: "One", items: [1, 2, 3] },
  { name: "Two", items: [4, 5, 6] },
]

data.each_with_object([]) do |datum, results|
  name, items = datum.values_at(:name, :items)
  foo = Foo.new(name: name, items: [])

  items.each do |n|
    foo.items << n
  end

  results << foo
end

=> [#<Foo @items=[1, 2, 3], @name="One">, #<Foo @items=[4, 5, 6], @name="Two">]

I didn't have time to dig in yet, but wanted to get this submitted to see if this is unexpected or if I just misunderstood the implementation.

henrik commented 1 year ago

Sorry, somehow missed this. Good catch; thank you! There is now a linked PR that I hope to look at soonish.