Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Ruby

Tìm kiếm & Lọc Rails Model dễ dàng trong khi giữ Controller thon gọn

Tìm kiếm, sắp xếp, và lọc trong Rails controller có thể đau đầu. ElasticSearch và Solr là giải pháp mạnh, nhưng là dependency quá lớn cho app nhỏ.

May mắn thay, Rails bao gồm scopes, có thể cung cấp nhiều thứ bạn cần cho tìm kiếm, lọc, sắp xếp đơn giản. Nếu bạn tận dụng scope chaining, bạn có thể build feature bạn muốn mà không cần dependency lớn hoặc viết đống code search lặp lại.

Tìm kiếm với scopes

Hãy tưởng tượng method #index trên RESTful controller hiển thị bảng product. Product có thể active, pending, inactive, có ở một location, và có tên.

Nếu bạn muốn lọc product này, bạn có thể viết scope:

app/models/product.rb
class Product < ActiveRecord::Base scope :filter_by_status, -> (status) { where status: status } scope :filter_by_location, -> (location_id) { where location_id: location_id } scope :filter_by_starts_with, -> (name) { where("name like ?", "#{name}%")} end 

Mỗi scope định nghĩa class method trên Product bạn có thể dùng để giới hạn kết quả.

@products = Product.filter_by_status("active").filter_by_starts_with("Ruby") # => All active products whose names start with 'Ruby' 

Controller của bạn có thể dùng scope để lọc kết quả:

app/controllers/product_controller.rb
def index @products = Product.where(nil) # creates an anonymous scope @products = @products.filter_by_status(params[:status]) if params[:status].present? @products = @products.filter_by_location(params[:location]) if params[:location].present? @products = @products.filter_by_starts_with(params[:starts_with]) if params[:starts_with].present? end 

Và bây giờ bạn có thể hiển thị chỉ product active với tên bắt đầu bằng 'Ruby'.

https://example.com/products?status=active&starts_with=Ruby 

Rõ ràng, điều này cần dọn dẹp

Bạn thấy code bắt đầu trở nên cồng kềnh và lặp lại! Tất nhiên, bạn dùng Ruby, nên bạn có thể nhét vào vòng lặp:

app/controllers/product_controller.rb
def index @products = Product.where(nil) filtering_params(params).each do |key, value| @products = @products.public_send("filter_by_#{key}", value) if value.present? end end private # A list of the param names that can be used for filtering the Product list def filtering_params(params) params.slice(:status, :location, :starts_with) end 

Giải pháp tái sử dụng hơn

Bạn có thể chuyển code này vào module và include vào bất kỳ model nào hỗ trợ lọc:

app/models/concerns/filterable.rb
module Filterable extend ActiveSupport::Concern module ClassMethods def filter(filtering_params) results = self.where(nil) filtering_params.each do |key, value| results = results.public_send("filter_by_#{key}", value) if value.present? end results end end end 
app/models/product.rb
class Product include Filterable ... end 
app/controllers/product_controller.rb
def index @products = Product.filter(params.slice(:status, :location, :starts_with)) end 

Bây giờ bạn có lọc và tìm kiếm model với một dòng trong controller và một dòng trong model. Dễ dàng đến mức nào? Bạn cũng có thể có sắp xếp built-in dùng order class method, nhưng có lẽ ý tưởng tốt hơn là viết scope riêng cho sorting. Cách đó bạn có thể sanity-check input.

Để tiết kiệm công sức, tôi để Filterable trong gist. Hãy thử trong project của bạn, nó đã tiết kiệm cho tôi nhiều thời gian và code.

Cập nhật: Cảm ơn Jan Sandbrink chỉ ra: Dễ quên whitelist params trong filtering_params. Nếu quên, có thể mở lỗ hổng bảo mật nghiêm trọng.

Để tránh đó, thay vì dùng scope tên status, location, starts_with, tôi cập nhật bài này để scope giờ tên filter_by_status, filter_by_location, filter_by_starts_with. Chúng rõ ràng và an toàn hơn.

Cảnh báo quan trọng

Gửi params đến scope là cách dễ dàng lấy search/filter cơ bản cho web app. Nhưng nếu bạn không cẩn thận, và chấp nhận bất cứ gì user gửi, app có thể có bug bảo mật tồi tệ.

Đặc biệt, order dễ bị SQL injection. Vì vậy nếu bạn dùng params định nghĩa sort order, bạn luôn luôn phải check tên cột user gửi và chỉ cho phép giá trị bạn biết an toàn.

Trang Rails SQL Injection sẽ giúp bạn học về method ActiveRecord nào dễ bị tấn công, để giữ app an toàn.

Bạn học tốt hơn với video?

Bạn có thể xem mọi bước, từ khởi tạo app mới đến thêm search/filter, trong screencast đi kèm. Chúng ta sẽ tạo app, điền sample data, thêm form search, và hook nó lên. Bạn cũng nhận source cùng video, để tra cứu khi thêm search/filter đơn giản cho Rails app của mình.

Tìm hiểu thêm về screencast ở đây!