Reputation: 61
I have 3 simple classes CashRegister, Bill and Position. A CashRegister is composed of Bill objects and a Bill object is composed of Position objects. They're implemented as followed
class CashRegister
def initialize
@bills = []
end
def clone
#?
end
end
class Bill
def initialize(nr)
@nr = nr
@positions = []
end
def clone
#?
end
end
class Position
def initialize(product, price)
@product = product
@price = price
end
def clone
#?
end
end
How do I create methods that can deep copy the objects of these classes. The use of Marshal.load(Marshal.dump(an_obj))
is not allowed.
Edit: So far I've got this:
class CashRegister
def initialize
@bills = []
end
def clone
@bills.map { |bill| bill.clone}
end
end
class Bill
def initialize(nr)
@nr = nr
@positions = []
end
def clone
cloned = super
cloned.positions = @positions.map{ |pos| pos.clone}
cloned
end
end
class Position
attr_reader :preis
# this method is given
def produkt
@produkt.clone()
end
def initialize(product, price)
@product = product
@price = price
end
def clone
cloned = super
cloned.product
cloned
end
end
The clone method in class Position seems to be ok (no compile error). But there is an error in the one in class Bill, it says "undefined method 'positions=', so the problem must be in the line cloned.positions = @positions.map{ |pos| pos.clone}
. But I don't understand, can't we call cloned.positions
like that?
Upvotes: 3
Views: 162
Reputation: 61
This solution works
class CashRegister
attr_accessor :bills
def initialize
@bills = []
end
def clone
cloned = super
cloned.bills = @bills.map { |bill| bill.clone }
cloned
end
end
class Bill
attr_accessor :positions
def initialize(nr)
@nr = nr
@positions = []
end
def clone
cloned = super
cloned.positions = @positions.map{ |pos| pos.clone }
cloned
end
end
class Position
attr_reader :price
attr_writer :product
# this method is given
def product
@product.clone
end
def initialize(product, price)
@product = product
@price = price
end
def clone
cloned = super
cloned.product = product
cloned
end
end
Upvotes: 1
Reputation: 1912
Another possible answer is to use the full_dup gem (full disclosure, written by me) then simply use:
p2 = p1.full_dup
Now full_dup, like regular dup, does not copy any singleton methods. If that is important, try the full_clone gem (yup, by me too) instead.
If there are fields that need to excluded from the dup (or clone process), the optional full_dup_exclude (or full_clone_exclude) method can be defined to list fields to be excluded from processing.
Note there is no need to worry about trying to clone numbers, symbols, and other non-clonable things that may exist in your object. These are handled safely be the gems.
Upvotes: 0
Reputation: 110725
It's just the instance variables you have to worry about.
class Position
attr_accessor :product, :price
def initialize(product, price)
@product = product
@price = price
end
end
p1 = Position.new("lima beans", 2.31)
#=> #<Position:0x000000027587b0 @product="lima beans", @price=2.31>
p2 = Position.new(p1.product, p1.price)
#=> #<Position:0x0000000273dd48 @product="lima beans", @price=2.31>
We can confirm that p2
is a deep copy of p1
.
p1.product = "lettuce"
p1.price = 1.49
p1 #=> #<Position:0x0000000271f870 @product="lettuce", @price=1.49>
p2 #=> #<Position:0x000000026e9e00 @product="lima beans", @price=2.31>
p2.product = "spinach"
p2.price = 2.10
p1 #=> #<Position:0x0000000271f870 @product="lettuce", @price=1.49>
p2 #=> #<Position:0x000000026e9e00 @product="spinach", @price=2.1>
It's more complex if, for example, the class were defined as follows (where products
is an array).
p1 = Position.new ["carrots", "onions"]
#=> #<Position:0x000000025b8928 @products=["carrots", "onions"]>
p2 = Position.new p1.products
#=> #<Position:0x000000025b0048 @products=["carrots", "onions"]>
p1.products << "beets"
p1 #=> #<Position:0x000000025b8928 @products=["carrots", "onions", "beets"]>
p2 #=> #<Position:0x000000025b0048 @products=["carrots", "onions", "beets"]>
p2
is not what we want. We would need to write
p1 = Position.new ["carrots", "onions"]
#=> #<Position:0x00000002450900 @products=["carrots", "onions"]>
p2 = Position.new p1.products.dup
#=> #<Position:0x0000000243aa88 @products=["carrots", "onions"]>
(note the .dup
) so that
p1.products << "beets"
#=> ["carrots", "onions", "beets"]
p1 #=> #<Position:0x00000002450900 @products=["carrots", "onions", "beets"]>
p2 #=> #<Position:0x0000000243aa88 @products=["carrots", "onions"]>
More generally, we need to make deep copies of the instance variables.
Upvotes: 3