+2

Một số medthods hữu dụng thường dùng trong Ruby Metaprogramming

Mời các bạn đọc các concept trước ở trong series này

Còn dưới đây là một số method hay dùng trong Ruby Metaprogramming:

Sự tự xét(introspection) hay sự phản chiếu (reflection)

Trong ruby, ta hoàn toàn có thể đọc thông tin về một class hay object trong quá trình thực thi (runtime). Để làm điều đó, ta có thể sử dụng các method như là class(), instance_method(), instance_variables. Ví dụ:

class Rubyist
  def what_does_he_do
    @person = 'A Rubyist'
    'Ruby programming'
  end
end

an_object = Rubyist.new
puts an_object.class # => Rubyist
puts an_object.class.instance_methods(false) # => what_does_he_do
an_object.what_does_he_do
puts an_object.instance_variables # => @person

Method respond_to? là một ví dụ khác của introspection hay reflection. Ta có thể xác định trước (trước khi yêu cầu object làm gì đó) liệu object có biết làm thế nào để handle message mà mình muốn gửi đi hay không, bằng cách dùng respond_to?. Method này tồn tại trên tất cả các object, mình có thể hỏi bất cứ object nào rằng nó có respond được cái message này ko.

obj = Object.new
if obj.respond_to?(:program)
  obj.program
else
  puts "Sorry, the object doesn't understand the 'program' message."
end

send

send() là một instance method của Object class. Đối số (argument) đầu tiên của send() là tên method mà mình muốn object gọi đến. Tên method đó có thể là string hay symbol, nhưng mà thường thì người ta thích dùng symbol hơn. Các đối số còn lại là các data gửi kèm.

class Rubyist
  def welcome(*args)
    "Welcome " + args.join(' ')
  end
end

obj = Rubyist.new
puts(obj.send(:welcome, "famous", "Rubyists"))   # => Welcome famous Rubyists 

Bằng cách kết hợp với respond_to?, ta có thể để một object tự xử lý khi nó gọi hàm:

class Rubyist
end

rubyist = Rubyist.new
if rubyist.respond_to?(:also_railist)
  puts rubyist.send(:also_railist)
else
  puts "No such information available"
end

Bạn có thể gọi bất cứ method nào với send(), kể cả private methods


class Rubyist
  private
    def say_hello(name)
      "#{name} rocks!!"
    end
end
obj = Rubyist.new
puts obj.send( :say_hello, 'Quynh')

define_method

Module define_method() là một private instance method của class Module. define_method() chỉ được định nghĩa trên class và module. Ta có thể định nghĩa một instance method động ngay trong receiver luôn, chỉ cần đưa tên method vào 1 block, block này chính là body của method sắp đc định nghĩa:

class Rubyist
  define_method :hello do |my_arg|
    my_arg
  end
end
obj = Rubyist.new
puts(obj.hello('Quynh')) # => Quynh

method_missing

Khi ruby thực thi method look-up và ko tìm thấy method phù hợp, nó sẽ gọi method_missing() trên receiver. method_missing() được gọi kèm theo 1 mảng các đối số được truyền vào và block. Ruby hiểu rằng method_missing() ở ngay đó, vì nó là instance method của Kernel mà mọi object đều được kế thừa. Kernel#method_missing() trả lời lại bằng cách raise lên một NoMethodError. Việc overriding lại method_missing() cho phép ta gọi những method không tồn tại.

class Rubyist
  def method_missing(m, *args, &block)
    puts "Called #{m} with #{args.inspect} and #{block}"
  end
end

Rubyist.new.anything # => Called anything with [] and
Rubyist.new.anything(3, 4) { something } # => Called anything with [3, 4] and #<Proc:0x02efd664@tmp2.rb:7>

remove_method và undef_method

Để remove các method đang tồn tại, ta có thể dùng remove_method trong scope của class chứa method đó. Nếu 1 method cùng tên được định nghĩa cho class ancestor(tổ tiên, nằm phía trên trong cây thừa kế) của class đó, thì method trong ancestor class đó sẽ ko được remove.

undef_method thì ngược lại, nó sẽ chặn việc gọi đến method của ancestor class nếu method trong class hiện tại cùng tên với method của ancestor class. Xem ví dụ để hiểu rõ hơn.

class A
  def method_missing(m, *args, &block)
    puts "Method Missing: Called #{m} with #{args.inspect} and #{block}"
  end

  def hello
    puts "Hello A"
  end
end

class B < A
  def hello
    puts "Hello B"
  end
end

obj = B.new
obj.hello # => Hello B

class B
  remove_method :hello  # xóa method từ B, nhưng vẫn tồn tại ở A
end
obj.hello # => Hello A

class IndianRubyist
  undef_method :hello   # chặn bất cứ lần gọi nào đến 'hello'
end
obj.hello # => Method Missing: Called hello with [] and

eval

Method eval() của module Kernel được dùng trong trường hợp thực thi code bên trong string. Method eval() có thể phỏng đoán được các string thậm chí là nhiều dòng, và thực thi các program được nhúng trong string đó. Method này sẽ biên dịch các đoạn code trong string trước khi thực thi, nên nó khá chậm, thậm chí còn khá nguy hiểm. Nếu ai đó tác động từ bên ngoài (giả dụ viết script sử dùng eval method) có mục đích xấu đến ứng dụng của mình, thì nó có thể là 1 lỗ hỏng bảo mật lớn. Nên việc sử dụng eval() được xem là giải pháp cuối cùng.

str = "hello"
puts eval("str + 'world'") # => "hello world"

instance_eval, module_eval, class_eval

instance_eval(), module_eval(), class_eval() và những kiểu đặc biệt của eval().

instance_eval

Class Object có một public method là instance_eval() có thể được gọi từ một object cụ thể. Nó cấp quyền truy cập đến các biến instance của object đó. Method này được gọi dưới dạng block hoặc string.

class Rubyist
  def initialize
    @geek = "Quynh"
  end
end
obj = Rubyist.new
# instance_eval có thể truy cập các private methods của obj
# và các biến instance

obj.instance_eval do
  puts self # => #<Rubyist:0x2ef83d0>
  puts @geek # => Quynh
end

Block mà ta truyền vào instance_eval() có thể giúp ta làm được nhiều việc, ko còn data nào là private nữa ✌️ instance_eval còn có thể được dùng để thêm class method nữa:

class Rubyist
end
Rubyist.instance_eval do
  def who
    "Geek"
  end
end
puts Rubyist.who # => Geek

module_eval, class_eval

Những methods module_evalclass_eval được dùng trên các module và class chứ ko phải trên object. class_eval là 1 alias của module_eval. Những method này được sử dụng để thêm/bớt giá trị của các class variables từ bên ngoài class đó.

class Rubyist
  @@geek = "someone"
end
puts Rubyist.class_eval("@@geek") # => someone

module_evalclass_eval có thể được dùng để thêm instance methods vào 1 module hoặc class. Mặc dù 2 method này có tên khác nhau nhưng chức năng thì giống hệt nhau.

class Rubyist
end
Rubyist.class_eval do
  def who
    "Geek"
  end
end
obj = Rubyist.new
puts obj.who # => Geek

Lưu ý: class_eval định nghĩa instance methods, còn instance_eval định nghĩa class_methods.

class_variable_get, class_variable_set

Để thêm/bớt giá trị của các class variables, ta có thể sử dụng 2 methods là class_variable_get(lấy vào 1 đối số dạng symbol làm tên biến và trả về giá trị của biến) và class_variable_set(lấy vào 1 đối số dạng symbol làm tên biến và đối số thứ 2 là giá trị được gán cảu biến đó).

class Rubyist
  @@geek = "someone"
end

Rubyist.class_variable_set(:@@geek, "Quynh")
puts Rubyist.class_variable_get(:@@geek) # => Quynh

class_variables

Để lấy danh sách các tên biến class dưới dạng 1 mảng, ta có thể sử dụng method class_variables:

class Rubyist
  @@geek = "Quynh"
  @@country = "VN"
end

class Child < Rubyist
  @@city = "DN"
end

print Rubyist.class_variables # => [:@@geek, :@@country]
puts
p Child.class_variables # => [:@@city]

const_get, const_set

Ta có thể lấy giá trị hoặc set giá trị mới của hằng số bằng cách dùng const_getconst_set.

const_get trả về giá trị của constant được gọi, ví dụ:

puts Float.const_get(:MIN) # => 2.2250738585072e-308

const_set thiết lập hằng số (đã được đặt tên) cho object, trả về chính object đó. Nó sẽ tạo mới constant neeys constant đó chưa tồn tại. Ví dụ bên dưới:

class Rubyist
end

puts Rubyist.const_set("PI", 22.0/7.0) # => 3.14285714285714

Wrapup

Các method vừa kể trên được dùng khá phổ biến trong metaprogramming. Hi vọng bài viết sẽ giúp ích ít nhiều cho đọc giả.

Nguồn: http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_3.html


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í