ActiveSupport: What You Didn't Know
Trong quá trình làm việc với Ruby on Rails, khi gặp phải vấn đề đặc biệt cần giải quyết, nếu như không tìm hiểu kỹ, ta sẽ dễ tự tay xây dựng và giải quyết mọi thứ trong khi bản thân framework đã có sẵn những công cụ hỗ trợ cho vấn đề đó. Vì vậy thay miệt mài tìm giải pháp, hãy lướt qua ActiveSupport
một lượt, rất có thể bạn sẽ tìm được ngay thứ mà mình cần.
ActiveSupport::CurrentAttributes
Với những trang web hỗ trợ authetication, nhất là khi sử dụng gem devise
bạn sẽ bắt gặp current_user
ở khắp nơi trong view hoặc controller. Có một vấn đề là current_user
không khả dụng ở trong các models hay services…Lúc này ActiveSupport::CurrentAttributes
có thể là một giải pháp dành cho bạn.
Theo định nghĩa, đây là một abstract class hỗ trợ singleton attribute cô lập theo thread. Nghĩa là nếu mỗi request được xử lý bởi một thread, thì bạn có thể truy cập attribute đó ở khắp mọi nơi đến khi request kết thúc. Trở lại với ví dụ trên, ta hãy so sánh trước và sau khi sử dụng current attributes:
- Trước khi sử dụng:
# app/presenters/post_presenter.rb
class PostPresenter
def initialize post, user
@post = post
@user = user
end
def current_user_post?
@post.user.eql?(@user)
end
end
Khi trả dữ liệu cho API:
# app/controllers/posts_controller.rb
def show
@presenter = PostPresenter.new(@post, current_user)
end
- Sau khi sử dụng:
class Current < ActiveSupport::CurrentAttributes
attribute :user
end
# app/controllers/application_controller.rb
before_action :set_current_user
def set_current_user
Current.user = current_user
end
# app/presenters/post_presenter.rb
class PostPresenter
def initialize post
@post = post
end
def current_user_post?
@post.user.eql?(Current.user)
end
end
# app/controllers/user_controller.rb
def show
@presenter = PostPresenter.new(@post)
end
Với những logic phức tạp, nhiều tầng code, đồng nghĩa với việc bạn phải truyền current_user
vào sâu bên trong, lúc đó bạn sẽ thấy được lợi ích từ việc sử dụng current attributes. Tuy nhiên, tính năng này chỉ có từ phiên bản Rails 5.2 trở lên.
ActiveSupport::Subscriber
Nếu như đã từng làm việc với Javascript, chắc bạn cũng không lạ gì với việc đăng ký và trigger sự kiện của một DOM. Rails cũng cung cấp một cơ chế tương tự như vậy và nó chính là ActiveSupport::Subscriber
. Trong quá trình chạy, Rails trigger rất nhiều sự kiện, ví dụ như:
start_processing.action_controller
: Trigger khi bắt đầu process controller action .process_action.action_controller
: Trigger sau khi đã process xong một action.sql.active_record
: Trigger sau khi ActiveRecord chạy một câu sql.
Và còn nhiều event khác nữa. Nhưng chúng ta có thể dùng nó vào việc gì
Bạn có thể sử dụng nó vào việc monitoring hệ thống. Hãy cùng xem qua ví dụ dưới đây:
module Monitoring
class QuerySubscriber < ActiveSupport::Subscriber
IGNORE_NAMES = ["SCHEMA", "EXPLAIN"]
SLOW_TIME = 1000
def sql event
return if event.duration <= SLOW_TIME
payload_name = event.payload[:name]
return if payload_name.blank? || IGNORE_NAMES.include?(payload_name)
event_data = event.payload.slice(:name, :sql)
report_slow_query(event_data)
end
private
def report_slow_quary event_data
Slack.push_message(:slow_query, event_data)
end
end
end
Monitoring::QuerySubscriber.attach_to(:active_record)
Ở trên, chúng ta đăng ký một subscriber lắng nghe sự kiện sql.active_record
. Với mỗi câu query được chạy, ta sẽ kiểm tra thời gian thực thi và report nếu như nó vượt quá 1s.
Bạn cũng có thể trigger event của riêng mình bằng việc sử dụng ActiveSupport::Notifications với cơ chế tương tự.
ActiveSupport::Callbacks
Với notifications hay subscribers, chúng sẽ được trigger tại một thời điểm thường là khi bắt đầu hay kết thúc một hành động nào đó. Callbacks cũng có phần tương đồng nhưng phạm vi của nó chỉ trong life cycle của một object. Ví dụ khi publish một bài post, bạn cần gửi notification cho followers, lúc này bạn nên sử dụng ActiveSupport::Notifications
. Còn nếu bạn cần tự động set description cho bài post trước khi nó được save thì đó là lúc bạn nên dùng callback.
Ngoài những callbacks mặc định của Rails, bạn có thể tự đăng ký callback bằng cách sử dụng ActiveSupport::Callbacks
. Hãy xem ví dụ dưới đây:
class Post
include ActiveSupport::Callbacks
define_callbacks :publish
def make_publish
run_callbacks :publish do
puts "Publish post"
end
end
set_callback :publish, :before, :log_before_publish
def log_before_publish
puts "Log before publish"
end
set_callback :publish, :after do |post|
puts "Log after publish ID: #{post.object_id}"
end
end
Để define callbacks bạn cần làm các bước sau:
- Khai báo event nào trong object sẽ hỗ trợ callbacks thông qua
define_callbacks
. - Cài đặt callbacks của events thông qua
set_callback
. Bạn có thể sử dụng instance method hoặc đơn giản là hơn là mộtProc
haylambda
. Các timming hỗ trợ bao gồmbefore
,arround
vàafter
. - Chạy các callback đã được đăng ký với events thông qua
run_callbacks
.
post = Post.new
post.make_publish
Output:
Log before publish
Publish post
Log after publish ID: 2379297800
ActiveSupport::StringInquirer
Đây là cách bạn có thể sử dụng để làm cho việc kiểm tra string trở nên dễ dàng và trong sáng hơn. Hãy xem ví dụ sau:
class Media
def initialize type
@type = type
end
def text?
@type == "text"
end
def image?
@type == "image"
end
def video?
@type == "photo"
end
end
media = Media.new("image")
media.image? # => true
media.text? # => false
Nhưng với ActiveSupport::StringInquirer
mọi thứ sẽ đơn giản hơn rất nhiều:
class Media
def initialize type
@type = ActiveSupport::StringInquirer.new(type)
end
delegate :text?, :image?, :video?, to: :@type
end
media = Media.new("image")
media.image? # => true
media.text? # => false
ActiveSupport::Duration
Khi bạn muốn xử lý các vấn đề liên quan đến một khoảng thời gian như countdown event, hiển thị deadline… ActiveSupport::Duration
sẽ là điều đầu tiên bạn nên nghĩ đến. Nó hỗ trợ hầu hết các yêu cầu của bạn liên quan đến durration.
- Bạn có thể hiển thị durration:
ActiveSupport::Duration.build(400.days)
# => 1 year, 1 month, 4 days, 7 hours, 41 minutes, and 42.0 seconds
- Nếu muốn lấy thông tin của durration:
ActiveSupport::Duration.build(400.days).parts
# => {:years=>1, :months=>1, :days=>4, :hours=>7, :minutes=>41, :seconds=>42.0}
Conclusion
Bản thân Rails đã hỗ trợ hầu hết các tính năng cần thiết cho việc phát triển một trang web cơ bản. Do đó, ứng dụng của ActiveSupport đôi khi bị lãng quên. Vì thế, nếu như gặp phải vấn đề với những hỗ trợ mặc định của Rails, bạn hãy tìm sự trợ giúp của ActiveSupport trước khi bắt tay vào xử lý mọi thứ.