Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ค้นหาและกรอง Rails Models ได้อย่างง่ายดาย พร้อมรักษา Controllers ให้ Lean

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:

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

แต่ละ 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:

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

ตอนนี้แสดง active products ที่ชื่อขึ้นต้น 'Ruby' ได้

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

ชัดเจนว่าต้อง cleanup

เห็นโค้ดเริ่ม unwieldy และ repetitive! 當然 用 Ruby stuff ใน loop:

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
# list of param names that can be used for filtering the Product list
def filtering_params(params)
  params.slice(:status, :location, :starts_with)
end

Solution reusable มากกว่า

ย้ายโค้ดเข้า module include เข้า model ใดๆ ที่ support filtering:

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

ตอนนี้ 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 ที่นี่!