Searching, sorting, filtering ใน Rails controllers อาจปวด ElasticSearch และ Solr ดี powerful แต dependencies ใหญ่สำหรับ small app
โชคดี Rails มี scopes ให้สิ่งที่ต้องการเยอะสำหรับ simple searching, filtering, sorting ถ้าใช้ scope chaining สร้าง features ได้โดยไม่取大 dependencies หรือเขียน repetitive search code
ค้นหาด้วย scopes
จินตนาการ #index method บน RESTful controller แสดงตาราง products สถานะ active, pending, inactive อยู่ single location มี name
อยาก filter products เขียน scopes:
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แต่ละ scope 定義 class method บน Product ใช้ limit results
@products = Product.filter_by_status("active").filter_by_starts_with("Ruby")
# => All active products whose names start with 'Ruby'Controller 用 scopes filter results:
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ตอนนี้แสดง active products ที่ชื่อขึ้นต้น 'Ruby' ได้
https://example.com/products?status=active&starts_with=Rubyชัดเจนว่าต้อง cleanup
เห็นโค้ดเริ่ม unwieldy และ repetitive! 當然 用 Ruby stuff ใน loop:
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
# list of param names that can be used for filtering the Product list
def filtering_params(params)
params.slice(:status, :location, :starts_with)
endSolution reusable มากกว่า
ย้ายโค้ดเข้า module include เข้า model ใดๆ ที่ support filtering:
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
endclass Product
include Filterable
...
enddef index
@products = Product.filter(params.slice(:status, :location, :starts_with))
endตอนนี้ filtering และ searching models ด้วยบรรทัดเดียวใน controller และบรรทัดเดียวใน model ง่ายแค่ไหน? สั่ง sorting ด้วย built-in order class method แตเขียน scopes สำหรับ sorting เองดีกว่า sanity-check input
省力 把 Filterable 放在 gist ลองในโปรเจกต์ ประหยัดเวลาและโค้ดเยอะ
Update: ขอบคุณ Jan Sandbrink ชี้: ง่ายลืม whitelist params ใน filtering_params ลืม = security problems 嚴重
หลีกเลี่ยง แทน scopes ชื่อ status, location, starts_with อัปเดตเป็น filter_by_status, filter_by_location, filter_by_starts_with ชัดเจนและปลอดภัยกว่า
คำเตือนสำคัญ
ส่ง params ไป scopes วิธีง่ายได้ basic searching/filtering แตไม่ระวัง รับอะไร user ส่งมาก็ได้ app อาจมี security bugs น่ากลัว
โดยเฉพาะ order vulnerable SQL injection ถ้า用 params กำหนด sort order ต้อง check column names ที่ user ส่ง allow แค่ค่าที่รู้ว่า safe
Rails SQL Injection site ช่วยเรียน ActiveRecord methods ไหน vulnerable รักษาความปลอดภัย
เรียนดีกว่ากับ video?
ดูทุกขั้นตอน จากสร้าง app ใหม่จนเพิ่ม searching/filtering ใน screencast ร่วม สร้าง app เติม sample data เพิ่ม search form hook up ได้ source กับ videos 參考 ตอนเพิ่ม simple searching/filtering ใน Rails apps ของคุณ
เรียนรู้เพิ่มเกี่ยว screencast ที่นี่!