Hook methods trong Ruby
Bài đăng này đã không được cập nhật trong 8 năm
Triết lý của Ruby là lập trình hạnh phúc (programmer happiness). Ruby tin tưởng mạnh mẽ vào điều đó (programmer happiness) và nó đã cung cấp nhiều cách khác nhau để đạt được. Metaprogramming cung cấp cho các lập trình viên cách để viết dynamic code. Đa luồng cung cấp cho các lập trình một cách thoải mái để viết code. Đó là chính là hook method, nó giúp cho các coder mở rộng hành vi của chương trình trong quá trình runtime.
Các tính năng nói trên, cùng với một số khía cạnh khác, làm cho Ruby là một trong những sự lựa chọn ưa thích để lập trình. Bài viết này sẽ tìm hiểu một số hook method quan trọng trong Ruby. Chúng ta sẽ thảo luận về các khía cạnh khác nhau về hook method, chẳng hạn như đó là gì, sử dụng mục đích gì và làm thế nào để sử dụng vào những trường hợp khác nhau. Chúng ta cũng sẽ xem xét cách thức phổ biến của các Ruby framework / gems / libraries sử dụng hook method để cung cấp những tính năng tuyệt vời.
Chúng ta cùng bắt đầu
Hook Method là gì?
Hook Method cung cấp cách để mở rộng hành vi của chương trình tại thời điểm runtime. Bạn hãy tưởng tượng khả năng nhận được thông báo bất cứ khi nào một lớp con kế thừa từ một lớp cha hoặc xử lý lỗi những phương thức của object nhưng không có phép complier bắn ra ngoại lệ. Đây là một trong số các trường hợp sử dụng hook method. Các framework / library đã sử dụng hook method khác nhau để đạt được chức năng của họ mong muốn.
Chúng tôi sẽ thảo luận về các hook method sau đây móc trong bài viết này:
- included
- extended
- prepended
- inherited
- method_missing
Included
Ruby cho chúng ta một cách để viết modular code sử dụng modules
(gọi là mixins trong những ngôn ngữ khác) mà sau này có thể được sử dụng trong các modules/classes
khác. Ý tưởng đằng sau module là khá đơn giản; đó là một phần code mà có thể được sử dụng ở những nơi khác.
Ví dụ, nếu chúng ta muốn viết một đoạn code trả về một String
theo ý muốn bất cứ khi nào một hàm của object được gọi:
module Person
def name
puts "My name is Person"
end
end
Việc này khá đơn giản, và áp dụng chúng ta có như sau:
class User
include Person
end
Ruby cung cấp nhiều cách khác nhau để sử dụng modules
. Một trong những cách đó là include
. Những gì include
thực hiện là làm cho những phần code trong module có sẵn trên class.
Trong trường hợp của chúng ta, những hàm được định nghĩa trong module Person
trở thành một trong những hàm của đối tượng của class User
. Do đó chúng ta có thể dễ dàng gọi hàm name
với object của class User
. Ví dụ:
User.new.name
=> My name is Person
Cùng xem xét hook method dựa trên include
. included
là một hook method được cung cấp bởi Ruby và được gọi bất cứ lúc nào bạn include
module
vào trong 1 class. Cập nhật Person
module chúng ta có như sau:
module Person
def self.included(base)
puts "#{base} included #{self}"
end
def name
"My name is Person"
end
end
Chúng ta thấy một method included
được định nghĩa trong Person
module giống như class method. Thử gọi kiểm tra lại như lúc nãy chúng ta thấy kết quả như sau:
User included Person
My name is Person
Như chúng ta thấy, base
trả về tên của class
mà module
được include
vào. Như vậy chúng ta có một tham chiếu đến class bao gồm cả Person
Chắc hẳn mọi người cũng khá quen thuộc với gem Devise. Vậy chúng ta cùng xem cách mà Devise sử dụng included
hook. Khai báo devise trong class hay được sử dụng như sau:
devise :database_authenticatable, :registerable, :validatable
Để làm được điều đó devise đã xử lý ở đây. Tôi sẽ dán đoạn code đó vào ngay bên dưới để tiện theo dõi:
def devise(*modules)
options = modules.extract_options!.dup
selected_modules = modules.map(&:to_sym).uniq.sort_by do |s|
Devise::ALL.index(s) || -1 # follow Devise::ALL order
end
devise_modules_hook! do
include Devise::Models::Authenticatable
selected_modules.each do |m|
mod = Devise::Models.const_get(m.to_s.classify)
if mod.const_defined?("ClassMethods")
class_mod = mod.const_get("ClassMethods")
extend class_mod
if class_mod.respond_to?(:available_configs)
available_configs = class_mod.available_configs
available_configs.each do |config|
next unless options.key?(config)
send(:"#{config}=", options.delete(config))
end
end
end
include mod
end
self.devise_modules |= selected_modules
options.each { |key, value| send(:"#{key}=", value) }
end
end
Các module được truyền vào phương thức devise trong model của chúng ta chính là tham số *modules
như là một mảng (array). Thông qua việc xử lý sẽ chuyển các tham số đầu vào thành các hằng số tương ứng. Ví dụ:
:validateable => Validatable
:registration => Registration
và cuối cùng là sử dụng include
để ở dòng số 27 để xử lý.
Ví dụ Validatable
, module này được định nghĩa ở đây và biểu diễn như sau:
def self.included(base)
base.extend ClassMethods
assert_validations_api!(base)
base.class_eval do
validates_presence_of :email, if: :email_required?
validates_uniqueness_of :email, allow_blank: true, if: :email_changed?
validates_format_of :email, with: email_regexp, allow_blank: true, if: :email_changed?
validates_presence_of :password, if: :password_required?
validates_confirmation_of :password, if: :password_required?
validates_length_of :password, within: password_length, allow_blank: true
end
end
Model trong trường hợp này là base
. Ở dòng số 5, có một block class_eval
. Viết code thông qua class_eval
thì giống như viết code trong các class tương ứng.
Extended
Ruby cũng cho phép những lập trình viên extend một module, có chút khác biệt với include
. Thay vì xem như đó là hàm của object thì extend lại xem đó là những hàm của class. Cùng xem ví dụ sau:
module Person
def name
"My name is Person"
end
end
class User
extend Person
end
puts User.name # => My name is Person
Thử so sánh include
và extend
qua ví dụ sau:
#We are using same Person module and User class from previous example.
u1 = User.new
u2 = User.new
u1.extend Person
puts u1.name # => My name is Person
puts u2.name # => undefined method `name' for #<User:0x007fb8aaa2ab38> (NoMethodError)
Trong bài viết này tôi đã làm rõ included
và extended
. Những phần còn lại sẽ làm rõ trong những bài viết tiếp theo.
Nguồn + tham khảo:
All rights reserved