Filterameter menyediakan filter deklaratif untuk pengontrol Rails guna mengurangi kode boilerplate dan meningkatkan keterbacaan. Berapa kali Anda melihat (atau menulis) aksi pengontrol ini?
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
Ini adalah kode yang mubazir dan agak sulit untuk ditulis dan dipelihara. Belum lagi apa yang akan dikatakan RuboCop tentang hal itu. Bukankah lebih baik jika Anda bisa mendeklarasikan filter yang diterima pengontrol?
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
Menyederhanakan dan mempercepat pengembangan pengontrol Rails dengan membuat parameter filter deklaratif dengan Filterameter.
Permata ini memerlukan Rails 6.1+, dan berfungsi dengan ActiveRecord.
Tambahkan baris ini ke Gemfile aplikasi Anda:
gem 'filterameter'
Dan kemudian jalankan:
$ bundle install
Atau instal sendiri sebagai:
$ gem install filterameter
Sertakan modul Filterameter::DeclarativeFilters
di pengontrol untuk menyediakan filter DSL. Ini dapat dimasukkan dalam ApplicationController
untuk membuat fungsionalitas tersedia untuk semua pengontrol atau dapat digabungkan berdasarkan kasus per kasus.
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 } } ]
Filter tanpa opsi dapat dideklarasikan sekaligus dengan filters
:
filters :color ,
:size ,
:name
Opsi berikut dapat ditentukan untuk setiap filter.
Jika nama parameter berbeda dengan nama atribut atau cakupan, gunakan parameter nama untuk menentukan nama atribut atau cakupan. Misalnya, jika nama atributnya adalah current_status
tetapi filternya ditampilkan hanya sebagai status
gunakan yang berikut ini:
filter :status , name : :current_status
Opsi ini juga dapat berguna dengan filter bertingkat sehingga parameter kueri dapat diawali dengan nama model. Lihat opsi association
sebagai contoh.
Jika atribut atau cakupannya bertingkat, maka dapat direferensikan dengan memberi nama asosiasinya. Misalnya, jika atribut manager_id ada di catatan departemen karyawan, gunakan yang berikut ini:
filter :manager_id , association : :department
Atribut atau cakupannya dapat disarangkan lebih dari satu tingkat. Deklarasikan filter dengan array yang menentukan asosiasi secara berurutan. Misalnya, jika seorang karyawan termasuk dalam departemen dan departemen termasuk dalam unit bisnis, gunakan yang berikut ini untuk menanyakan nama unit bisnis:
filter :business_unit_name , name : :name , association : [ :department , :business_unit ]
Jika suatu asosiasi adalah has_many
metode berbeda akan dipanggil pada kueri.
Batasan: Jika ada lebih dari satu pengaitan pada tabel yang sama dan kedua pengaitan bisa menjadi bagian dari kueri, maka Anda tidak bisa menggunakan filter bertingkat secara langsung. Sebaliknya, buatlah cakupan yang memperjelas keterkaitan, lalu buat filter terhadap cakupan tersebut.
Jika nilai filter harus divalidasi, gunakan opsi validates
bersama dengan validasi ActiveModel. Berikut ini contoh validator penyertaan yang digunakan untuk membatasi ukuran:
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] } }
Validator inclusion
telah diganti untuk memberikan opsi allow_multiple_values
. Jika benar, nilainya bisa berupa array dan setiap entri dalam array akan divalidasi. Gunakan ini ketika filter dapat menentukan satu atau beberapa nilai.
filter :size , validates : { inclusion : { in : %w[ Small Medium Large ] , allow_multiple_values : true } }
Tentukan opsi parsial jika filter harus melakukan pencarian parsial ( LIKE
SQL). Opsi parsial menerima hash untuk menentukan perilaku pencarian. Berikut adalah opsi yang tersedia:
Ada dua jalan pintas: : opsi parsial dapat dideklarasikan dengan true
, yang hanya menggunakan default; atau opsi parsial dapat dideklarasikan dengan opsi kecocokan secara langsung, misalnya partial: :from_start
.
filter :description , partial : true
filter :department_name , partial : :from_start
filter :reason , partial : { match : :dynamic , case_sensitive : true }
Opsi match
menentukan tempat Anda mencari (yang kemudian mengontrol tempat munculnya wildcard):
Tentukan opsi rentang untuk mengaktifkan pencarian berdasarkan rentang, nilai minimum, atau nilai maksimum. (Semua ini bersifat inklusif. Penelusuran dengan nilai minimum $10,00 akan mencakup semua item dengan harga $10,00.)
Berikut adalah opsi yang tersedia:
Menggunakan opsi rentang berarti bahwa selain filter atribut, parameter kueri minimum dan maksimum juga dapat ditentukan. Nama parameternya adalah nama atribut ditambah akhiran _min atau _max .
filter :price , range : true
filter :approved_at , range : :min_only
filter :sale_price , range : :max_only
Pada contoh pertama, parameter kueri dapat mencakup price , price_min , dan price_max .
Secara default, sebagian besar filter dapat diurutkan. Untuk mencegah filter atribut agar tidak dapat diurutkan, setel opsi ke false.
filter :price , sortable : false
Filter berikut tidak dapat diurutkan:
Untuk cakupan yang tidak menggunakan argumen, filter harus menyediakan boolean yang menunjukkan apakah cakupan tersebut harus dipanggil atau tidak. Misalnya, bayangkan cakupan yang disebut high_priority
dengan kriteria yang mengidentifikasi rekaman berprioritas tinggi. Cakupannya akan dipanggil oleh parameter kueri high_priority=true
.
Melewati high_priority=false
tidak akan memanggil cakupan. Hal ini memudahkan untuk menyertakan filter dengan UI kotak centang.
Cakupan yang memerlukan argumen harus ditulis sebagai metode kelas, bukan cakupan sebaris. Misalnya, bayangkan sebuah ruang lingkup yang disebut recent
yang menggunakan tanggal sebagai argumen. Berikut tampilannya:
def self . recent ( as_of_date )
where ( 'created_at > ?' , as_of_date )
end
Seperti disebutkan di atas, sebagian besar filter atribut dapat diurutkan secara default. Jika tidak ada filter yang dideklarasikan untuk suatu atribut, deklarasi sort
dapat digunakan. Gunakan name
dan opsi association
yang sama sesuai kebutuhan.
Misalnya, deklarasi berikut dapat digunakan pada pengontrol aktivitas untuk memungkinkan aktivitas diurutkan berdasarkan proyek yang dibuat.
sort :project_created_at , name : :created_at , association : :project
Pengurutan tanpa opsi dapat dideklarasikan sekaligus dengan sorts
:
sorts :created_at ,
:updated_at ,
:description
Cakupan dapat digunakan untuk pengurutan, tetapi harus dideklarasikan dengan sort
(atau sorts
). Misalnya, jika suatu model menyertakan cakupan yang disebut by_created_at
Anda dapat menambahkan yang berikut ini ke pengontrol untuk mengeksposnya.
sort :by_created_at
Opsi name
dan association
juga dapat digunakan. Misalnya, jika cakupannya berada pada model Project, cakupan tersebut juga dapat digunakan pada pengontrol Aktivitas anak menggunakan opsi association
:
sort :by_created_at , association : :project
Hanya asosiasi tunggal yang valid untuk penyortiran. Asosiasi koleksi dapat mengembalikan beberapa nilai, sehingga pengurutannya menjadi tidak dapat ditentukan.
Cakupan yang digunakan untuk pengurutan harus menerima satu argumen. Ini akan diteruskan :asc
atau :desc
tergantung pada parameternya.
Contoh cakupan di atas dapat didefinisikan sebagai berikut:
def self . by_created_at ( dir )
order ( created_at : dir )
end
Pengurutan default dapat dideklarasikan menggunakan default_sort
. Argumen harus menentukan satu atau lebih jenis yang dideklarasikan atau filter yang dapat diurutkan berdasarkan nama. Secara default, urutannya menaik. Jika ingin urutan menurun, Anda dapat memetakan simbol nama kolom ke :desc.
default_sort updated_at : :desc , :description
Untuk memberikan hasil yang konsisten, pengurutan selalu diterapkan. Jika tidak ada default yang ditentukan, maka akan menggunakan kunci utama secara menurun.
Ada dua cara untuk menerapkan filter dan membuat kueri, bergantung pada seberapa besar kontrol dan/atau visibilitas yang diinginkan:
build_filtered_query
sebelum tindakanbuild_query_from_filters
secara manual build_filtered_query
sebelum tindakan Tambahkan panggilan balik sebelum tindakan build_filtered_query
untuk tindakan pengontrol yang harus membuat kueri. Hal ini dapat dilakukan di ApplicationController
atau berdasarkan kasus per kasus.
Saat menggunakan panggilan balik, nama variabel adalah nama model jamak. Misalnya, model Foto akan menggunakan variabel @photos
untuk menyimpan kueri. Nama variabel dapat ditentukan secara eksplisit dengan filter_query_var_name
. Misalnya, jika kueri disimpan sebagai @data
, gunakan yang berikut ini:
filter_query_var_name :data
Selain itu, perintah filter_model
mengambil parameter kedua opsional untuk menentukan nama variabel. Model dan nama variabel dapat ditentukan dengan jalan pintas ini. Misalnya, untuk menggunakan model Picture dan menyimpan hasilnya sebagai @data
, gunakan yang berikut ini:
filter_model 'Picture' , :data
Di jalur bahagia, WidgetsController menyajikan Widget dan dapat memfilter ukuran dan warna. Berikut tampilan pengontrolnya:
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
secara manual Untuk membuat kueri secara manual, Anda dapat memanggil build_query_from_filters
secara langsung alih-alih menggunakan callback .
Inilah pengontrol Widget lagi, kali ini membuat kueri secara manual:
class WidgetsController < ApplicationController
include Filterameter :: DeclarativeFilters
filter :size
filter :color
def index
@widgets = build_query_from_filters
end
end
Metode ini secara opsional mengambil kueri awal. Jika ada pengontrol untuk Widget Aktif yang hanya mengembalikan widget aktif, hal berikut dapat diteruskan ke metode sebagai titik awal:
def index
@widgets = build_query_from_filters ( Widget . where ( active : true ) )
end
Kueri awal juga merupakan tempat yang baik untuk menyediakan penyertaan apa pun untuk mengaktifkan pemuatan cepat:
def index
@widgets = build_query_from_filters ( Widgets . includes ( :manufacturer ) )
end
Perhatikan bahwa kueri awal menyediakan model, sehingga model tidak dicari dan deklarasi model_name
tidak diperlukan.
Konvensi Rails digunakan untuk menentukan model pengontrol. Misalnya, PhotosController membuat kueri terhadap model Foto. Jika suatu pengontrol diberi namespace, modelnya akan dicari terlebih dahulu tanpa namespace, kemudian dengan namespace.
Jika konvensi tidak menyediakan model yang benar , model tersebut dapat diberi nama secara eksplisit sebagai berikut:
filter_model 'Picture'
Penting: Jika deklarasi filter_model
digunakan, deklarasi tersebut harus berada sebelum deklarasi filter atau sortir.
Ada tiga opsi konfigurasi:
Opsi konfigurasi dapat diatur di penginisialisasi, file lingkungan, atau di application.rb
.
Pilihannya dapat diatur secara langsung...
Filterameter.configuration.action_on_undeclared_parameters = :log
...atau konfigurasi dapat dihasilkan:
Filterameter . configure do | config |
config . action_on_undeclared_parameters = :log
config . action_on_validation_failuer = :log
config . filter_key = :f
end
Terjadi ketika parameter filter berisi kunci apa pun yang tidak ditentukan. Tindakan yang valid adalah :log
, :raise
, dan false
(jangan ambil tindakan). Secara default, pengembangan akan mencatat, pengujian akan meningkat, dan produksi tidak akan melakukan apa pun.
Terjadi ketika parameter filter gagal divalidasi. Tindakan yang valid adalah :log
, :raise
, dan false
(jangan ambil tindakan). Secara default, pengembangan akan mencatat, pengujian akan meningkat, dan produksi tidak akan melakukan apa pun.
Secara default, parameter filter disarangkan di bawah kunci :filter
. Gunakan pengaturan ini untuk mengganti kunci.
Jika parameter filter TIDAK disarangkan, setel ke false. Melakukan hal ini akan membatasi parameter filter hanya pada parameter yang telah dideklarasikan, artinya parameter yang tidak dideklarasikan akan diabaikan (dan opsi konfigurasi action_on_undeclared_parameters tidak ikut berperan).
Deklarasi dapat diuji untuk setiap pengontrol, menemukan kesalahan ketik, cakupan yang salah didefinisikan, atau masalah lainnya. Metode declarations_validator
ditambahkan ke setiap pengontrol, dan pengujian pengontrol tunggal dapat ditambahkan untuk memvalidasi semua deklarasi untuk pengontrol tersebut.
Tes RSpec mungkin terlihat seperti ini:
expect ( WidgetsController . declarations_validator ) . to be_valid
Di Minitest mungkin terlihat seperti ini:
validator = WidgetsController . declarations_validator
assert_predicate validator , :valid? , -> { validator . errors }
Parameter filter diambil dari parameter pengontrol, yang ditempatkan di bawah filter
kunci (secara default; lihat Konfigurasi untuk mengubah kunci filter). Misalnya permintaan untuk widget besar berwarna biru mungkin memiliki parameter kueri berikut pada urlnya:
?filter[size]=large&filter[color]=blue
Pada formulir pencarian umum, form_with
form helper mengambil scope
opsi yang memungkinkan parameter dikelompokkan:
<%= 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 %>
Pengurutannya juga terletak di bawah kunci filter:
/widgets?filter[sort]=size
Gunakan array untuk meneruskan beberapa jenis. Urutan parameter adalah urutan pengurutan yang akan diterapkan. Misalnya, yang berikut ini diurutkan terlebih dahulu berdasarkan ukuran, lalu berdasarkan warna:
/widgets?filter[sort]=size&filter[sort]=color
Pengurutan dilakukan secara menaik secara default, tetapi dapat menggunakan awalan yang dapat ditambahkan untuk mengontrol pengurutan:
+
naik (default)-
turunMisalnya, pengurutan berikut berdasarkan ukuran secara menurun:
/widgets?filter[sort]=-size
Masukan, permintaan fitur, dan usulan perubahan disambut baik. Silakan gunakan pelacak masalah untuk masukan dan permintaan fitur. Untuk mengusulkan perubahan secara langsung, harap fork repo dan buka permintaan tarik. Awasi tindakan untuk memastikan tes dan Rubocop lulus. Code Climate juga digunakan secara manual untuk menilai codeline.
Untuk melaporkan bug, silakan gunakan pelacak masalah dan berikan informasi berikut:
Bintang emas akan diberikan jika Anda dapat mereplikasi masalah tersebut dengan sebuah tes.
Pengujian ditulis dalam RSpec dan aplikasi tiruannya menggunakan database buruh pelabuhan. Skrip bin/start_db.sh
memulai dan menyiapkan database pengujian. Ini adalah langkah satu kali sebelum menjalankan pengujian.
bin/start_db.rb
bundle exec rspec
Pengujian juga dapat dijalankan pada semua kombinasi ruby dan Rails menggunakan appraisal. Penginstalan juga merupakan langkah satu kali.
bundle exec appraisal install
bundle exec appraisal rspec
Permata ini tersedia sebagai sumber terbuka berdasarkan ketentuan Lisensi MIT.