You can use clone to do prototype-based programming in Ruby. Ruby's Object class defines both the clone method and dup method. Both clone and dup produce a shallow copy of the object it is copying; that is, the instance variables of the object are copied but not the objects they reference. I will demonstrate an example:
class Apple
attr_accessor :color
def initialize
@color = 'red'
end
end
apple = Apple.new
apple.color
=> "red"
orange = apple.clone
orange.color
=> "red"
orange.color << ' orange'
=> "red orange"
apple.color
=> "red orange"
Notice in the above example, the orange clone copies the state (that is, the instance variables) of the apple object, but where the apple object references other objects (such as the String object color), those references are not copied. Instead, apple and orange both reference the same object! In our example, the reference is the string object 'red'. When orange uses the append method, <<, to modify the existing String object, it changes the string object to 'red orange'. This in effect changes apple.color too, since they are both pointing to the same String object.
As a side note, the assignment operator, =, will assign a new object and thus destroy a reference. Here is a demonstration:
class Apple
attr_accessor :color
def initialize
@color = 'red'
end
end
apple = Apple.new
apple.color
=> "red"
orange = apple.clone
orange.color
=> "red"
orange.color = 'orange'
orange.color
=> 'orange'
apple.color
=> 'red'
In the above example, when we assigned a fresh new object to the color instance method of the orange clone, it no longer references the same object as apple. Hence, we can now modify the color method of orange without affecting the color method of apple, but if we clone another object from apple, that new object will reference the same objects in copied instance variables as apple.
dup will also produce a shallow copy of the object it is copying, and if you were to do the same demonstration shown above to dup, you will see it works exactly the same way. But there are two major differences between clone and dup. First, as others mentioned, clone copies the frozen state and dup does not. What does this mean? The term 'frozen' in Ruby is an esoteric term for immutable, which itself is a nomenclature in computer science, meaning that something cannot be changed. Thus, a frozen object in Ruby cannot be modified in any way; it is, in effect, immutable. If you attempt to modify a frozen object, Ruby will raise a RuntimeError exception. Since clone copies the frozen state, if you attempt to modify a cloned object, it will raise a RuntimeError exception. Conversely, since dup does not copy the frozen state, no such exception will occur, as we'll demonstrate:
class Apple
attr_accessor :color
def initialize
@color = 'red'
end
end
apple = Apple.new
apple.frozen?
=> false
apple.freeze
apple.frozen?
=> true
apple.color = 'crimson'
RuntimeError: can't modify frozen Apple
apple.color << ' crimson'
=> "red crimson" # we cannot modify the state of the object, but we can certainly modify objects it is referencing!
orange = apple.dup
orange.frozen?
=> false
orange2 = apple.clone
orange2.frozen?
=> true
orange.color = 'orange'
=> "orange" # we can modify the orange object since we used dup, which did not copy the frozen state
orange2.color = 'orange'
RuntimeError: can't modify frozen Apple # orange2 raises an exception since the frozen state was copied via clone
Second, and, more interestingly, clone copies the singleton class (and hence its methods)! This is very useful if you desire to undertake prototype-based programming in Ruby. First, let's show that indeed the singleton methods are copied with clone, and then we can apply it in an example of prototype-based programming in Ruby.
class Fruit
attr_accessor :origin
def initialize
@origin = :plant
end
end
fruit = Fruit.new
=> #<Fruit:0x007fc9e2a49260 @origin=:plant>
def fruit.seeded?
true
end
2.4.1 :013 > fruit.singleton_methods
=> [:seeded?]
apple = fruit.clone
=> #<Fruit:0x007fc9e2a19a10 @origin=:plant>
apple.seeded?
=> true
As you can see, the singleton class of the fruit object instance is copied to the clone. And hence the cloned object has access to the singleton method :seeded?. But this is not the case with dup:
apple = fruit.dup
=> #<Fruit:0x007fdafe0c6558 @origin=:plant>
apple.seeded?
=> NoMethodError: undefined method `seeded?'
Now in prototype-based programming, you do not have classes which extend other classes and then create instances of classes whose methods derive from a parent class that serves as a blueprint. Instead, you have a base object and then you create a new object from the object with its methods and state copied over (of course, since we are doing shallow copies via clone, any objects the instance variables reference will be shared just as in JavaScript prototypes). You can then fill in or change the object's state by filling in the details of the cloned methods. In the below example, we have a base fruit object. All fruit have seeds, so we create a method number_of_seeds. But apples have one seed, and so we create a clone and fill in the details. Now when we clone apple, we not only cloned the methods but we cloned the state! Remember clone does a shallow copy of the state (instance variables). And because of that, when we clone apple to get a red_apple, red_apple will automatically have 1 seed! You can think of red_apple as an object that inherits from Apple, which in turn inherits from Fruit. Hence, that is why I capitalized Fruit and Apple. We did away with the distinction between classes and objects courtesy of clone.
Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
@number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
@number_of_seeds
end
Apple = Fruit.clone
=> #<Object:0x007fb1d78165d8>
Apple.number_of_seeds = 1
Apple.number_of_seeds
=> 1
red_apple = Apple.clone
=> #<Object:0x007fb1d892ac20 @number_of_seeds=1>
red_apple.number_of_seeds
=> 1
Of course, we can have a constructor method in protoype-based programming:
Fruit = Object.new
def Fruit.number_of_seeds=(number_of_seeds)
@number_of_seeds = number_of_seeds
end
def Fruit.number_of_seeds
@number_of_seeds
end
def Fruit.init(number_of_seeds)
fruit_clone = clone
fruit_clone.number_of_seeds = number_of_seeds
fruit_clone
end
Apple = Fruit.init(1)
=> #<Object:0x007fcd2a137f78 @number_of_seeds=1>
red_apple = Apple.clone
=> #<Object:0x007fcd2a1271c8 @number_of_seeds=1>
red_apple.number_of_seeds
=> 1
Ultimately, using clone, you can get something similar to the JavaScript prototype behavior.
dup
andclone
does, but why you'd use one rather than the other. – Andrew Grimm