Filterameter ให้ตัวกรองที่ประกาศสำหรับตัวควบคุม Rails เพื่อลดโค้ดสำเร็จรูปและเพิ่มความสามารถในการอ่าน คุณเคยเห็น (หรือเขียน) การกระทำของคอนโทรลเลอร์นี้กี่ครั้งแล้ว?
def index
@films = Films . all
@films = @films . where ( name : params [ :name ] ) if params [ :name ]
@films = @films . joins ( :film_locations ) . merge ( FilmLocations . where ( location_id : params [ :location_id ] ) ) if params [ :location_id ]
@films = @films . directed_by ( params [ :director_id ] ) if params [ :director_id ]
@films = @films . written_by ( params [ :writer_id ] ) if params [ :writer_id ]
@films = @films . acted_by ( params [ :actor_id ] ) if params [ :actor_id ]
end
มันเป็นโค้ดที่ซ้ำซ้อนและยุ่งยากเล็กน้อยในการเขียนและบำรุงรักษา ไม่ต้องพูดถึงว่า RuboCop จะพูดอะไรเกี่ยวกับเรื่องนี้ จะดีกว่าไหมหากคุณสามารถประกาศตัวกรองที่คอนโทรลเลอร์ยอมรับได้
filter :name , partial : true
filter :location_id , association : :film_locations
filter :director_id , name : :directed_by
filter :writer_id , name : :written_by
filter :actor_id , name : :acted_by
def index
@films = build_query_from_filters
end
ลดความซับซ้อนและรวดเร็วในการพัฒนาตัวควบคุม Rails โดยการประกาศพารามิเตอร์ตัวกรองด้วย Filterameter
Gem นี้ต้องการ Rails 6.1+ และใช้งานได้กับ ActiveRecord
เพิ่มบรรทัดนี้ลงใน Gemfile ของแอปพลิเคชันของคุณ:
gem 'filterameter'
แล้วดำเนินการ:
$ bundle install
หรือติดตั้งด้วยตัวเองเป็น:
$ gem install filterameter
รวมโมดูล Filterameter::DeclarativeFilters
ในตัวควบคุมเพื่อจัดเตรียมตัวกรอง DSL สามารถรวมไว้ใน ApplicationController
เพื่อให้ฟังก์ชันต่างๆ พร้อมใช้งานสำหรับคอนโทรลเลอร์ทั้งหมด หรือสามารถผสมเข้าด้วยกันเป็นรายกรณีไปก็ได้
filter :color
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] , allow_multiple_values : true } }
filter :brand_name , association : :brand , name : :name
filter :on_sale , association : :price , validates : [ { numericality : { greater_than : 0 } } ,
{ numericality : { less_than : 100 } } ]
ตัวกรองที่ไม่มีตัวเลือกสามารถประกาศทั้งหมดพร้อมกันได้โดยใช้ filters
:
filters :color ,
:size ,
:name
สามารถระบุตัวเลือกต่อไปนี้สำหรับแต่ละตัวกรองได้
หากชื่อของพารามิเตอร์แตกต่างจากชื่อของคุณลักษณะหรือขอบเขต ให้ใช้พารามิเตอร์ชื่อเพื่อระบุชื่อของคุณลักษณะหรือขอบเขต ตัวอย่างเช่น หากชื่อแอ็ตทริบิวต์เป็น current_status
แต่ตัวกรองถูกเปิดเผยเหมือนกับ status
ให้ใช้สิ่งต่อไปนี้:
filter :status , name : :current_status
ตัวเลือกนี้ยังมีประโยชน์กับตัวกรองแบบซ้อนเพื่อให้สามารถใส่ชื่อรุ่นนำหน้าพารามิเตอร์การสืบค้นได้ ดูตัวเลือก association
สำหรับตัวอย่าง
หากแอตทริบิวต์หรือขอบเขตซ้อนกัน ก็สามารถอ้างอิงได้โดยการตั้งชื่อการเชื่อมโยง ตัวอย่างเช่น หากแอตทริบิวต์ manager_id อยู่ในบันทึกแผนกของพนักงาน ให้ใช้ดังนี้:
filter :manager_id , association : :department
แอ็ตทริบิวต์หรือขอบเขตสามารถซ้อนกันได้มากกว่าหนึ่งระดับ ประกาศตัวกรองด้วยอาร์เรย์ที่ระบุการเชื่อมโยงตามลำดับ ตัวอย่างเช่น ถ้าพนักงานอยู่ในแผนกและแผนกอยู่ในหน่วยธุรกิจ ให้ใช้สิ่งต่อไปนี้เพื่อสอบถามชื่อหน่วยธุรกิจ:
filter :business_unit_name , name : :name , association : [ :department , :business_unit ]
หากการเชื่อมโยงเป็น has_many
วิธีการที่แตกต่างจะถูกเรียกใช้ในแบบสอบถาม
ข้อจำกัด: หากมีการเชื่อมโยงมากกว่าหนึ่งรายการในตารางเดียวกัน และ การเชื่อมโยงทั้งสองสามารถเป็นส่วนหนึ่งของแบบสอบถามได้ คุณจะไม่สามารถใช้ตัวกรองแบบซ้อนได้โดยตรง ให้สร้างขอบเขตที่แยกความเกี่ยวข้องออกจากกัน จากนั้นสร้างตัวกรองตามขอบเขตนั้นแทน
หากควรตรวจสอบค่าตัวกรอง ให้ใช้ตัวเลือก validates
พร้อมกับการตรวจสอบ ActiveModel ต่อไปนี้คือตัวอย่างของเครื่องมือตรวจสอบการรวมที่ใช้ในการจำกัดขนาด:
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] } }
เครื่องมือตรวจสอบ inclusion
ถูกแทนที่เพื่อจัดเตรียมตัวเลือกเพิ่มเติม allow_multiple_values
เมื่อเป็นจริง ค่าอาจเป็นอาร์เรย์ได้ และแต่ละรายการในอาร์เรย์จะได้รับการตรวจสอบความถูกต้อง ใช้สิ่งนี้เมื่อตัวกรองสามารถระบุค่าได้ตั้งแต่หนึ่งค่าขึ้นไป
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] , allow_multiple_values : true } }
ระบุตัวเลือกบางส่วนหากตัวกรองควรทำการค้นหาบางส่วน ( LIKE
ของ SQL) ตัวเลือกบางส่วนยอมรับแฮชเพื่อระบุพฤติกรรมการค้นหา นี่คือตัวเลือกที่ใช้ได้:
มีทางลัดสองทาง: : ตัวเลือกบางส่วนสามารถประกาศด้วย true
ซึ่งใช้ค่าเริ่มต้นเท่านั้น หรือสามารถประกาศตัวเลือกบางส่วนด้วยตัวเลือกการจับคู่โดยตรง เช่น partial: :from_start
filter :description , partial : true
filter :department_name , partial : :from_start
filter :reason , partial : { match : :dynamic , case_sensitive : true }
ตัวเลือก match
จะกำหนดตำแหน่งที่คุณกำลังค้นหา (ซึ่งจะควบคุมตำแหน่งที่ไวด์การ์ดจะปรากฏ):
ระบุตัวเลือกช่วงเพื่อเปิดใช้งานการค้นหาตามช่วง ค่าต่ำสุด หรือค่าสูงสุด (ทั้งหมดนี้รวมอยู่ด้วย การค้นหามูลค่าขั้นต่ำ $10.00 จะรวมสินค้าทั้งหมดที่มีราคา $10.00)
นี่คือตัวเลือกที่ใช้ได้:
การใช้ตัวเลือกช่วงหมายความว่า นอกเหนือจากพารามิเตอร์การสืบค้นขั้นต่ำและสูงสุดของตัวกรองแอตทริบิวต์ แล้ว ยังสามารถระบุได้อีกด้วย ชื่อพารามิเตอร์คือชื่อแอ็ตทริบิวต์บวกส่วนต่อท้าย _min หรือ _max
filter :price , range : true
filter :approved_at , range : :min_only
filter :sale_price , range : :max_only
ในตัวอย่างแรก พารามิเตอร์การค้นหาอาจประกอบด้วย price , price_min และ price_max
ตามค่าเริ่มต้น ตัวกรองส่วนใหญ่สามารถจัดเรียงได้ เพื่อป้องกันไม่ให้ตัวกรองแอตทริบิวต์เรียงลำดับได้ ให้ตั้งค่าตัวเลือกเป็นเท็จ
filter :price , sortable : false
ตัวกรองต่อไปนี้ไม่สามารถจัดเรียงได้:
สำหรับขอบเขตที่ไม่ได้รับอาร์กิวเมนต์ ตัวกรองควรจัดเตรียมบูลีนที่ระบุว่าควรเรียกใช้ขอบเขตหรือไม่ ตัวอย่างเช่น ลองจินตนาการถึงขอบเขตที่เรียกว่า high_priority
พร้อมด้วยเกณฑ์ที่ระบุบันทึกที่มีลำดับความสำคัญสูง ขอบเขตจะถูกเรียกใช้โดยพารามิเตอร์การสืบค้น high_priority=true
การส่งผ่าน high_priority=false
จะไม่เรียกใช้ขอบเขต ทำให้ง่ายต่อการรวมตัวกรองเข้ากับ UI ของช่องทำเครื่องหมาย
ขอบเขตที่รับข้อโต้แย้งจะต้องเขียนเป็นวิธีการเรียน ไม่ใช่ขอบเขตแบบอินไลน์ ตัวอย่างเช่น ลองนึกภาพขอบเขตที่เรียกว่า recent
ซึ่งใช้ข้อมูล ณ วันที่เป็นอาร์กิวเมนต์ นี่คือสิ่งที่อาจมีลักษณะดังนี้:
def self . recent ( as_of_date )
where ( 'created_at > ?' , as_of_date )
end
ตามที่ระบุไว้ข้างต้น ตัวกรองแอตทริบิวต์ส่วนใหญ่สามารถจัดเรียงตามค่าเริ่มต้นได้ หากไม่มีการประกาศตัวกรองสำหรับแอตทริบิวต์ ก็สามารถใช้การประกาศ sort
ได้ ใช้ name
และตัวเลือก association
เดียวกันตามความจำเป็น
ตัวอย่างเช่น การประกาศต่อไปนี้สามารถใช้กับตัวควบคุมกิจกรรมเพื่ออนุญาตให้กิจกรรมจัดเรียงตามโครงการที่สร้างขึ้นที่
sort :project_created_at , name : :created_at , association : :project
การเรียงลำดับโดยไม่มีตัวเลือกสามารถประกาศทั้งหมดพร้อมกันได้ sorts
:
sorts :created_at ,
:updated_at ,
:description
สามารถใช้ขอบเขตในการเรียงลำดับได้ แต่ต้องประกาศด้วย sort
(หรือ sorts
) ตัวอย่างเช่น หากโมเดลมีขอบเขตที่เรียกว่า by_created_at
คุณสามารถเพิ่มสิ่งต่อไปนี้ลงในคอนโทรลเลอร์เพื่อแสดงมันได้
sort :by_created_at
สามารถใช้ตัวเลือก name
และ association
ได้ ตัวอย่างเช่น หากขอบเขตอยู่ในโมเดลโครงการ ก็สามารถใช้กับตัวควบคุมกิจกรรมย่อยได้โดยใช้ตัวเลือก association
:
sort :by_created_at , association : :project
การเชื่อมโยงเอกพจน์เท่านั้นที่ใช้สำหรับการเรียงลำดับได้ การเชื่อมโยงคอลเลกชันสามารถส่งคืนค่าหลายค่า ทำให้การเรียงลำดับไม่แน่นอน
ขอบเขตที่ใช้สำหรับการเรียงลำดับต้องยอมรับอาร์กิวเมนต์เดียว มันจะถูกส่งผ่าน :asc
หรือ :desc
ขึ้นอยู่กับพารามิเตอร์
ขอบเขตตัวอย่างข้างต้นอาจมีการกำหนดดังนี้:
def self . by_created_at ( dir )
order ( created_at : dir )
end
การเรียงลำดับเริ่มต้นสามารถประกาศได้โดยใช้ default_sort
อาร์กิวเมนต์ควรระบุการเรียงลำดับที่ประกาศหรือตัวกรองที่เรียงลำดับได้ตั้งแต่หนึ่งรายการขึ้นไปตามชื่อ โดยค่าเริ่มต้น ลำดับจะเพิ่มขึ้น หากคุณต้องการเรียงลำดับจากมากไปน้อย คุณสามารถแมปสัญลักษณ์ชื่อคอลัมน์กับ :desc
default_sort updated_at : :desc , :description
เพื่อให้ผลลัพธ์ที่สม่ำเสมอ ระบบจะใช้การเรียงลำดับเสมอ หากไม่มีการระบุค่าเริ่มต้น ระบบจะใช้คีย์หลักจากมากไปน้อย
มีสองวิธีในการใช้ตัวกรองและสร้างแบบสอบถาม ขึ้นอยู่กับจำนวนการควบคุมและ/หรือการมองเห็นที่ต้องการ:
build_filtered_query
ก่อนการดำเนินการโทรกลับbuild_query_from_filters
ด้วยตนเอง build_filtered_query
ก่อนการดำเนินการโทรกลับ เพิ่มก่อน action callback build_filtered_query
สำหรับการดำเนินการของคอนโทรลเลอร์ที่ควรสร้างแบบสอบถาม ซึ่งสามารถทำได้ใน ApplicationController
หรือเป็นกรณีไป
เมื่อใช้การโทรกลับ ชื่อตัวแปรจะเป็นชื่อโมเดลที่เป็นพหูพจน์ ตัวอย่างเช่น โมเดล Photo จะใช้ตัวแปร @photos
เพื่อจัดเก็บคำค้นหา ชื่อตัวแปรสามารถระบุได้อย่างชัดเจนโดยใช้ filter_query_var_name
ตัวอย่างเช่น ถ้าแบบสอบถามถูกจัดเก็บเป็น @data
ให้ใช้ดังต่อไปนี้:
filter_query_var_name :data
นอกจากนี้ คำสั่ง filter_model
ยังใช้พารามิเตอร์ตัวที่สองทางเลือกเพื่อระบุชื่อตัวแปร สามารถระบุทั้งรุ่นและชื่อตัวแปรได้ด้วยทางลัดนี้ ตัวอย่างเช่น หากต้องการใช้โมเดล Picture และจัดเก็บผลลัพธ์เป็น @data
ให้ใช้ดังต่อไปนี้:
filter_model 'Picture' , :data
ในเส้นทางแห่งความสุข WidgetsController ทำหน้าที่วิดเจ็ตและสามารถกรองขนาดและสีได้ คอนโทรลเลอร์อาจมีหน้าตาดังนี้:
class WidgetsController < ApplicationController
include Filterameter :: DeclarativeFilters
before_action :build_filtered_query , only : :index
filter :size
filter :color
def index
render json : @widgets
end
end
build_query_from_filters
ด้วยตนเอง หากต้องการสร้างการสืบค้นด้วยตนเอง คุณสามารถเรียก build_query_from_filters
โดยตรง แทนการใช้การเรียกกลับ
นี่คือตัวควบคุมวิดเจ็ตอีกครั้ง คราวนี้สร้างแบบสอบถามด้วยตนเอง:
class WidgetsController < ApplicationController
include Filterameter :: DeclarativeFilters
filter :size
filter :color
def index
@widgets = build_query_from_filters
end
end
วิธีนี้จะใช้แบบสอบถามเริ่มต้นหรือไม่ก็ได้ หากมีตัวควบคุมสำหรับ Active Widgets ที่ควรส่งคืนวิดเจ็ตที่ใช้งานอยู่เท่านั้น ข้อมูลต่อไปนี้สามารถส่งผ่านไปยังเมธอดเป็นจุดเริ่มต้นได้:
def index
@widgets = build_query_from_filters ( Widget . where ( active : true ) )
end
ข้อความค้นหาเริ่มต้นยังเป็นสถานที่ที่ดีในการระบุการรวมเพื่อเปิดใช้งานการโหลดอย่างกระตือรือร้น:
def index
@widgets = build_query_from_filters ( Widgets . includes ( :manufacturer ) )
end
โปรดทราบว่าแบบสอบถามเริ่มต้นระบุโมเดล ดังนั้นจึงไม่ต้องค้นหาโมเดลและไม่จำเป็นต้องประกาศ model_name
แบบแผนของ Rails ใช้เพื่อกำหนดรุ่นของคอนโทรลเลอร์ ตัวอย่างเช่น PhotosController จะสร้างแบบสอบถามกับโมเดล Photo หากคอนโทรลเลอร์มีเนมสเปซ อันดับแรกโมเดลจะถูกค้นหาโดยไม่มีเนมสเปซ จากนั้นจึงค้นหาด้วยเนมสเปซ
หากแบบแผนไม่ได้ระบุ model ที่ถูกต้อง คุณสามารถตั้งชื่อโมเดลได้อย่างชัดเจนด้วยสิ่งต่อไปนี้:
filter_model 'Picture'
สำคัญ: หากใช้การประกาศ filter_model
จะต้องอยู่ก่อนการประกาศตัวกรองหรือเรียงลำดับใดๆ
มีตัวเลือกการกำหนดค่าสามแบบ:
ตัวเลือกการกำหนดค่าสามารถตั้งค่าได้ในเครื่องมือเริ่มต้น ไฟล์สภาพแวดล้อม หรือใน application.rb
สามารถตั้งค่าตัวเลือกได้โดยตรง...
Filterameter.configuration.action_on_undeclared_parameters = :log
...หรือสามารถให้ผลลัพธ์การกำหนดค่าได้:
Filterameter . configure do | config |
config . action_on_undeclared_parameters = :log
config . action_on_validation_failuer = :log
config . filter_key = :f
end
เกิดขึ้นเมื่อพารามิเตอร์ filter มีคีย์ใดๆ ที่ไม่ได้กำหนดไว้ การกระทำที่ถูกต้องคือ :log
, :raise
และ false
(อย่าดำเนินการ) ตามค่าเริ่มต้น การพัฒนาจะบันทึก การทดสอบจะเพิ่มขึ้น และการผลิตจะไม่ทำอะไรเลย
เกิดขึ้นเมื่อพารามิเตอร์ตัวกรองไม่ผ่านการตรวจสอบ การกระทำที่ถูกต้องคือ :log
, :raise
และ false
(อย่าดำเนินการ) ตามค่าเริ่มต้น การพัฒนาจะบันทึก การทดสอบจะเพิ่มขึ้น และการผลิตจะไม่ทำอะไรเลย
ตามค่าดีฟอลต์ พารามิเตอร์ตัวกรองจะซ้อนอยู่ใต้คีย์ :filter
ใช้การตั้งค่านี้เพื่อแทนที่คีย์
หากพารามิเตอร์ตัวกรองไม่ซ้อนกัน ให้ตั้งค่านี้เป็นเท็จ การทำเช่นนี้จะจำกัดพารามิเตอร์ตัวกรองไว้เฉพาะพารามิเตอร์ที่ได้รับการประกาศเท่านั้น ซึ่งหมายความว่าพารามิเตอร์ที่ไม่ได้ประกาศจะถูกละเว้น (และตัวเลือกการกำหนดค่า action_on_undeclared_parameters จะไม่มีผล)
สามารถทดสอบการประกาศสำหรับคอนโทรลเลอร์แต่ละตัว การตรวจจับการพิมพ์ผิด ขอบเขตที่กำหนดไม่ถูกต้อง หรือปัญหาอื่น ๆ มีการเพิ่มเมธอด declarations_validator
ให้กับคอนโทรลเลอร์แต่ละตัว และสามารถเพิ่มการทดสอบคอนโทรลเลอร์ตัวเดียวเพื่อตรวจสอบความถูกต้องของการประกาศทั้งหมดสำหรับคอนโทรลเลอร์นั้น
การทดสอบ RSpec อาจมีลักษณะดังนี้:
expect ( WidgetsController . declarations_validator ) . to be_valid
ใน Minitest อาจมีลักษณะดังนี้:
validator = WidgetsController . declarations_validator
assert_predicate validator , :valid? , -> { validator . errors }
พารามิเตอร์ตัวกรองจะถูกดึงมาจากพารามิเตอร์ตัวควบคุม ซึ่งซ้อนอยู่ใต้ filter
คีย์ (โดยค่าเริ่มต้น ดูการกำหนดค่าเพื่อเปลี่ยนคีย์ตัวกรอง) ตัวอย่างเช่น คำขอวิดเจ็ตสีน้ำเงินขนาดใหญ่อาจมีพารามิเตอร์เคียวรีต่อไปนี้บน url:
?filter[size]=large&filter[color]=blue
ในแบบฟอร์มการค้นหาทั่วไป form_with
ตัวช่วยแบบฟอร์มจะใช้ scope
ตัวเลือกที่อนุญาตให้จัดกลุ่มพารามิเตอร์:
<%= form_with url: "/search", scope: :filter, method: :get do |form| %>
<%= form.label :size, "Size:" %>
<%= form.text_field :size %>
<%= form.label :color, "Color:" %>
<%= form.text_field :color %>
<%= form.submit "Search" %>
<% end %>
การเรียงลำดับยังซ้อนกันอยู่ใต้คีย์ตัวกรอง:
/widgets?filter[sort]=size
ใช้อาร์เรย์เพื่อส่งผ่านหลายประเภท ลำดับของพารามิเตอร์คือลำดับการเรียงลำดับที่จะนำไปใช้ ตัวอย่างเช่น การเรียงลำดับต่อไปนี้ตามขนาดก่อนแล้วจึงตามสี:
/widgets?filter[sort]=size&filter[sort]=color
การเรียงลำดับจะเพิ่มขึ้นตามค่าเริ่มต้น แต่คุณสามารถเพิ่มคำนำหน้าเพื่อควบคุมการเรียงลำดับได้:
+
จากน้อยไปมาก (ค่าเริ่มต้น)-
จากมากไปน้อยตัวอย่างเช่น การเรียงลำดับตามขนาดจากมากไปหาน้อยต่อไปนี้:
/widgets?filter[sort]=-size
ยินดีรับข้อเสนอแนะ คำขอคุณลักษณะ และการเปลี่ยนแปลงที่เสนอ โปรดใช้เครื่องมือติดตามปัญหาสำหรับข้อเสนอแนะและคำขอคุณสมบัติ หากต้องการเสนอการเปลี่ยนแปลงโดยตรง โปรดแยก repo และเปิดคำขอดึง จับตาดูการกระทำเพื่อให้แน่ใจว่าการทดสอบและ Rubocop ผ่าน Code Climate ยังใช้ด้วยตนเองเพื่อประเมิน Codeline
หากต้องการรายงานข้อบกพร่อง โปรดใช้เครื่องมือติดตามปัญหาและให้ข้อมูลต่อไปนี้:
จะมีการมอบดาวทองหากคุณสามารถจำลองปัญหาด้วยแบบทดสอบได้
การทดสอบเขียนด้วย RSpec และแอปจำลองใช้ฐานข้อมูลนักเทียบท่า สคริปต์ bin/start_db.sh
เริ่มต้นและเตรียมฐานข้อมูลทดสอบ เป็นขั้นตอนที่ทำเพียงครั้งเดียวก่อนที่จะทำการทดสอบ
bin/start_db.rb
bundle exec rspec
การทดสอบยังสามารถดำเนินการกับชุดค่าผสม Ruby และ Rails ทั้งหมดได้โดยใช้การประเมิน การติดตั้งยังเป็นขั้นตอนที่ทำเพียงครั้งเดียวเช่นกัน
bundle exec appraisal install
bundle exec appraisal rspec
อัญมณีนี้มีให้ใช้งานในรูปแบบโอเพ่นซอร์สภายใต้เงื่อนไขของใบอนุญาต MIT