Rails Autoloading and Reloading Constants
Bài đăng này đã không được cập nhật trong 6 năm
Giới thiệu
Với code Ruby bình thường bạn cần phải khai báo các file chứa các class phụ thuộc với lệnh require. Việc này cũng khá quen thuộc bởi vì hầu hết các ngôn ngữ đều như vậy
require 'application_controller'
require 'post'
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Còn với Rails đa số trường hợp bạn đều không thấy từ khóa require
nào cả. Có thể thấy Rails đã tối giản đi rất nhiều và giảm công sức cho người lập trình, co thể nói là thông mình
hơn với auto load. Với Rails ta chỉ cần viết như sau
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Vì một lí do nào đó mà bạn gọi đến các constants hoặc class ở ngoài mà không cần require một cách thủ công. Chúng ta sẽ cùng tìm hiểu cơ chế tự động load các hằng số (constants) trong rails như thế nào.
Hằng số (Constants)
Nesting
Các class hoặc module có thể được định nghĩa theo cấu trúc phân cấp (nested) hay còn gọi là namspaces
module XML
class SAXParser
# (1)
end
end
Trong ví dụ trên ruby sẽ tự động tạo ra một module là XML
đồng thời tạo thêm một class SAXParser
thuộc module này
[XML::SAXParser, XML]
Đoạn code sau cũng khá giống tuy nhiên có một chút khác biệt là XML cần được định nghĩa từ trước. Khi đó ta sẽ thêm một class mới là SAXParser thuộc về đối tượng XML
class XML::SAXParser
# (2)
en
Dưới đây là một trường hợp khá là đặc biệt về module
# 1
module X
module Y
end
end
# 2
module A
end
# 3
module X::Y
module A::B
end
module C
end
end
Bạn có thể thấy nó khá kì quái. Bạn nghĩ là có thể gọi module B theo cách sau X::Y::A::B
nhưng sự thực không phải vậy. Thậm chí bạn gọi X::Y::A
cũng thấy báo lỗi còn X::Y::C
thì gọi bình thường. Như vậy là sao?
Thực ra cũng không có gì là khó hiểu. Ta sẽ phân tích chi tiết như sau. Sau khi chạy xong # 1
ta có 2 module là X và X::Y sau # 2
ta có thêm A
. Ở đoạn code #3
ta mở module X::Y ra để thay đổi module này. Trong phần thân của module này ta có đoạn code
module A::B
end
Đoạn code này có nghĩa ta sẽ mở module A ra và thêm vào đó một module là B (bởi vì module A chưa có module B). module A này không liên quan gì đến module X::Y nên ta sẽ không thể truy cập nó với namespace từ module X::Y theo kiểu X::Y::A::B mà sẽ truy cập bằng cách gọi A::B. Có thể nói ngắn gọn hơn thực chất đây là lệnh khai báo thêm mới một module B vào module A được gọi ở trong phần thân của module X::Y
Định nghĩa class và module
class C
end
Ruby sẽ tạo ra một hằng số là C tham chiếu đến một đối tượng được lưu trữ dưới dạng class. Tên của class này cũng là C
class Project < ApplicationRecord
end
Sẽ tương đương với
Project = Class.new(ApplicationRecord)
Có thể bạn sẽ thắc mắc theo cách thứ 2 thì Project.name
sẽ có giá trị là gì?
Project.name # => "Project"
Như vậy theo cách thứ 2 Project.name vẫn ra kết quả đúng nhưng mong đợi là Project
. Nguyên nhân là do gán giá trị cho một hằng số có một quy luật đặc biệt xảy ra là: nếu một object bị gán cho một hằng số là một class hoặc module chưa có tên thì tên của nó chính là tên của hằng số.
A = Class.new
A.name # => A
B = A
B.name # => A
C = A.dup
C.name # => C
Với module cũng tương tự
A = Module.new
A.name # => A
B = A
B.name # => A
C = A.dup
C.name # => C
Rails autoloading
Rails tự động load các files với Kernel#load
khi config.cache_classes
là false và dùng Kernel#require
trong trường hợp config.cache_classes
là true (môi trường production)
Với Kernel#load cho phép Rails cập nhật lại file mỗi khi nội dung file thay đổi.
Cơ chế load của Rails
Ruby tìm kiếm file cài đặt cho các class module ở trong thư mục $LOAD_PATH. Nó tìm các file có đuôi rb
, so
, o
, dll
ở trong các thư mục trong $LOAD_PATH và dùng lại ngay khi tìm thấy file tương ứng, trong trường hợp không tìm thấy file tương ứng nó sẽ raise ra lỗi LoadError
Bạn có thể thay đổi $LOAD_PATH với config.autoload_paths. Ví dụ bạn thêm thư mục lib, muốn code ở trong thư mục này được tự động load thì bạn cần thêm vào config/application.rb như sau
config.autoload_paths << "#{Rails.root}/lib"
Bạn có thể kiểm tra giá trị hiện tại của autoload_paths với lệnh sau
bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths'
Cơ chế autoload của Rails
Ta lấy ví dụ một đoạn code như sau
class PostsController < ApplicationController
def index
@posts = Post.all
end
end
Ta có 3 hằng số là PostsController
, ApplicationController
và Post
Hằng số ở sau từ khóa module
hoặc class
trong trường hợp này là PostsController
. Ruby sẽ kiểm tra hằng số này đã có chưa nếu chưa có Ruby sẽ tạo mới hằng số này còn có rồi thì sẽ mở lại hằng số này ra và sửa lại nó.
Các hằng số phụ thuộc khác như ApplicationController
, Post
. Trong trường hợp hằng số này chưa được load (chưa tồn tại), Rails sẽ tự động load hằng số này từ file hiện tại và các thư mục trong autoload_paths. Nếu trường hợp không tìm thấy sẽ raise ra lỗi LoadError
Ví dụ với ApplicationController ở trong file này không hề định nghĩa ApplicationController và hằng số này cũng chưa được load trước đó. Rails sẽ tự động tìm kiếm hằng số này trong các file cài tương ứng ở các đường dẫn cài đặt trong config.autoload_paths ví dụ (app/assets/application_controller.rb và app/controllers/application_controller.rb)
Tự động load với namespaces
Ví dụ
module Admin
class BaseController < ApplicationController
@@all_roles = Role.all
end
end
Hằng số Role sẽ tìm kiếm hằng số Role theo namespace với thứ tự như sau
Admin::BaseController::Role
Admin::Role
Role
Tương ứng với các hằng số theo namespace kia Rails sẽ tìm kiếm các file tương ứng với namespace như sau để load hằng số này
admin/base_controller/role.rb
admin/role.rb
role.rb
Tải lại các hằng số (Constant Reloading)
Khi config.cache_classes is false
Rails sẽ tự động tải lại nội dung file mỗi khi nội dung file thay đổi.
Tuy nhiên một số trường hợp Rails không tự động load lại code, ví dụ như trong trường hợp bạn chạy rails console bạn cần chạy lệnh reload!
để tải lại code mới được cập nhật.
Tham khảo
All rights reserved