Tìm hiểu về Macros trong Ruby
Bài đăng này đã không được cập nhật trong 5 năm
1. Giới thiệu
Trong Rails bạn có thể thường xuyên làm việc với một số các class method như has_many
, belongs_to
,... những class method đó còn được gọi chung là Macros
. Ví dụ:
class Movie < ActiveRecord::Base
has_many :reviews
end
class Project < ActiveRecord::Base
has_many :tasks
end
Rất quen đúng không
Đối với những người bắt đầu sử dụng Rails (và Ruby) có thể khai báo các đoạn mã trên là do sự hỗ trợ của Rails
. Nhưng sự thực ở đây là không có sự hỗ trợ nào cả, tất cả chỉ là mã code của Ruby
, Ruby
đã làm cho việc lập trình theo kiểu khai báo này dễ dàng hơn cho chúng ta.
Bài viết này sẽ giúp các bạn hiểu được vì sao lại có thể khai báo như vậy, qua đó giúp các bạn hiểu hơn về ngôn ngữ lập trình Ruby
.
2. Làm rõ vấn đề.
Để hiểu rõ đoạn code trên phần giới thiệu các bạn cần nắm rõ một số các khái niệm cơ bản trong Ruby
.
2.1 Singleton Method của một Object
Dưới đây là một đối tượng String
và chúng ta gọi phương thức upcase
được định nghĩa trong lớp String
:
dog1 = "Rosco"
puts dog1.upcase # => ROSCO
Tương tự, ta có thể gọi đến bất cứ method
nào thuộc lớp string
theo cách trên, tuy nhiên trong ruby
ta còn cho phép ta có thể định nghĩa một method
trên một object
cụ thể.
Ví dụ:
def dog1.hunt
puts "WOOF!"
end
dog1.hunt # => WOOF!
Đối tượng dog1
gọi tới phương thức hunt
, và kết quả là WOOF!
Ta thử gọi phương thức hunt
tứ một đối tượng khác:
dog2 = "Snoopy"
dog2.hunt # => undefined method `hunt`
Việc gọi hunt trên đối tượng dog2 là không thế.
Suy ra, singleton method là method chỉ được định nghĩa cho 1 object cụ thể.
Như vậy là các bạn đã hiểu về singleton method, và đây cũng là một hàm cơ bản để xây dựng lên các class method như has-many
đó .
2.2 Class cũng là Object
Đây là một class Movie
class Movie
end
Trong Ruby, class cũng là object.
p Movie.class # => Class
Tất nhiên, ta cũng có thể xem object_id của 1 class"
p Movie.class.object_id # => 70233956488680
Thực tế thì tên của class Movie
là một constant
và nó tham chiếu đến object class
.
2.3 Singleton Method của Class
Như đã nói mỗi class
trong Ruby
là một object, vậy thì ta có thể làm bất cứ thứ gì với chúng như với một object thông thường vầy. Ví dụ, ta có thể định nghĩa một singleton method
cho object class
mà được reference qua Movie
, ví dụ:
movie_class = Movie
def movie_class.my_class_method
puts "Running class method..."
end
my_class_method
ở đây sẽ là một singleton method
định nghĩa cho object Movie
.
movie_class.my_class_method # => "Running class method..."
Vì vậy, về cơ bản, điều đó giống như những gì chúng ta đã làm với đối tượng dog1
trước đó. Trong trường hợp này, ta định nghĩa một singleton method
cho object Class
.
Để cho đoạn code trên trở nên ngắn gọn hơn ta có thể viết lại như sau:
class Movie
def Movie.my_class_method
puts "Running class method..."
end
end
Nhìn quen thuộc hơn rồi đúng không?
Từ nhưng điều trên ta thấy : class method thực chất chỉ là một singleton method được định nghĩa cho object class.
2.4 Các class cũng là các mã thực thi.
Thêm một điểu chúng ta cần phải hiểu là class definitions are executable code
, theo dõi đoạn code dưới đây:
puts "Before class definition"
class Movie
puts "Inside class definition"
def Movie.my_class_method
puts "Running class method..."
end
end
puts "After class definition"
Movie.my_class_method
Và kết quả là:
Before class definition
Inside class definition
After class definition
Running class method...
Theo dõi kết quả thì chúng ta thấy code có thể thực thi ngay trong quá trình định nghĩa class
. Hơn thế nữa ta cũng có thể thực thi nó trong cả quá trình định nghĩa method luôn
Ví dụ:
puts "Before class definition"
class Movie
puts "Inside class definition"
def Movie.my_class_method
puts "Running class method..."
end
Movie.my_class_method
end
puts "After class definition"
Kết quả:
Before class definition
Inside class definition
Running class method...
After class definition
Tuy nhiên Ruby sẽ gán biến self tới class object đang được định nghĩa đó nên ta có thể sử dụng cách sau:
puts "Inside class definition of #{self}"
Và kết quả là:
Before class definition
Inside class definition of Movie
Running class method...
After class definition
self
ở đây được gán cho class object hiện tại đang được định nghĩa, trong trường hợp này là class object Movie
.
Ta có thể sửa lại đoạn code trên như sau:
def self.my_class_method
puts "Running class method..."
end
self.my_class_method
Đến đây thì self.my_class_method bắt đầu nhìn giống với hàm has_many , ngoài việc nó thừa ra từ self
!
Nhưng ta cũng có thể bỏ luôn self
vì trong trường hợp này Ruby sẽ có thể tự ngầm hiểu self
là receiver.
my_class_method
Oke, bây giờ chúng ta thực hiện thay đổi tên phương thức thành has_many
xem nó có hoạt động tốt không nhé
class Movie
def self.has_many(name)
puts "#{self} has many #{name}"
end
has_many :reviews
end
Chú ý rằng has_many
nhận name
là một đối số của nó, vì vậy việc gọi has_many :reviews
sẽ truyền :reviews
như là đối số cho has_many
Và kết quả nó vẫn hoạt động.
Movie has many reviews
2.5 Define Method
Oke, vậy là phương thức has_many
đã được xây dựng, tuy nhiên như chúng ta biết trong Rails
hàm has_many
có tác dụng khởi tạo một assocation
kèm theo các method phụ trợ đi kèm với nó.
Ví dụ, ta có thể khởi tạo method reviews
mà sẽ trả về các review
được kết nối tới Movie
:
movie = Movie.new
movie.reviews # => undefined method
Nếu là trong Rails, hàm này sẽ trả về mảng các review
có kết nối tới movie
.
Ở đây ta sẽ làm như thế này:
def self.has_many(name)
puts "#{self} has many #{name}"
def reviews
puts "SELECT * FROM reviews WHERE..."
puts "Returning reviews..."
[]
end
end
Tuy nhiên nếu làm như thế thế thì ta không thể thêm các association khác, ví dụ như genres chẳng hạn.
Vì thế ta cần phải định nghĩa dynamically một method cho mỗi association: trong trường hợp này là một method mang tên reviews
và một mang tên genres
. Ta sẽ không biết được tên chính xác của chúng cho tới khi class được định nghĩa.
Để thực hiện được điều này, ta có thể dùng define_method
:
def self.has_many(name)
puts "#{self} has many #{name}"
define_method(name) do
puts "SELECT * FROM #{name} WHERE..."
puts "Returning #{name}..."
[]
end
end
define_method
nhận đối số là tên của method sẽ được khởi tạo, cùng với một block mà sẽ là body của method đó. define_method
luôn định nghĩa instance method trong receiver, trong trường hợp này là object Movie
. Vì vậy đến cuối cùng ta sẽ nhận được instance method reviews
cho class Movie
.
Chạy tử chương trình ta có method reviews
đã được định nghĩa:
Movie has many reviews
SELECT * FROM reviews WHERE...
Returning reviews..
Oke bây giờ ta có thể gọi phương thức has_many
bao nhiêu lần tùy thích.
class Movie < ActiveRecord::Base
has_many :reviews
has_many :genres
end
movie.reviews
movie.genres
Hiện tại thì has_many
chỉ dành cho Movie
mà thôi. Nhưng trong rails has_many
có thể dùng được cho nhiều class. Và ta có thể thực hiện điều đó bằng kế thừa.
2.6 Class Method Inheritance
Để share has_many sử dụng kế thừa, trước tiên chúng ta sẽ định nghĩa nó trong class Base
, bên trong là một module có tên là ActiveRecord
(để nó giống với Rails).
module ActiveRecord
class Base
def self.has_many(name)
puts "#{self} has many #{name}"
define_method(name) do
puts "SELECT * FROM #{name}..."
puts "Returning #{name}..."
end
end
end
end
Sau đó, class Movie
có thể kế thừa từ lớp ActiveRecord :: Base
class Movie < ActiveRecord::Base
has_many :reviews
has_many :genres
end
Và mọi thứ hoạt động:
Movie has many reviews
Movie has many genres
SELECT * FROM reviews WHERE...
Returning reviews...
SELECT * FROM genres WHERE...
Returning genres...
Lưu ý: giá trị của self
ở đây là Movie
class nhé.
Bây giờ khi chúng ta kế thừa từ ActiveRecord::Base
thì bất cứ class nào cũng có thể sử dụng has_many
method.
Ví dụ: ta có một class Project
class Project < ActiveRecord::Base
has_many :tasks
end
project = Project.new
project.tasks
Kết qủa là:
Project has many tasks
SELECT * FROM tasks WHERE...
Returning tasks...
3. Tổng kết
Vậy là ta đã kết thúc bài viết ở đây với việc implement thành công method has_many
mà ta muốn.
class Movie < ActiveRecord::Base
has_many :reviews
has_many :genres
end
class Project < ActiveRecord::Base
has_many :tasks
end
Thông qua bài viết này hi vọng các bạn đang bắt đầu với Ruby on Rails
có thêm các kiến thức liên quan Macros
để thực hiện tốt các dự án trong tương lai .
Cảm ơn!
Nguồn dịch: https://pragmaticstudio.com/tutorials/ruby-macros
All rights reserved