+3

#dup vs #clone trong Ruby and Rails

Bạn có bao giờ tự hỏi sự khác biệt giữa #dup và # clone trong Ruby Cả hai đều tạo ra một bản sao ngoài (shallow copy) của một đối tượng (nghĩa là chúng không sao chép các object có thể được tham chiếu trong object được sao chép)

1 In Rails #clone is a less complete copy of an object than #dup

Ở các phiên bản Rails trước, có sự mơ hồ trong cách Rails định nghĩa "shallow". Trong Rails 4.0, #clone là một bản sao ngoài của một đối tượng ActiveRecord. "Shallow" trong ngữ cảnh này có nghĩa là chia sẻ các thuộc tính với bản gốc (original):

Identical to Ruby’s clone method. This is a “shallow” copy. Be warned that your attributes are not copied. That means that modifying attributes of the clone will modify the original, since they will both point to the same attributes hash. If you need a copy of your attributes hash, please use the #dup method.

Tuy nhiên, #dup cũng được mô tả như một bản ngoài. "Shallow" trong ngữ cảnh này có nghĩa là trong khi dup không chia sẻ các thuộc tính với bản gốc, nó sẽ chia sẻ các liên kết

Duped objects have no id assigned and are treated as new records. Note that this is a “shallow” copy as it copies the object’s attributes only, not its associations. The extent of a “deep” copy is application specific and is therefore left to the application to implement according to its need.

pry> original = User.find(3)
  User Load (0.7ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", 3]]
=> #<User id: 3, first_name: "katie", last_name: "leonard", email: nil, created_at: "2015-01-10 17:37:00", updated_at: "2015-01-10 17:37:00">

pry> clone_copy = original.clone
=> #<User id: 3, first_name: "katie", last_name: "leonard", email: nil, created_at: "2015-01-10 17:37:00", updated_at: "2015-01-10 17:37:00">

pry> dup_copy = original.dup
=> #<User id: nil, first_name: "katie", last_name: "leonard", email: nil, created_at: nil, updated_at: nil>

Lưu ý rằng clone_copy là một bản chính xác của bản gốc (cùng một user.id) và dup_copy là một bản ghi mới (user.id = nil). Mọi thay đổi đối với clone_copy sẽ được thay đổi trong bản gốc, nhưng bất kỳ thay đổi nào đối với các thuộc tính dup_copy sẽ vẫn bị cô lập.

2 In Ruby #clone is a more complete copy of an object than #dup

Với các lớp đơn giản, clone () và dup () hoạt động giống hệt nhau:

irb> class User
irb>   attr_accessor :first_name, :last_name, :email
irb>   def initialize(options={})
irb>     @first_name = options[:first_name]
irb>     @last_name  = options[:last_name]
irb>     @email      = options[:email]
irb>   end
irb> end
=> :initialize

irb> original = User.new(first_name: "katie", last_name: "leonard")
=> #<User:0x007fd7e98e0aa8 @first_name="katie", @last_name="leonard", @email=nil>

irb> cloned_copy = original.clone
=> #<User:0x007fd7e98c87c8 @first_name="katie", @last_name="leonard", @email=nil>

irb> dup_copy = original.dup
=> #<User:0x007fd7e98b24a0 @first_name="katie", @last_name="leonard", @email=nil>

irb> cloned_copy.first_name = "foo"
=> "foo"

irb> original.first_name
=> "katie"

irb> dup_copy.first_name
=> "katie"

irb> dup_copy.first_name = "bar"
=> "bar"

irb> original.first_name
=> "katie"

Nói chung, clone và dup có thể có ngữ nghĩa khác nhau trong các lớp con cháu(descendant classes). Trong khi bản sao được sử dụng để sao chép một đối tượng, bao gồm cả trạng thái nội bộ của nó, dup thường sử dụng lớp của đối tượng con cháu để tạo ra trường hợp mới. Khi sử dụng dup bất kỳ mô-đun mà đối tượng đã được mở rộng với sẽ không được sao chép. #dup sẽ hoạt động như #clone, nhưng không có singleton class của bản gốc (original)

irb> class User
irb> attr_accessor :first_name, :last_name, :email
irb>   def initialize(options={})
irb>     @first_name = options[:first_name]
irb>     @last_name  = options[:last_name]
irb>     @email      = options[:email]
irb>   end
irb> end
=> :initialize

irb> module Crunchy
irb>   def bacon
irb>     "bacon"
irb>   end
irb> end
=> :bacon

irb> a = User.new(first_name: "katie", last_name: "leonard")
=> #<User:0x007fd7e8882490 @first_name="katie", @last_name="leonard", @email=nil>

irb> a.extend(Crunchy)
=> #<User:0x007fd7e8882490 @first_name="katie", @last_name="leonard", @email=nil>

irb> a.bacon
=> "bacon"

irb> b = a.clone
=> #<User:0x007fd7e8843060 @first_name="katie", @last_name="leonard", @email=nil>

irb> b.bacon
=> "bacon"

irb> c = a.dup
=> #<User:0x007fd7e98f05c0 @first_name="katie", @last_name="leonard", @email=nil>

irb> c.bacon
NoMethodError: undefined method `bacon' for #<User:0x007fd7e98f05c0>
        from (irb):101
        from /usr/local/var/rbenv/versions/2.1.5/bin/irb:11:in `<main>'

#clone làm được những điều mà #dup ko làm được

  • Sao chép các singleton class của đối tượng được sao chép
  • Duy trì trạng thái đông(froze) của đối tượng được sao chép
a = Object.new
a.freeze
p a.frozen?
# => true
b = a.dup
p b.frozen?
# => false
c = a.clone
p c.frozen?
# => true

Có sự khác biệt giữa #clone và #dup trong Ruby, và ít khác biệt tinh tế hơn trong Rails (tùy thuộc vào phiên bản của bạn). Hãy cẩn thận rằng đối tượng bạn muốn là đối tượng bạn nhận được.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí