Hook methods trong Ruby
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 ...
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:
- sitepoint.com
- rubylearning.com
- railstip.com