Auto thay đổi STATE bằng StateMachine và sidekiq rails 4
I. Các khái niệm 1. Sidekiq Sidekiq là một gem hỗ trỡ xử lý ngầm dưới background mạnh mẽ cho Ruby. Nó nhằm mục đích là đơn giản để tích hợp với bất kỳ ứng dụng Rails hiện đại và hiệu suất cao hơn nhiều so với các giải pháp hiện có khác. Link github Link hướng dẫn cài đặt và sử dụng ...
I. Các khái niệm
1. Sidekiq
Sidekiq là một gem hỗ trỡ xử lý ngầm dưới background mạnh mẽ cho Ruby. Nó nhằm mục đích là đơn giản để tích hợp với bất kỳ ứng dụng Rails hiện đại và hiệu suất cao hơn nhiều so với các giải pháp hiện có khác.
-
Link github
-
Link hướng dẫn cài đặt và sử dụng
2. State machine
StateMachine là 1 gem được tạo ra để đơn giản hóa quản lý các hành vi của một Object. Bình thường, trạng thái của một Object lưu giữ bằng cách tạo ra nhiều thuộc tính và các hành động xử lý dựa trên các giá trị. Điều này có thể trở nên cồng kềnh và khó khăn để duy trì khi sự phức tạp của các Object bắt đầu tăng. StateMachine sẽ giúp bạn xử lý các hành động phức tạp trên, bằng cơ chế rất đơn giản.
- Link github
II. Kết hợp giữa sidekiq và state_machine
Khi chúng ta muốn 1 hành động nào đó xảy ra một cách tự động hóa.
VD cụ thể: Ta có 1 User, ta muốn tạo ra hành động công việc hoạt động sau 10s khi User bấm nút create nó, sau 1 khoảng thời gian sau (thời gian được quy định bơi User) thì hoạt động đó trở về trạng thái stop và cộng điểm cho User.
1. Cài đặt state_machine
Nếu chúng ta không sử dụng gem thì phải viết các hàm:
class Test < ActiveRecord::Base belongs_to :user def start? state == "ongoing" end def pending end def start update! state: :ongoing end def after_start # Đây là chỗ xử lý tự động để khi bấm nút start sau khoảng thời gian state sẽ tự động về trạng thái stop end def stop end end ``` Khi sử dụng gem chúng ta sẽ được code gọn hợn, đẹp hơn và dễ hiểu hơn ```ruby class Test < ActiveRecord::Base belongs_to :user state_machine :state, initial: :creating do event :prepare do transition creating: :pending end event :start do transition pending: :ongoing end event :stop do transition all => :ended end after_transition on: :pending do |test| end end ``` Code ở trên có nghĩa là khi chúng ta tạo ra 1 test thì `state` mặc định của nó sẽ là `creating` và các event bên dưới là khi chúng ta có 1 record `@test` ta chỉ cần `@test.start` thì `state` của nó sẽ tự động chuyển về `ongoing` #### 2. Cài state_machine với sidekiq Ở trên ta vừa lấy ví dụ với `User` có nhiều bài `tests`. Ta tạo 1 project như sau - Giao diện: ![home2.png](/uploads/8a6bec80-3e98-4028-a3e6-37239d4ab8e9.png) Ta có 1 `user` đang có 10 point, và list các `tests` chưa có cái nào, giờ ta sẽ tạo nó, đồng thời chuyển nó về trạng thái `pending`, ở form nhập vào chúng ta sẽ có các thuộc tính `start_after`, `during`, `name`. trước khi tạo thì ta bật `terminal` cd đến project gõ `sidekiq` để bật server của sidekiq lên ;). ![modal.png](/uploads/ea501116-9e4e-4e8a-9c68-3d544e58e87f.png) - `start_after` là thời gian sau khi bấm nút `Add` bài `test` đó sẽ được bắt đầu, thời điểm bắt đầu thì `test.state = ongoing` - `during` là thời gian diễn ra bài `test` đó, lúc hết thời gian thì `test.state = ended` Tất cả các trạng thái sẽ được chuyển tự động bằng phương thức `after_transition` của `state_machine`, trong phương thức này ta sẽ lập lịch cho nó tự động chuyển trạng thái bằng `sidekiq`. - Ta sẽ tạo ra file `app/workers/test_worker.rb`: ```ruby class TestWorker include Sidekiq::Worker def perform id, action TestWorker.delete_schedule id, action TestWorker.delete_schedule id, action test = Test.find_by id: id test.try action.to_sym test.user.increment! :point, 10 if action.to_s == "stop" end class << self def delete_schedule id, action jobs = Sidekiq::ScheduledSet.new.select do |retri| retri.klass == name && retri.item["args"] == [id, action] end jobs.each(&:delete) end end end ``` Như vậy chúng ta đã khai báo 1 lịch cho `Test` và thời điểm đặt lịch như thế nào thì ta sẽ chuyển đến model `Test`, `app/models/test.rb`: ```ruby class Test < ActiveRecord::Base attr_accessor :start_after, :during belongs_to :user state_machine :state, initial: :creating do # ... after_transition on: :prepare do |test| TestWorker.delete_schedule test.id, :start TestWorker.perform_at(test.starts_at, test.id, :start) end after_transition on: :start do |test| TestWorker.delete_schedule test.id, :stop TestWorker.perform_at(test.ends_at, test.id, :stop) end end end ``` Như trên có nghĩa là sau khi `test` chuyển đến trạng thái `pending` thì sẽ đặt lịch cho `sidekiq` đã được định nghĩa ở trên, thực hiện hành động `start` cho `test` đó tại thời điểm `test.starts_at` được ghi bởi lúc tạo test. -> Như vậy chúng ta đã cho `test.state` auto thay đổi từ lúc tạo ra `test` bằng cách sử dụng state_machine và sidekiq - [Link video demo](https://youtu.be/p0bC57_OLas) ## III.