logo

Đang load dữ liệu

logo devmaster

VIỆN CÔNG NGHỆ VÀ ĐÀO TẠO DEVMASTER

Đào tạo - Phần mềm - Cho thuê nhân sự

  • 0969.609.003
  • 0978.611.889
  • Trang chủ
  • Các khoá đào tạo
    • Chuyên đề WEB - PHP

      • Lập trình web với HTML5 - CSS3- JQuery - Bootstrap - Ajax - [36 giờ]
      • Lập trình web frontend - reactjs - [75 giờ]
      • Lập trình web với mã nguồn mở PHP&MYSQL - PHP FRAMEWORK [126 giờ]

      Chuyên đề Mobile

      • Lập trình Games/Apps trên nền tảng Android - [120 giờ]
      • Lập trình Games/Apps trên nền tảng IOS - [120 giờ]

      Chuyên đề JAVA

      • Ngôn ngữ lập trình hướng đối tượng với java - [40 giờ]
      • Lập trình ứng dụng với java - [100 giờ]
      • Lập trình web site với java framework (JPA, HIBERNATE, SPRING MVC, SPRINGBOOT) - [276 giờ]

      Chuyên đề NETWORK/SECURITY

      • Khoá học Quản trị hạ tầng mạng CCNA v6 - [72 giờ]
      • Khoá học quản trị hệ thống với Windows SERVER 2012- [72 giờ]
      • Chuyên gia bảo mật hệ thống CompTIA + - [110 giờ]

      Chuyên đề .NET

      • Nền tảng lập trình hướng đối tượng với C# - [40 giờ]
      • Lập trình ứng dụng WINDOWS FORM - [100 giờ]
      • Lập trình Web với ASP.NET MVC 5, WebAPI - [145 giờ]

      Chuyên đề khác

      • Ngôn ngũ lập trình C/C++ - [80 giờ]
  • Lập trình cho trẻ em
  • Dịch vụ
    • Đào tạo theo như cầu
    • Cung cấp thiết bị - Phần mềm
    • Tư vấn - Thiết kế mạng hạ tầng
    • Tư vấn - Triển khai dịch vụ mạng
    • Tư vấn - Tư vấn, triển khai giám sát hệ thống
    • Thực tập dự án
  • Lịch khai giảng
  • Tin tức
    • Tin tức và sự kiện
    • Tin hoạt động
    • Tin công nghệ
    • Hội thảo, workshop, Codecam
    • Thông tin việc làm
    • Cẩm nang chia sẻ kiến thức
  • Tiện ích
  • Liên hệ

Tin tức và sự kiện

Oct - 2018

12

Ruby Gem AASM & Giải quyết bài toán chuyển trạng thái phức tạp dễ như trở bàn tay

Tin tức và sự kiện

Ruby Gem AASM – Giải quyết bài toán chuyển trạng thái phức tạp dễ như trở bàn tay

 

Bài toán

Đã bao giờ bạn gặp tình huống phải xử lý việc chuyển trạng thái của các đối tượng, mà việc thay đổi trạng thái ấy có tính ràng buộc, có điều kiện, lại kèm theo một đống hook cần phải thực hiện với nó. Ví dụ một khóa học đang init (khởi tạo) bạn muốn cho nó sẵn sàng chạy thì chuyển trạng thái pending (chờ), sau khi khai giảng khóa học thì trạng thái cần chuyển sang in_progress (đang chạy), muốn kết thúc khóa học thì phải chuyển về trạng thái finished (kết thúc), muốn đóng khóa học trước khi kết thúc thì phải đưa trạng thái về closed (đóng). Ở đây về tính logic, rõ ràng bạn chỉ có thể cho chạy lớp học khi lớp đó đang chờ hoặc đang đóng. Bạn có thể kết thúc hay đóng, hoặc cho một lớp học vào trạng thái chờ nếu lớp đó đang chạy. Thêm nữa, mỗi khi chuyển trạng thái của khóa học, thông báo sẽ được gửi đến những người có vai trò nhất định trong khóa học đó (ví dụ như trainer và trainee cùng với giáo vụ). Khi kết thúc khóa học phải lưu lại thông tin về kết quả học tập của trainee.v.v Nếu bạn đang cố gắng thực hiện bài toán này bằng cách tạo ra một đống hàm, một chuỗi các biểu thức điều kiện và tạo ra callback ở khắp mọi nơi, thì mình tin rằng code của bạn sẽ trở thành một đống mess và chính bạn cũng tự nhận thấy rằng đó không phải là cách làm đúng.

Đây là lúc Máy trạng thái (State machine) xuất hiện và thể hiện những đặc điểm lợi thế của mình.

Máy trạng thái là một mô hình tính toán toán học. Nó là một máy trừu tượng luôn có trạng thái nằm trong tổng hữu hạng các trạng thái tại bất kỳ thời điểm nào. Máy trạng thái hữu hạn có thể chuyển từ trạng thái này sang trạng thái khác để phù hợp với đầu vào; sự thay đổi này được gọi là quá trình chuyển đổi. Máy trạng thái hữu hạn được xác định bởi danh sách các trạng thái của nó, trạng thái khởi đầu, và các điều kiện cho từng sự chuyển đổi trạng thái.

Bài toán ví dụ ở trên có thể được diễn tả lại bằng máy trạng thái như hình:

Trong đó:

  • init, pending, in_progress, finished, closed là các trạng thái của khóa học
  • ready, start, stop, finish, close là các sự kiện (event). Các sự kiện này phát sinh khi nhận các input như click lên button, hoặc được kích hoạt bằng job… Các sự kiện sẽ gây ra sự thay đổi trạng thái (ví dụ sự kiện start sẽ là từ pending -> in_progress), còn được gọi là quá trình chuyển đổi (transition)

Phần tiếp theo mình sẽ giới thiệu về việc áp dụng máy trạng thái để giải quyết bài toán chuyển đổi phức tạp này với gem aasm.

Gem aasm

Gem này chứa gói AASM, một thư viện cho phép chúng ta thêm vào các class của Ruby một máy trạng thái hữu hạn (finite-state machine FSM). AASM là viết tắt của plugin acts_as_state_machine trước đây, hiện nay đã không còn sử dụng riêng cho ActiveRecord mà còn được tích hợp cho nhiều ORM khác. Một điều chắc chắn là nó có thể sử dụng cho bất cứ Ruby class nào dù cho parent class có là gì đi nữa.

Cài đặt gem

Bạn có thể cài đặt thủ công từ RubyGems.org

1
2
3
 
% gem install aasm
 

Hoặc là sử dụng Bundler

1
2
3
4
 
# Gemfile
gem 'aasm'
 

 

Generators

Sau khi cài đặt xong bạn có thể sử dụng generator

1
2
3
 
% rails generate aasm NAME [COLUMN_NAME]
 

Nếu bạn đã có một trường để lưu trạng thái của model rồi thì không cần thực hiện bước này nữa. Nếu chưa thì bạn cần thực hiện câu lệnh trên và thay NAME là tên model. COLUMN_NAME là tên của trường trạng thái bạn cần dùng, có thể tùy chọn, vì mặc định nếu để trống thì nó sẽ được đăt là “aasm_state”. Câu lệnh sẽ sinh ra model (nếu nó chưa tồn tại) và generate tự động đoạn block aasm (bạn sẽ thấy ở phần sau). Nếu bạn sử dụng Active Record thì nó sẽ đồng thời tạo một file migration để thêm trường trạng thái vào trong bảng.

Cách dùng

Giờ ta sẽ áp dụng máy trạng thái sử dụng gem aasm, bằng cách include module AASM, định nghĩa các state (trạng thái), events (sự kiện) cùng với các transitions (chuyển dịch) tương ứng.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 
class Course
  include AASM
 
  aasm do
    state :init, initial: true
    state :pending, :in_progress, :finished, :closed
 
    event :ready do
      transitions from: :init, to: :pending
    end
 
    event :start do
      transitions from: :pending, to: :in_progress
    end
 
    event :stop do
      transitions from: :in_progress, to: :pending
    end
    
    event :finish do
      transitions from: :in_progress, to: :finished
    end
    
    event :close do
      transitions from: :pending, to: :closed
      transitions from: :in_progress, to: :closed
      # có thể viết gộp lại như dưới đây
      # transitions from: [:pending, :in_progress], to: :closed
    end
  end
end
 

Từ đây bạn có thể nhận thấy rằng chúng ta đang khai báo một máy trạng thái với khả năng cung cấp một cơ chế để quản lý ràng buộc các trạng thái, các sự kiện và chuyển đổi rất rõ ràng và tường minh. Khối lệnh trên sẽ cung cấp cho class Course một vài public methods như sau:

1
2
3
4
5
6
7
8
9
10
 
course = Course.new
course.init?       # => true
course.may_ready?  # => true
course.ready
course.pending?    # => true
course.init?       # => false
course.may_ready?  # => false
course.ready       # => raises AASM::InvalidTransition
 

Khá là dễ để hiểu được ý nghĩa của các phương thức này, nên mình sẽ không giải thích quá nhiều. Để ý ở dòng cuối cùng, khi course đang ở trạng thái pending thì nó không thể thực hiện event ready, theo mặc định thì việc gọi đến một sự kiện mà không được phép sẽ raise ra lỗi AASM::InvalidTransition.

Tuy nhiên nếu bạn không thích exceptions mà muốn kết quả đơn giản là true hay false, thì chỉ cần thêm:

1
2
3
4
5
6
7
8
 
class Course
  ...
  aasm whiny_transitions: false do
    ...
  end
end
 

whiny có nghĩa là càu nhàu, như vậy là bạn hiểu ý của option này là gì rồi đấy 😄. Kết quả sau trả về sẽ như chúng ta muốn:

1
2
3
4
5
 
course.pending?    # => true
course.may_ready?  # => false
course.ready       # => false
 

Khi chạy một event, bạn có thể truyền vào phương thức của nó một block, và block ấy sẽ được gọi chỉ khi transition thành công:

1
2
3
4
5
 
course.ready do
  notify_all_users  # ví dụ như vậy
end
 

 

Callbacks

Callbacks là cách tuyệt vời để thực hiện các tác vụ đi kèm với quá trình chuyển đổi trạng thái. Bạn có thể định nghĩa callbacks cho events, transitions hoặc states dưới dạng các phương thức, Procs hay classes. Callbacks được gọi tại các thời điểm khác nhau trong vòng đời của quá trình chuyển đổi trạng thái. Bảng sau mô tả một life cycle hoàn chỉnh kèm theo các callback được sắp xếp theo thứ tự tương ứng:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
 
begin
  event           before_all_events
  event           before
  event           guards
  transition      guards
  old_state       before_exit
  old_state       exit
                  after_all_transitions
  transition      after
  new_state       before_enter
  new_state       enter
  ...update state...
  event           before_success      # if persist successful
  transition      success             # if persist successful
  event           success             # if persist successful
  old_state       after_exit
  new_state       after_enter
  event           after
  event           after_all_events
rescue
  event           error
  event           error_on_all_events
ensure
  event           ensure
  event           ensure_on_all_events
end
 

Giờ chúng ta sẽ thử áp dụng vào bài toán mẫu để khiến nó trở nên thực tế hơn một chút.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
 
class Course
  include AASM
 
  aasm do
    state :init, initial: true
    state :pending, before_enter: :save_progress
    state :in_progress, :finished, :closed
 
    after_all_events Proc.new { |*args| notify_somebody(*args) }
 
    event :ready do
      transitions from: :init, to: :pending
    end
 
    event :start do
      transitions from: :pending, to: :in_progress
    end
 
    event :stop do
      transitions from: :in_progress, to: :pending
    end
    
    event :finish, success: :dump_trainee_data do
      transitions from: :in_progress, to: :finished
    end
    
    event :close do
      transitions from: :pending, to: :closed
      transitions from: :in_progress, to: :closed
    end
  end
  
  private
  
  # Lưu quá trình học
  def save_progress
    ...
  end
  
  # Lưu dữ liệu học của trainee
  def dump_trainee_data
    ...
  end
  
  # Gửi thông báo đến đối tượng nào đó
  def notify_somebody(subjects)
    ...
  end
end
 

OK, ví dụ vài callbacks như vậy thôi nhé. Dưới đây là giải thích ngắn gọn về cách dùng:

  • state :pending, before_enter: :save_progress -> :save_progress thực hiện khi course bắt đầu vào trạng thái :pending.
  • event :finish, success: :dump_trainee_data -> :dump_trainee_data thực hiện khi course thực hiện thành công event :finish
  • after_all_events Proc.new { |*args| notify_somebody(*args) } -> callback dạng proc được sử dụng để truyền tham số cho event (*), thực hiện sau mỗi khi event kết thúc.

(*) Chúng ta có thể truyền tham số cho event bằng cú pháp như sau: course.ready(:pending, managers). Trong đó tham số đầu tiên xác định trạng thái kết thúc của transition, phần còn lại chính là tham số truyền vào cho event. Cụ thể trong trường hợp này, notify_somebody sẽ nhận đối số truyền vào là managers. Bạn cũng có thể truyền vào tham số đầu tiên cho event là nil, khi đó AASM sẽ thực hiện transition đầu tiên định nghĩa cho event đó.

Trong trường hợp một lỗi xảy ra khi diễn ra event, nó sẽ được rescued và truyền vào callback :error, do đó chúng ta có thể dùng nó để xử lý tùy ý.

1
2
3
4
5
6
7
8
9
 
event :close do
  error do |e|
    ...
  end
  transitions from: :pending, to: :closed
  transitions from: :in_progress, to: :closed
end
 

Trong quá trình callback diễn ra bạn cũng có thể kiểm tra các thông tin về state hoặc event đang chạy:

1
2
3
4
5
6
7
8
 
def dump_trainee_data
  ...
  logger.info "from #{aasm.from_state} to #{aasm.to_state}"
  ...
  puts "triggered #{aasm.current_event}"
end
 

 

Guards

Giả sử bạn muốn ràng buộc các transition, cho phép chúng được thực hiện chỉ khi thỏa mãn một điều kiện cho trước. Lúc này chúng ta cần sử dụng guard (lính canh) cho transition. Nếu guard trả về false thì transition sẽ bị denied (raise AASM::InvalidTransition hoặc trả về false). Ví dụ:

1
2
3
4
5
6
7
8
 
event :finish, success: :dump_trainee_data do
  transitions from: :in_progress, to: :finished, guard: :evaluations_finished?
end
 
course = Course.new
course.may_finish?            # => false nếu :evaluations_finished? trả về false và ngược lại
 

Tuy nhiên thì có vẻ như bạn sẽ thích ràng buộc transition bằng if, unless hơn vì nó khá gần Ruby:

1
2
3
4
5
 
event :finish, success: :dump_trainee_data do
  transitions from: :in_progress, to: :finished, if: :evaluations_finished?
end
 

NOTE: Khi một event có nhiều transitions, transition đầu tiên thực hiện thành công sẽ ngăn các transitions tiếp theo trong cùng event được thực hiện.

ActiveRecord

Như đã nói ở phần trước, aasm sẽ sinh ra cho model một trường trạng thái mặc định là aasm_state, tuy nhiên ta có thể custom tên trường trạng thái theo ý sử dụng như sau:

1
2
3
4
5
 
aasm column: :status do
  ...
end
 

Thường trong thực tế chúng ta sẽ ưa dùng trường trạng thái với kiểu enum hơn dựa trên những ưu điểm của nó. Để sử dụng enum, chúng ta chỉ cần áp dụng đơn giản như dưới đây:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
class Course < ActiveRecord::Base
  include AASM
 
  enum status: {
    init: 1,
    pending: 2,
    in_progress: 3,
    finished: 4,
    closed: 5
  }
 
  aasm column: :status, enum: true do
    state :init, initial: true
    state :pending, before_enter: :save_progress
    state :in_progress, :finished, :closed
  end
end
 

Tuyệt vời!

Bang events

Bang (!) thường khiến cho các phương thức có ý nghĩa riêng biệt trong những trường hợp khác nhau. Đối với các event của AASM cũng vậy, nó giúp bạn auto-save đối tượng thay vì để nó ở dạng unsaved như khi sử dụng phương thức bình thường. Ví dụ:

1
2
3
4
5
 
course = Course.new
course.finish   # chưa lưu
course.finish!  # lưu
 

Việc lưu đối tượng ở đây tất nhiên sẽ bao gồm tất cả các phép validations trên đối tượng đó. Nếu ta sử dụng cờ whiny_persistence đặt bằng true, exception sẽ được raised lên khi có lỗi. Ngược lại, các phương thức với (!) sẽ trả về true nếu có lỗi xảy ra, và đương nhiên nếu mọi thứ đều thành công thì nó sẽ trả về true.

Trong trường hợp bạn muốn thực hiện lưu nhưng muốn bỏ qua validation, thì bạn có thể bảo AASM không làm việc đó nữa (giống như là sử dụng update_column trong ActiveRecord vậy), bằng cách:

1
2
3
4
5
 
aasm skip_validation_on_save: true do
  ...  
end
 

Một điều đáng chú ý nữa là nếu bạn không muốn ai đó gán trực tiếp trạng thái của aasm (điều mà có thể đem lại hệ quả không như mong muốn), thì bạn nên sử dụng option này:

1
2
3
4
5
6
7
8
9
 
class Course < ActiveRecord::Base
  include AASM
 
  aasm no_direct_assignment: true, column: :status do
    ...
  end
end
 

Điều này sẽ dẫn đến behavior như sau:

1
2
3
4
5
6
 
course = Course.create
course.status # => 'init'
course.status = :pending # => raises AASM::NoDirectAssignmentError
course.status # => 'init'
 

Dễ hiểu phải không nào? 😄

Kết luận

Trên đây mình đã trình bày sơ qua về khái niệm của một máy trạng thái và đưa ra cách áp dụng nó vào một bài toán thực tế, sử dụng gem aasm. Còn rất nhiều chi tiết nâng cao liên quan đến việc sử dụng aasm để giúp bạn có thể xử lý bài toán của mình một cách tinh tế hơn, tuy nhiên trong khuôn khổ bài viết mình chỉ trình bày những đặc điểm đặc trưng và cơ bản để áp dụng với bài toán thường gặp. Nếu bạn muốn tìm hiểu sâu hơn, hãy đọc thêm ở đây. Cảm ơn sự chú ý theo dõi!

Nguồn: Sưu tầm từ internet Via Viblo

Các bài viết cùng chủ đề

KHÓA HỌC LẬP TRÌNH #FULLSTACK TẠI #DEVMASTER – LỘ TRÌNH TOÀN DIỆN TỪ CƠ BẢN ĐẾN CHUYÊN NGHIỆP
KHÓA HỌC LẬP TRÌNH #FULLSTACK TẠI #DEVMASTER – LỘ ...
🔟 Buổi hướng dẫn đồ án
🔟 Buổi hướng dẫn đồ án
KHÉP LẠI MỘT HÀNH TRÌNH
KHÉP LẠI MỘT HÀNH TRÌNH
Tưng bừng ưu đãi khóa học lập trình fullstack với công nghệ JavaSpringBoot
Tưng bừng ưu đãi khóa học lập trình fullstack với ...
Devmaster- Thông báo khai giảng khóa học lập trình cung chuyên gia tháng 8-2023
Devmaster- Thông báo khai giảng khóa học lập trình...
Khóa học lập trình ReactJs khai giảng tháng 7 - 2023 tại devmaster
Khóa học lập trình ReactJs khai giảng tháng 7 - 20...

Các khóa đào tạo chuyên đề

Thiết kế và lập trình Website PHP, Laravel chuyên nghiệp - FullStack
Thiết kế và lập trình Website PHP, Laravel chuyên nghiệp - FullStack
Lập trình ứng dụng trên nền tảng android Lập trình ứng dụng trên nền tảng android
Lập trình Ứng dụng với Công nghệ ASP.NET Core MVC, WebAPI, ReactJS - FullStack

Lập trình Ứng dụng với Công nghệ ASP.NET Core MVC, WebAPI, ReactJS - FullStack
Lập trình ứng dụng với WINDOWS FORM Lập trình ứng dụng với WINDOWS FORM
Lập trình ứng dụng với JAVA (FORM) Lập trình ứng dụng với JAVA (FORM)
Thiết kế và lập trình Ứng dụng với công nghệ Java (Java Framework springBoot, hibernate,...) - FullStack
Thiết kế và lập trình Ứng dụng với công nghệ Java (Java Framework springBoot, hibernate,...) - FullStack
Thiết kế và lập trình website với công nghệ HTML5, CSS3, Javascript, Bootstrapt 4, Jquery Thiết kế và lập trình website với công nghệ HTML5, CSS3, Javascript, Bootstrapt 4, Jquery
Lập trình frontend với reacjs (Full) Lập trình frontend với reacjs (Full)
Viện Công Nghệ Và Đào Tạo Devmaster

DEVMASTER ACADEMY

Địa chỉ: Tầng 6 - Tòa nhà VIỆN CÔNG NGHỆ
Số 25, Vũ Ngọc Phan - Láng Hạ - Đống Đa - Hà Nội

Hotline: 0969 609 003 | 0978 611 889

devmaster.contact@gmail.com

hna.tvchung@gmail.com

CÁC KHÓA HỌC CHUYÊN ĐỀ

  • Thiết kế và lập trình Website PHP, Laravel chuyên nghiệp - FullStack
  • Lập trình ứng dụng trên nền tảng android
  • Lập trình Ứng dụng với Công nghệ ASP.NET Core MVC, WebAPI, ReactJS - FullStack
  • Lập trình ứng dụng với WINDOWS FORM
  • Lập trình ứng dụng với JAVA (FORM)
  • Thiết kế và lập trình Ứng dụng với công nghệ Java (Java Framework springBoot, hibernate,...) - FullStack
  • Thiết kế và lập trình website với công nghệ HTML5, CSS3, Javascript, Bootstrapt 4, Jquery
  • Lập trình frontend với reacjs (Full)
Viện Công Nghệ Và Đào Tạo Devmaster

VIỆN CÔNG NGHỆ VÀ ĐÀO TẠO DEVMASTER - Học thực tế * Làm thực tế * Cam kết việc làm
Copyright by Ⓒ DEVMASTER 2015