instance_eval, class_eval và module_eval trong Ruby
instance_eval Thêm các phương thức cho một instance hay class, module một cách "tốc hành" ("on-the-fly") là một pattern khá phổ biến trong Ruby(activerecord, activesupport, ...) Để thực hiện điều đó, chúng ta có thể dùng các phương thức BasicObject#instance_eval, Module#class_eval, ...
instance_eval
Thêm các phương thức cho một instance hay class, module một cách "tốc hành" ("on-the-fly") là một pattern khá phổ biến trong Ruby(activerecord, activesupport, ...) Để thực hiện điều đó, chúng ta có thể dùng các phương thức BasicObject#instance_eval, Module#class_eval, Module#module_eval.
Thêm phương thức
Trong Ruby, khi chúng ta muốn thêm 1 method vào một instance, chúng ta có thể dùng phương thức BasicObject#instance_eval.Phương thức này nhận đối số là một String hoặc một block
array_second = <<-RUBY def second self[1] end RUBY a = [1, 2, 3] a.instance_eval(array_second) a.second # returns: 2 str = "ruby.devscoop.fr" str.instance_eval do def /(delimiter) split(delimiter) end end str / '.' # returns: ["ruby", "devscoop", "fr"]
cách gọi a.instance_eval(array_second) đã add method second vào biến a- 1 instance của Array
Việc gọi a.instance_eval(array_second) đã thêm phương thức second vào biến a - một instance của Array - bằng cách truyền một String mà sẽ được đánh giá trong context của instance a.
Việc gọi đến str.instance_eval với một block sẽ đánh giá nội dung của block trong context của str - một instance của String.
Ở đây nó sẽ thêm một phương thức /(delimiter)- mà chúng ta có thể sử dụng làm toán tử cho instance str.
Truy cập biến inner
Một feature khác của instance_eval là nó có khả năng truy cập biến inner
class User def initialize(email) @email = email end end u = User.new('ruby@devscoop.fr') u.instance_eval('@email') # returns: "ruby@devscoop.fr" u.instance_eval { @email } # returns: "ruby@devscoop.fr"
Ở đây, chúng ta truy cập biến inner @email bằng cách gọi nó như một đối số String của u.instance_eval cũng như bên trong một block mà chúng ta truyền vào instance_eval.
Điều này là có thể bởi vì instance_eval được thực thi trong context của đối tượng đóng vai trò là người nhận - trong trường hợp này là biến u.
Điều này có nghĩa là tất cả các mã được truyền dưới dạng đối số sẽ được thực thi trong scope của instance của User có tên u.
bỏ qua phương thức điều khiển truy cập
Bằng cách sử dụng instance_eval, Điều này có thể gọi một method private hoặc một method protectedcủa một object nhất định từ bên ngoài của đối tượng này.
class User def initialize(email) @email = email end private def secret_key 'XX-XXXX-XXXX-XXXX' end end u = User.new('ruby@devscoop.fr') u.instance_eval('secret_key') # returns: "XX-XXXX-XXXX-XXXX" u.instance_eval { secret_key } # returns: "XX-XXXX-XXXX-XXXX"
Ở đây, chúng ta truy cập private method User#secret_key bằng cách gọi nó như là một đối số kiểu String của u.instance_eval cũng như bên trong một block mà chúng ta truyền vào instance_eval.
class_eval
Trong Ruby, khi chúng ta muốn thêm một method vào một class, chúng ta có thể sử dụng phương thức Module#class_eval. Phương thức này nhận một String hoặc một 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"]
Việc gọi tới Array.class_eval(array_second) đã thêm method second vào bất kỳ instance nào của Array bằng cách chuyển một String mà sẽ được đánh giá trong context của class Array .
Cuộc gọi đến String.class_evalvới một khối sẽ đánh giá nội dung của khối trong ngữ cảnh của lớp String. Ở đây nó sẽ thêm một String#/(delimiter)phương thức - mà chúng ta có thể sử dụng làm toán tử - cho bất kỳ cá thể nào của String.
module_eval
các Module#module_eval là tương đương với Module#class_eval đối với các module:
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
hai lớp vỏ của cùng một lõi
Chúng ta nói rằng class_eval được sử dụng để thêm các method 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ó.
Trong thực tế, class_eval là một alias đối với module_eval.
Chúng ta hãy xem xét mã nguồn Ruby để xác nhận lời khẳng định trước đó.
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_eval và class_eval chia sẻ cùng một function C có tên rb_mod_module_eval().
Khi chỉ định hàm C này bao gồm module_eval chúng ta có thể nói rằng class_eval là bí danh đối với module_eval.
Tham khảo
https://medium.com/@farsi_mehdi/ruby-instance-eval-a49fd4afa268
https://medium.com/@farsi_mehdi/ruby-class-eval-vs-module-eval-6c3cc24a070