Các lựa chọn xóa bản ghi trong Ruby on Rails

Lời nói đầu

Khi bắt đầu thiết lập các quan hệ rằng buộc nhau ở model trong Rails, thì chắc chắn sẽ có lúc bạn phải đối mặt với việc lựa chọn xóa các bản ghi có quan hệ với nhau. Để rõ hơn về việc này, chúng ta đi vào 1 ví dụ nho nhỏ:

class Post < ActiveRecord::Base
  has_many :comments
end
class Comment < ActiveRecord::Base
  belongs_to :post
end

Và bạn cố gắng xóa 1 bản ghi Post mà có chứa bản ghi Comment ở trong đó. Bán chắc chắn sẽ không thể thực hiện điều này được vì nó có quan hệ phụ thuộc ở đây.

Điều này cũng dễ hiểu thôi, ràng buộc này sẽ tránh các bản ghi phụ thuộc bị xóa 1 cách vô tình. Và bạn có thể thiết lập 1 vài kịch bản khi thực hiện hành động xóa bản ghi như trên dễ dàng hơn, đó là sử dụng từ khóa dependent trong model của bạn, chẳng hạn như ví dụ trên ta viết lại như sau:

class Post < ActiveRecord::Base
   has_many :comments, dependent: :destroy
end

hoặc là

class Post < ActiveRecord::Base
   has_many :comments, dependent: :delete_all
end

Nhưng 1 vấn đề mới phát sinh ở đây là bạn nên chọn destroy hay delete_all ở đây

Destroy

Destroy là option phổ biến nhất mà chúng ta thướng sử dụng trong model Rails, ví dụ thực tế thì: Khi bạn xóa 1 bài đăng trên facebook chẳng hạn, thì các bình luận cũng được xóa theo

class Post < ActiveRecord::Base
  has_many :comments, dependent: :destroy
end

class Comment < ActiveRecord::Base
  belongs_to :post
  after_destroy { puts "Callback Fired" }
end

Cơ bản thì destroy sẽ gọi đến tất cả các quan hệ phụ thuộc trong model và thực hiện hành động xóa chúng. Một ưu điểm của việc sự dụng destroy là nó có thể gọi lại calbacks được khi có vấn đề về dữ liệu. Ví dụ: bạn có thể sự dụng gọi lại after_destroy để ghi lại xác nhận các bản ghi đang bị xóa

Delete_all

Về cơ bản thì delete_all xóa tất cả các các tùy chọn tương tự destroy nhưng sẽ không thực hiện callbacks được.

class Comment < ActiveRecord::Base
  belongs_to :post
  after_destroy { puts "Callback Fired" }
end

class Post < ActiveRecord::Base
  has_many :comments, dependent: :delete_all
end

Nullify

Ngoài DestroyDelele_all thì là Nullify thường ít được sử dụng hơn Nullify hiểu ngược lại với destroy 1 chút, có nghĩa là các bản ghi con sẽ không bị phá hủy sau khi ActiveRecord loại bỏ các bản ghi cha mẹ của chúng. Option này thường ít được sử dụng nên đa phân các Developer có thể k biết đến keyword này, thực ra mình cũng mới biết =))

class Post < ActiveRecord::Base
  has_many :comments, dependent: :nullify
end

Restrict

Ngoài các cách kể trên, nếu bạn không muốn xóa bản ghi thì từ khóa restrict sẽ ngăn các bản ghi phụ thuộc bị xóa

  • :restrict ngăn cản các bản ghi bị xóa nhưng sẽ không sinh ra 1 ngoại lệ hoặc lỗi
  • :restrict_with_exceptions dừng bản ghi phụ thuộc khỏi xóa, trong khi sinh ra 1 ngoại lệ
class Post < ActiveRecord::Base
  has_many :comments, dependent: :restrict_with_exception
end
2.1.2 :003 > p.destroy
   (0.2ms)  BEGIN
   (2.3ms)  ROLLBACK
ActiveRecord::DeleteRestrictionError: Cannot delete record because of dependent comments
  • :restrict_with_error gây ra lỗi khi bản ghi cha mẹ cố gắng được xóa
class Post < ActiveRecord::Base
  has_many :comments, dependent: :restrict_with_error
end
2.1.2 :003 > p.destroy
   (0.2ms)  BEGIN
   (0.2ms)  ROLLBACK
 => false
2.1.2 :004 > p.errors
 => #<ActiveModel::Errors:0x000001016a9828 @base=#<Post id: 6, title: "Post 101", body: nil, created_at: "2015-01-31 13:19:36", updated_at: "2015-01-31 13:19:36">, @messages={:base=>["Cannot delete record because dependent comments exist"]}>

Tại sao bạn có thể muốn sử dụng ngoại lệ hay sinh ra lỗi ở đây? Cơ bản thì sẽ giúp người dùng có thể biết lý do tại sao các bản ghi cha mẹ không thể bị xóa

Tài liệu tham khảo

http://guides.rubyonrails.org/association_basics.html#dependent https://alexhandley.co.uk/rails-dependent-associations/ https://stackoverflow.com/questions/2797339/rails-dependent-destroy-vs-dependent-delete-all https://stackoverflow.com/questions/22757450/difference-between-destroy-and-delete