Ruby: class_eval vs module_eval



Thêm methods hoặc attributes vào class/module một cách nhanh chóng là một mô hình pattern khá phổ biến trong Ruby (activerecord, activesupport, rake, rack, vân vân..). Để làm như vậy, chúng ta có thể sử dụng các phương thức Module#class_evalModule#module_eval. Sự khác biệt chính giữa 2 methods này là gì?

class_eval

Trong Ruby, khi chúng ta muốn thêm một phương thức vào một lớp, chúng ta có thể sử dụng phương thức Module # class_eval. Phương thức này chấp nhận String hoặc block làm đối số.

array_second = <<-RUBY
def second
  self[1]
end
RUBY
Array.class_eval(array_second)
String.class_eval do
  def /(delimiter)
    split(delimiter)
  end
end
$> [1,2,3].second
 => 2
$> "1,2,3" / ','
  => ["1", "2", "3"]

Lệnh gọi Array.class_eval(array_second) thêm phương thức thứ hai vào bất kỳ instance nào của Array bằng cách chuyển một Chuỗi sẽ được đánh giá trong ngữ cảnh của class Array.

Lệnh gọi tới String.class_eval với một khối sẽ đánh giá nội dung của khối trong ngữ cảnh của class String. Ở đây, nó sẽ thêm một phương thức String#/(delimiter) - mà chúng ta có thể sử dụng làm toán tử - cho bất kỳ instance nào của String.

NB: vui lòng đọc bài viết này về heredocs nếu cú pháp <<-RUBY không quen thuộc với bạn

module_eval

Mô-đun Module#module_eval tương đương vớiModule#class_eval cho các mô-đun sau:

module Commentable
  def add_comment(comment)
    self.comments << comment
  end
  def comments
    @comments ||= []
  end
end
Commentable.module_eval do
  def comment_count
    comments.count
  end
end
class Post
  include Commentable
end
$> post = Post.new
 => #<Post:0x00007fd9ac0238b0> 
$> post.add_comment("Very nice !")
 => ["Very nice !"] 
$> post.comment_count
 => 1

two skins for one core

Chúng ta đã nói rằng class_eval được sử dụng để thêm các phương thức và thuộc tính vào một class hiện có. Và module_eval được sử dụng để thêm các phương thức và thuộc tính vào một module hiện có. Nhưng đây chỉ là một quy ước .. Trong thực tế, class_eval là một bí danh(alias) cho module_eval. Hãy nhìn một cái đến mã nguồn Ruby để xác nhận điều này.

Trong ruby/vm_eval.c

rb_define_method(rb_cModule, "module_eval", rb_mod_module_eval, -1);    rb_define_method(rb_cModule, "class_eval",  rb_mod_module_eval, -1);

Như chúng ta có thể thấy module_evalclass_eval chia sẻ cùng một hàm C có tên rb_mod_module_eval().

Vì hàm C này chỉ định bao gồm module_eval, chúng ta có thể nói class_eval là bí danh của module_eval.


All Rights Reserved