Intro
Thời gian qua trong dự án đang gặp một tính huống cần thêm hash code tag vào tất cả các file của có đuổi .rb
như các bạn biết một dự án sẽ có file code vừa phải cũng tầm nghìn file rồi, trong đó cần loại đi một số file kiểu db/schema.rb
chẳng hạn vì file này sẽ không giữ lại hash tag đó mỗi khi chạy rails db:migrate
xong.
Trong bài viết này đi qua một số phần để làm việc với File, Dir, cách viết rake task, xử lý xong xong trong Ruby.
IO trong Ruby
Input/Output thường gọi tắt là I/O, là một thuật ngữ bao quát một cách mà một máy tính tương tác với thế giới. Màn hình, bàn phím, các file và mạng tất cả hình thành từ I/O. Dữ liệu từ các device được gửi tới và từ các chương trình kiểu stream của các ký tự/bytes. Ruby có sẵn một số subclass của IO để dùng cho một số mục đích cụ theo kiểu IO:
File
File
là một subclass rất phổ biến của IO
. File cho phép read/write các file mà không cần làm việc phức tạp với các file descriptors. Với class File
có thêm một số phương thức tiện lợi như File#size
, File#chmod
, File#path
,....
The Sockets
Đa số socket class kế thừa từ IO
.
Các bạn có thể tham khảo: TCPSocket, UDPSocket, UNIXSocket, Socket
StringIO
StringIO cho phép chuỗi string có hành vi như IO
. Nó có ích khi muốn truyền string vào các hệ thống sử dụng streams.
Temfile
Temfile là một class không kế thừa từ IO
thay vì đó nó thực thực thi lớp giao diện của class File
và xử lý các temporary file. Nó có thể truyền cho bất kỳ đối tượng mà sử dụng kiểu đối tượng IO
.
Có thể tham khảo thêm ở bài post.
Dir trong Ruby
Các đối tượng của class Dir
là các directory streams biểu diễn các thư mục trong file system. Nó cung cấp một số cách để liệt kể thư mục và nội dung của chúng.
Trong ruby có thể thao tác Dir.mkdir("testing")
, Dir.pwd
tương tự trong command line mkdir testing
, pwd
.
Truờng hợp có lỗi sẽ trả về một số error sau: Errno::EEXIST
thư mục đã tồn tại, Errno::EACCES
quyền bị từ chối, Errno::ENOENT
khi cố tình tạo một thư mục dưới cấp một thư mục chưa tồn tại.
Một điều thú vị là Dir.glob cho phép chọn những file cần sử dụng/tìm kiếm. eg. Dir.glob("**/*.rb")
sẽ trả về một mảng với tất cả các file có đuổi .rb
.
Rake trong Ruby
Chi tiết bạn hãy tham khảo Tìm hiểu và cách sử dụng Rake trong Ruby
Coding
Processor
Ở đây sẽ tạo một class gọi Processor
để xử lý check file đã có hash code tag hay chưa nếu chưa thì thêm vào ở line đầu tiên.
class Processor
attr_reader :code, :files, :sample_format, :sample_format_regex
def initialize code, files
@code = code
@sample_format = "# hash_code: #{@code}"
@sample_format_regex = %r{#{@code}}
@files = files
@modified_files = []
end
def execute
files.each do |file|
# Next if hash code existed
next if sample_format == IO.binread(file, sample_format.length).strip
File.open(file, "r+") do |f|
content = f.read
content.insert 0, "#{sample_format}\n"
f.seek 0, IO::SEEK_SET
f.write content
end
modified_files << file
end
end
end
- Khởi tạo nhận biến
code, files
truyền vào, tạosample_format_regex
để check trong code,modified_files
tổng hợp lại các file thay đổi. - Trong hàm
execute
loop các file- đọc mỗi file bằng
binary mode
và loại khoảng trắng với hàmString#strip
để so sánh với regex đã khởi tạo trên next
khi đã tồn tại hash key tag- Khi chưa có hash key tag sẽ mở file đó,
content
nội dung file đã đọc là string sau đó chèn bằng hàminsert
tại vị trí 0 f.seek 0, IO::SEEK_SET
để seek về offset 0 trước khi ghi vào file- file được ghi lại với nội dung
content
- đọc mỗi file bằng
- Thu lại những file thay đổi vào
modified_files
Như các bạn biết loop trong ruby thực thi rất chậm vậy có cách để tăng tốc không? Cóa đó là sử dụng Mutex và Thread
Sau khi thêm ingredient vào nó sẽ trở thành
def initialize code, files
...
# Calculate batch size for feed to 100 threads, only use 1 thread if less
@batch_size = files.size >= 100 ? (files.size / 100.0).floor : 1
end
def execute
# To coordinate access to shared data from multiple concurrent threads.
m = Mutex.new
threads = files.in_groups_of(batch_size, false).map do |g_files|
Thread.new(g_files) do |batch_files|
batch_files.map do |file|
...
m.synchronize { modified_files << file }
end
end
end
threads.each &:join
- Tạo 100 thread và chia mỗi thread theo số lượng file gọi là
batch_size
(có thể thay đổi cách tính tùy ý) Mutex
để chia sẻ truy cấp giữa nhiều thread chomodified_files
- join để đình chỉ main thread cho đến các sub thread
Thread.new
thực thi xong.
Rake task
namespace :file_header do
desc "Add hash code tag to ruby files"
task start: :environment do
p "Start"
files = Dir["{app,config,db,lib,spec}/**/*.rb"] - Dir["db/schema.rb"]
processor = Processor.new("XYZ", files)
processor.execute
p "#{processor.modified_files.size} files changed"
end
end
Chỉ cần nhập bundle exec rake file_header:start
các file sẽ thêm hash key tag vào.
Đến đây là hết cảm ơn các bạn đã đọc.