Ruby Metaprogramming (Phần 1) - What is this Magic ?
This post hasn't been updated for 6 years
Để cho các bạn đỡ cái cảm giác chán ngán khi đọc phần mở bài của bất cứ cái bài viết kỹ thuật nào cũng đều bắt gặp format dạng " abc là một beep beep beep được tạo ra bởi oạch oạch oạch phục vụ cho bla bla bla", hôm nay mình sẽ viết cái series này theo một cách freestyle nhất với mục đích là đảm bảo cho người đọc vẫn thấy được cái magic của đề tài, nhưng sẽ không có cái cảm giác "what the hell did i just read ?" như mình đã từng sau khi đọc xong cuốn sách lần đầu tiên.
Bài viết nãy sẽ không có đề mục, không có gạch đầu dòng hay chia ý, vì vốn dĩ nó không có ý gì cụ thể cả =))
Tất cả những gì mình muốn tuyền tải chỉ là lý do vì sao bạn nên đọc Metaprogramming Ruby 2: Program Like the Ruby Pros mà thôi .
Và tất nhiên, lời khuyên của mình vẫn là nếu bạn muốn đọc cái cuốn sách này, hãy đọc chậm thôi, kết hợp ngẫm nghĩ và thử thực hành. Vì nếu bạn đọc nó đủ nhanh, nó sẽ biến thành 1 vòng tròn và đưa bạn về điểm bắt đầu
Okay, here we go !
Nếu như bạn đã từng học hoặc làm việc với Rails, chắc hẳn bạn sẽ thật sự bị hấp dẫn bởi cách mà Rails hỗ trợ bạn trong việc tạo các quan hệ giữa các model, hay cách nó tạo ra 1 bảng trong DB chỉ bằng việc viết một vài dòng migrate. Ngắn gọn, đẹp, và hiệu quả đó là tất cả những gì bạn nhìn thấy.
Well sad, hoa nào đẹp thì cũng có gai, việc được support quá nhiều có thể trở thành con dao hai lưỡi với các lập trình viên, nhất là với những người mới bắt đầu. Tất cả những gì chúng ta thấy đều diễn ra quá nhanh, và quá hiệu quả, nó làm ta ngỡ ngàng và ahm uhm, đôi lúc ta không biết chuyện gì vừa xảy ra.
Giờ hãy thử nhìn vào 1 đoạn code validation nào
class User < ActiveRecord::Base
validates :email, presence: true
end
Làm sao nó hoạt động được man ?
Trong Ruby hay là Rails, có một keyword luôn tồn tại bất chấp bạn đang ở đâu trong mớ code hỗn độn, đó chính là: self.
Vì vậy nếu bạn đang ở trong một class, và bạn puts self, nó sẽ ghi ra value/tên của class.
Nhưng nếu bạn đang ở trong một instance method thì self lúc này lại là value của instance đó chứ không còn của class nữa
class User
puts "#{self}"
end
User
Và đây là khi bạn gọi self ở instance method
class User
def full_name
puts "#{self}"
end
User.new.full_name
#<User:0x007fdcb2a14f50>
Điều này giúp cho việc viết các validate trong ví dụ ở trên trở nên có tác dụng, lúc này
validates :email, presence: true
sẽ giống với
self.validates :email, presence: true
Ví dụ ban đầu sẽ tương đương với:
class User < ActiveRecord::Base
self.validates :email, presence: true
end
Dù cho nghe có vẻ ghê gớm nhưng suy cho cùng validate chỉ đơn giản là 1 class method mà chúng ta gọi và truyền vào đó một tham số
Tiếp tục với câu chuyện magic tiếp theo nha
Làm sao mấy dòng code migration lại có thể hoạt động được trong Rails (??)
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.integer :age
end
end
end
Bây giờ có thể hiểu create_table là một instance method, vì vậy chúng ta có thể viết self.create_table thay vì create_table.
Nhưng self bây giờ đã được refer tới instance hiện tại, nên việc viết self phía trước là không cần thiết nữa
1 cú soi code trong Rails nào, chúng ta sẽ thấy create_table được định nghĩa:
def create_table(table_name, options = {})
td = create_table_definition table_name, options[:temporary], options[:op
tions], options[:as]
if options[:id] != false && !options[:as]
pk = options.fetch(:primary_key) do
Base.get_primary_key table_name.to_s.singularize
end
td.primary_key pk, options.fetch(:id, :primary_key), options
end
yield td if block_given?
if options[:force] && table_exists?(table_name)
drop_table(table_name, options)
end
result = execute schema_creation.accept td
td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless suppor ts_indexes_in_create?
result
end
Nếu để ý thì bạn sẽ thấy tham số đầu tiên của nó là table_name, sau đó thì bạn có thể truyền vào một tham số khác như kiểu id:false để tạo bảng mà không cần id
yield td if block_given? là cái quái gì ?
Nó được viết bên trong define của create_table để nói với chúng ta rằng sẽ có 1 block, và tất nhiên, bạn có thể thấy là có hay không rồi đúng không )
Giờ bạn có thể tưởng tượng xem has_many :users hoạt động ra sao rồi đúng không. Thấy magic không nào
Việc có được những kiến thức về cách hoạt động của Rails, Ruby sẽ giúp cho bạn có thể tự tin hơn trong quá trình viết hay debug code trong Rails App. Và vì những gì chúng ta biết được hay có thể hình dung được chỉ là 1 phần nhỏ của tảng băng, nên mình mong những Ruby developer nếu có thể thì hãy đọc và đọc nhiều lần cuốn Metaprogramming Ruby(đọc ver 2 nhé) vì nó thật sự hay và hữu ích.
Nếu bạn lười đọc sách, hãy follow seriers này và mình sẽ cố gắng viết đầy đủ nhất có thể những gì mình đọc và hiểu được. Peace!
Tài liệu tham khảo:
- http://www.square63.com/metaprogramming-in-rails-the-internals/
- Metaprogramming Ruby 2: Program Like the Ruby Pros
All Rights Reserved