Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

컨트롤러를 부풀리지 않고 레일 모델 검색 및 필터링

Rails 컨트롤러에서 검색, 정렬 및 필터링은 어려울 수 있습니다. ElasticSearch와 Solr는 훌륭한 고성능 솔루션이지만 작은 앱에 대한 종속성은 정말 큽니다.

다행히도 Rails에는 간단한 검색, 필터링 및 정렬에 필요한 많은 것을 제공할 수 있는 범위가 포함되어 있습니다. 스코프 체인을 활용하면 큰 의존성을 일으키거나 반복적인 검색 코드를 직접 작성하지 않고도 원하는 기능을 구축할 수 있습니다.

범위로 검색

#index를 상상해 보세요. 제품 테이블을 보여주는 RESTful 컨트롤러의 메소드. 제품은 활성, 보류 중 또는 비활성 상태일 수 있으며 단일 위치에서 사용할 수 있으며 이름이 있습니다.

이러한 제품을 필터링하려면 몇 가지 범위를 작성할 수 있습니다.

앱/모델/제품.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

이러한 각 범위는 반환되는 결과를 제한하는 데 사용할 수 있는 Product의 클래스 메서드를 정의합니다.

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

컨트롤러는 다음 범위를 사용하여 결과를 필터링할 수 있습니다.

앱/컨트롤러/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

이제 이름이 'Ruby'로 시작하는 활성 제품만 표시할 수 있습니다.

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

분명히 정리가 필요합니다.

이 코드가 어떻게 다루기 힘들고 반복적으로 시작되는지 알 수 있습니다! 물론 Ruby를 사용하고 있으므로 루프에 넣을 수 있습니다.

앱/컨트롤러/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

더욱 재사용 가능한 솔루션

이 코드를 모듈로 이동하고 필터링을 지원하는 모든 모델에 포함할 수 있습니다.

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
앱/모델/제품.rb
class Product
  include Filterable
  ...
end
앱/컨트롤러/product_controller.rb
def index
  @products = Product.filter(params.slice(:status, :location, :starts_with))
end

이제 컨트롤러의 한 줄과 모델의 한 줄로 모델을 필터링하고 검색할 수 있습니다. 얼마나 쉽습니까? 내장된 order를 사용하여 내장된 정렬을 얻을 수도 있습니다. 클래스 메서드이지만 정렬을 위해 고유한 범위를 작성하는 것이 더 나을 것입니다. 그렇게 하면 입력을 온전한 상태로 확인할 수 있습니다.

수고를 덜기 위해 Filterable 요점으로. 자신의 프로젝트에서 시도해 보세요. 시간과 코드를 많이 절약할 수 있습니다.

업데이트: 무언가를 지적해 주신 Jan Sandbrink에게 감사드립니다. filtering_params에서 매개변수를 허용 목록에 추가하는 것을 잊기 쉽습니다. . 잊어버리면 앱이 심각하게 열릴 수 있습니다. 보안 문제.

이 모든 것을 피하려면 status라는 범위를 사용하는 대신 , location , 및 starts_with , 이 문서를 해당 범위로 업데이트했습니다. 이제 이름이 filter_by_status입니다. , filter_by_locationfilter_by_starts_with . 그런 식으로 더 명확하고 안전합니다.

중요 경고

매개변수를 범위로 보내는 것은 웹 앱에서 기본적인 검색 및 필터링을 수행하는 쉬운 방법입니다. 하지만 주의하지 않고 사용자가 보내는 모든 것을 수락하면 앱에 꽤 심각한 보안 버그가 있을 수 있습니다.

특히 order SQL 인젝션에 취약합니다. 따라서 params를 사용하여 정렬 순서를 정의하는 경우 항상 사용자가 보내는 열 이름을 확인하고 안전하다고 알고 있는 값만 허용하십시오.

Rails SQL Injection 사이트는 어떤 ActiveRecord 메서드가 취약한지 알아보는 데 도움이 되므로 앱을 안전하게 유지할 수 있습니다.

비디오로 더 잘 배우나요?

컴패니언 스크린캐스트에서 새로운 앱 시작부터 검색 및 필터링 추가에 이르기까지 모든 단계를 볼 수 있습니다. 앱을 만들고 샘플 데이터로 채우고 검색 양식을 추가하고 연결합니다. 또한 동영상과 함께 소스를 얻을 수 있으므로 자신의 Rails 앱에 간단한 검색 및 필터링을 추가할 때 다시 참조할 수 있습니다.

여기에서 스크린캐스트에 대해 자세히 알아보세요!