Series hướng dẫn lập trình ruby on rails phần 3 năm 2024

Các bạn khi bắt đầu làm việc với Rails hầu hết đều làm việc với 7 actions chuẩn khi tạo một Controller mới: index, new, show, create, edit, update, destroy. Sau một thời gian làm việc, các bạn sẽ để ý thấy một điều rằng, các bạn thường sẽ muốn dùng đi dùng lại một đoạn tính năng tương tự nhau ở các actions hoặc thậm chí là các controllers. Trong bài viết này chúng ta sẽ cùng đi chung với nhau trên con đường giải quyết vấn đề này với một tính năng rất hay: Filters.

Bonus: Trong bài viết, chúng ta sẽ dùng một chút metaprogramming để tạo ra đoạn code giúp bỏ đi rất nhiều đoạn code lặp đi lặp lại nhé.

Trong bài viết chúng ta sẽ đi qua các phần sau:

  • Tìm hiểu về Filters trong Rails,
  • Áp dụng Filters để share code giữa các action trong cùng một Controller
  • Sử dụng metaprogramming để share code giữa các controller

Bắt đầu thôi. Let’s go.

Mục lục

3 loại filters trong Rails

Đầu tiên chúng ta sẽ nói về tính năng Filters mà Rails cung cấp. Rails cung cấp cho chúng ta 3 loại filters:

  • before_filter: chạy trước khi request được gửi đến action trong controller
  • after_filter: chạy sau khi action xử lý request
  • around_filter: bọc xung quanh action, chủ yếu sử dụng cho xử lý ngoại lệ [exception]
    3 filters trong Rails

Cùng tìm hiểu kỹ hơn nhé.

before_filter

Đây là Filter được sử dụng phổ biến nhất. Có 2 cách để gọi một before_filter.

Cách thứ nhất là sử dụng một anonymous block:

class ArticlesController < ApplicationController   before_filter do     @article = Article.find[params[:id]] if params[:id]   end   #...

Đoạn code bên trên sẽ được thực thi trước tất cả request đến bất kì action nào bên trong controller. Vậy nếu như chỉ muốn áp dụng đoạn code đó cho action

class ArticlesController < ApplicationController   before_filter :load_article

Actions...

private   def load_article     @article = Article.find[params[:id]] if params[:id]   end end

2 và

class ArticlesController < ApplicationController   before_filter :load_article

Actions...

private   def load_article     @article = Article.find[params[:id]] if params[:id]   end end

3 thôi thì làm thế nào? Một chút nữa chúng ta sẽ nói đến cách để scope phạm vi của filter.

Cách viết thứ hai sau đây là cách mà mình ưa thích hơn vì sự gọn gàng của nó: sử dụng tên của method.

class ArticlesController < ApplicationController   before_filter :load_article

Actions...

private   def load_article     @article = Article.find[params[:id]] if params[:id]   end end

Các bạn có thể để ý thấy mình để method

class ArticlesController < ApplicationController   before_filter :load_article

Actions...

private   def load_article     @article = Article.find[params[:id]] if params[:id]   end end

4 là private method. Bởi vì filter này chỉ được sử dụng bên trong controller, và sẽ không được truy cập bởi router để map vào một đường dẫn nào, vì vậy nên tốt hơn hết, chúng ta sẽ để nó là private method.

after_filter

Sau khi tìm hiểu về before_filter bên trên, chắc hẳn các bạn cũng đã đoán ra after_filter sử dụng như thế nào rồi. Nó hoạt động với cơ chế tương tự như before_filter, chỉ khác là được thực thi sau khi action của controller được thực thi. Bạn có thể sử dụng nó để lọc hoặc mask các trường thông tin nhạy cảm trước khi trả kết quả về cho client chẳng hạn.

around_filter

Đây là filter hiếm khi được sử dụng và cũng hơi khó hiểu một chút xíu. Cách dùng của nó như sau:

around_filter :wrap_actions def wrap_actions   begin

wrap action code will be here 

    yield # after yeild, action code will be run   rescue     render text: "It broke!" # if there is any exception throw by action code, reach here   end end

Như mình đã comment trong đoạn code trên, khi nào

class ArticlesController < ApplicationController   before_filter :load_article

Actions...

private   def load_article     @article = Article.find[params[:id]] if params[:id]   end end

9 được gọi, code bên trong action của controller sẽ được thực thi. Trong quá trình thực thi của action, nếu có exception xảy ra, exception sẽ được bắt ở đoạn code

around_filter :wrap_actions def wrap_actions   begin

wrap action code will be here 

    yield # after yeild, action code will be run   rescue     render text: "It broke!" # if there is any exception throw by action code, reach here   end end

0 của around_filter bên trên. Đoạn code trên là một trong những ứng dụng của around_filter: bắt exception.

Scope phạm vi của filter: only và except

Nếu để mặc định, các đoạn code Filter ở trên sẽ được thực thi trước tất cả request đến bất kì action nào bên trong controller. Vậy nếu như chỉ muốn áp dụng đoạn code đó cho action

class ArticlesController < ApplicationController   before_filter :load_article

Actions...

private   def load_article     @article = Article.find[params[:id]] if params[:id]   end end

2 và

class ArticlesController < ApplicationController   before_filter :load_article

Actions...

private   def load_article     @article = Article.find[params[:id]] if params[:id]   end end

3 thôi thì làm thế nào? Sau đây mình sẽ nói về cách scope phạm vi của Filter tới action mà các bạn mong muốn thôi: sử dụng

around_filter :wrap_actions def wrap_actions   begin

wrap action code will be here 

    yield # after yeild, action code will be run   rescue     render text: "It broke!" # if there is any exception throw by action code, reach here   end end

4 và

around_filter :wrap_actions def wrap_actions   begin

wrap action code will be here 

    yield # after yeild, action code will be run   rescue     render text: "It broke!" # if there is any exception throw by action code, reach here   end end

5.

Cả 3 filter bên trên đều nhận option

around_filter :wrap_actions def wrap_actions   begin

wrap action code will be here 

    yield # after yeild, action code will be run   rescue     render text: "It broke!" # if there is any exception throw by action code, reach here   end end

6 và

around_filter :wrap_actions def wrap_actions   begin

wrap action code will be here 

    yield # after yeild, action code will be run   rescue     render text: "It broke!" # if there is any exception throw by action code, reach here   end end

7

  • around_filter :wrap_actions

    def wrap_actions   begin

    wrap action code will be here

        yield # after yeild, action code will be run   rescue     render text: "It broke!" # if there is any exception throw by action code, reach here   end end 6: danh sách các actions mà filter đó sẽ chạy cùng
  • around_filter :wrap_actions

    def wrap_actions   begin

    wrap action code will be here

        yield # after yeild, action code will be run   rescue     render text: "It broke!" # if there is any exception throw by action code, reach here   end end 7: danh sách các actions mà filter đó sẽ không chạy cùng

Ví dụ, ở đoạn code ví dụ cho before_filter bên trên, các bạn có thể bỏ điều kiện check

class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

Actions...

private   def load_article     @article = Article.find[params[:id]]   end end

1 nếu như bạn scope filter này tới các action sẽ luôn có

class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

Actions...

private   def load_article     @article = Article.find[params[:id]]   end end

2, ví dụ như

class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

Actions...

private   def load_article     @article = Article.find[params[:id]]   end end

3. Cách làm như sau:

class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

Actions...

private   def load_article     @article = Article.find[params[:id]]   end end

Hoặc các bạn cũng có thể sử dụng

around_filter :wrap_actions def wrap_actions   begin

wrap action code will be here 

    yield # after yeild, action code will be run   rescue     render text: "It broke!" # if there is any exception throw by action code, reach here   end end

7 như sau:

class ArticlesController < ApplicationController   before_filter :load_article, except: [:index, :new, :create]   #...

Như vậy là chúng ta đã đi qua 3 loại filter được sử dụng trong Rails và cũng đã tìm hiểu làm sao để config các actions mà chúng ta muốn chạy filter bên trong controller. Sau đây sẽ tới phần thú vị nhất, share các filter này giữa các controller luôn. Bật mí, chúng ta sẽ sử dụng một chút metaprogramming.

Sharing Filters

Thông thường, filter sẽ được sử dụng để share code giữa các action bên trong một controller cụ thể. Tuy nhiên, chúng ta còn có thể share chúng giữa các controller, sử dụng OOP [lập trình hướng đối tượng].

Cách thông thường nhất để tái sử dụng filter giữa các controller đó là di chuyển đoạn code filter này ra

class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

Actions...

private   def load_article     @article = Article.find[params[:id]]   end end

5. Bởi vì tất cả các controller khác của chúng ta kế thừa từ

class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

Actions...

private   def load_article     @article = Article.find[params[:id]]   end end

5, các controller này sẽ có quyền truy cập đến các filter này.

class ApplicationController < ActionController::Base   protect_from_forgery private   def load_article     @article = Article.find[params[:id]]   end end class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

Actions...

end

Nhưng các bạn có thấy điều gì bất thường ở trong đoạn code trên hay không? Đúng rồi, ngoài controller

class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

Actions...

private   def load_article     @article = Article.find[params[:id]]   end end

7 ra, các controller khác đâu cần biến

class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

Actions...

private   def load_article     @article = Article.find[params[:id]]   end end

8 làm gì. Vậy có cách nào để:

  • class ArticlesController < ApplicationController

      before_filter :load_article, only: [:show, :edit, :update, :destroy]

    Actions...

    private   def load_article     @article = Article.find[params[:id]]   end end 7 thì tạo ra biến class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

    Actions...

    private   def load_article     @article = Article.find[params[:id]]   end end 8
  • class ArticlesController < ApplicationController   before_filter :load_article, except: [:index, :new, :create]   #... 1 thì tạo ra biến class ArticlesController < ApplicationController   before_filter :load_article, except: [:index, :new, :create]   #... 2
  • class ArticlesController < ApplicationController   before_filter :load_article, except: [:index, :new, :create]   #... 3 thì tạo ra biến class ArticlesController < ApplicationController   before_filter :load_article, except: [:index, :new, :create]   #... 4

hay không?

Sử dụng một chút metaprogramming, chúng ta có thể làm được. Chúng ta sẽ sửa method

class ArticlesController < ApplicationController   before_filter :load_article

Actions...

private   def load_article     @article = Article.find[params[:id]] if params[:id]   end end

4 lại thành

class ArticlesController < ApplicationController   before_filter :load_article, except: [:index, :new, :create]   #...

6. Trong method này, sử dụng

class ArticlesController < ApplicationController   before_filter :load_article, except: [:index, :new, :create]   #...

7 để suy ra model tương ứng, kiểu như sau:

  • class ArticlesController < ApplicationController

      before_filter :load_article, only: [:show, :edit, :update, :destroy]

    Actions...

    private   def load_article     @article = Article.find[params[:id]]   end end 7 -> Model: class ArticlesController < ApplicationController   before_filter :load_article, except: [:index, :new, :create]   #... 9 -> biến: class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

    Actions...

    private   def load_article     @article = Article.find[params[:id]]   end end 8
  • class ArticlesController < ApplicationController

      before_filter :load_article, except: [:index, :new, :create]   #... 1 -> Model: class ApplicationController < ActionController::Base   protect_from_forgery private   def load_article     @article = Article.find[params[:id]]   end end class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

    Actions...

    end 2 -> biến: class ArticlesController < ApplicationController   before_filter :load_article, except: [:index, :new, :create]   #... 2
  • class ArticlesController < ApplicationController

      before_filter :load_article, except: [:index, :new, :create]   #... 3 -> Model: class ApplicationController < ActionController::Base   protect_from_forgery private   def load_article     @article = Article.find[params[:id]]   end end class ArticlesController < ApplicationController   before_filter :load_article, only: [:show, :edit, :update, :destroy]

    Actions...

    end 5 -> biến: class ArticlesController < ApplicationController   before_filter :load_article, except: [:index, :new, :create]   #... 4

class ApplicationController < ActionController::Base   protect_from_forgery private   def find_resource     class_name = params[:controller].singularize     klass = class_name.camelize.constantize     self.instance_variable_set "@" + class_name, klass.find[params[:id]]   end end class ArticlesController < ApplicationController   before_filter :find_resource, only: [:show, :edit, :update, :destroy]

Actions...

end

Như vậy qua bài viết này, mình cùng với các bạn đã đi qua các kỹ thuật áp dụng Filter trong Rails để khiến code trở nên ngắn gọn và clean hơn. Hi vọng bài viết giúp ích phần nào đó cho các bạn trên con đường sử dụng framework Ruby on Rails trong công việc của mình. Các bạn có chia sẻ nào thêm có thể để dưới phần bình luận nhé.

Chủ Đề