您如何在 NON-CRUD 路線上實施驗證?例如,有一條瀏覽路線recipes。它接受引數term(用于搜索)、sort等page。
在 Rails 中執行此操作的最簡潔方法是什么?
最直接的解決方案是使用控制器中的一堆if-else運算式來驗證所有這些東西,但我正在尋找更干凈、更容易擴展和維護的東西。
我結束了以下
class RequestValidation
attr_reader :validators
attr_reader :errors
def initialize
@validators = []
@errors = []
end
def add_validator(validator)
@validators << validator
end
def validate
@validators.each do |validator|
validator.validate
@errors = validator.errors
end
end
def valid?
validate
errors.empty?
end
end
驗證器類示例
class SortValidator < Validator
VALID_SORT_FIELD = [:created_at, :rate, :total_time]
VALID_SORT_DIRECTIONS = [:asc, :desc]
def validate
return unless param
valid = true
begin
sort = param.to_unsafe_h
if sort.size == 1
k = sort.keys[0].to_sym
v = sort.values[0].to_sym
valid = VALID_SORT_FIELD.include?(k) && VALID_SORT_DIRECTIONS.include?(v)
else
valid = false
end
rescue => ex
valid = false
end
@errors << "Sort parameter invalid" unless valid
end
def is_valid?
!errors.empty?
end
end
所以,RequestValidation就像所有驗證器的構建器/容器類。然后,在控制器中:
validation = RequestValidation.new
validation.add_validator(RecipesValidators::SortValidator.new(params[:sort]))
validation.validate
if validation.errors.empty?
這樣,當需要新的驗證時,我們只需要創建一個新的 Validator 類并將其添加到請求驗證中。
鑒于我在 Rails 中停頓了好幾年,有人懷疑這段代碼是否干凈整潔,是否符合 Ruby 原則。
或者,還有更好的方法?
提前致謝
uj5u.com熱心網友回復:
現在我會在控制器和資料庫之間放置一些契約和強型別結構。
就像是:
# obtain an instance of RecipeSearchParamsContract
def contract
@contract ||= SomeValidationContract.new
end
# obtain an instance of a coercing struct
def input
MyStruct.new(params)
end
def query
MyQuery.new
end
def your_action
if contract.call(params).success?
render query.call(input)
end
end
所以你完全分離了所有的關注點。作為代碼基礎的好的庫是dry-validatorand dry-struct,您可以通過以下方式生成合約:
class SearchParamsContract < Dry::Validation::Contract
params do
optional(:sort).hash(max_size?: 1) do
optional(:rate).value(included_in?: %w[asc desc])
optional(:created_at).value(included_in?: %w[asc desc])
optional(:total_time).value(included_in?: %w[asc desc])
end
optional(:page).value(:integer)
optional(:term).filled(:string)
end
end
使用它,強制和驗證的結構在結果本身中是可用的,因此您可以將所有內容鏈接在一起并使用結構模式匹配來獲得很棒且非常 ruby?? 的代碼(沒有 rails 額外的東西)
def search
case contract.call(params)
in Success(input)
@recipes = query.call(input)
else
render :error
end
end
uj5u.com熱心網友回復:
這里的解決方案從來都不是真正的自定義驗證器——相反,您應該創建一個類(例如模型或表單物件)來封裝驗證和資料。僅僅因為您的操作不保留任何內容并不意味著您不能使用模型來表示資料和業務邏輯。
class RecipeSearch
include ActiveModel::Model
include ActiveModel::Attributes
attribute :query, :string
attribute :sort_by, :string
# ...
validates :query, length: { minimum: 5 }
validates :sort_by, in: ['newest', 'quickest', 'cheapest']
# ...
# @todo apply filters to Recipe and return collection
def perform
# ...
end
end
這使您可以添加驗證和邏輯。并將其傳遞給表單:
<%= form_with(model: local_assigns(:recipe_seach) || RecipeSearch.new) do |f| %>
# ...
<% end %>
這也是一種誤解,即搜索不能成為 CRUD 的一部分。畢竟只是閱讀應用了過濾器。如果您確實需要與普通索引分開的視圖,那么請務必創建一個單獨的搜索操作,但這并不是真正的必需品。
class RecipesController
def index
@recipe_search = RecipeSearch.new(search_params)
@recipes = @recipe_search.perform
end
private
def search_params
params.fetch(:recipe_search, {})
.permit(:query, :sort_by)
end
end
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/425407.html
