Pencarian yang didukung model di Rails. Contoh yang luar biasa:
## app/searches/post_search.rb
class PostSearch < TalentScout :: ModelSearch
criteria :title_includes do | string |
where ( "title LIKE ?" , "% #{ string } %" )
end
criteria :within , choices : {
"Last 24 hours" => 24 . hours ,
"Past Week" => 1 . week ,
"Past Month" => 1 . month ,
"Past Year" => 1 . year ,
} do | duration |
where ( "created_at >= ?" , duration . ago )
end
criteria :only_published , :boolean , default : true do | only |
where ( "published" ) if only
end
order :created_at , default : :desc
order :title
end
## app/controllers/posts_controller.rb
class PostsController < ApplicationController
def index
@search = model_search
@posts = @search . results
end
end
<!-- app/views/posts/index.html.erb -->
<%= form_with model : @search , local : true , method : :get do | form | %>
<%= form . label :title_includes %>
<%= form . text_field :title_includes %>
<%= form . label :within %>
<%= form . select :within , @search . each_choice ( :within ) , include_blank : true %>
<%= form . label :only_published %>
<%= form . check_box :only_published %>
<%= form . submit %>
<% end %>
< table >
< thead >
< tr >
< th >
<%= link_to_search "Title" , @search . toggle_order ( :title ) %>
<%= img_tag " #{ @search . order_directions [ :title ] || "unsorted" } _icon.png" %>
</ th >
< th >
<%= link_to_search "Time" , @search . toggle_order ( :created_at ) %>
<%= img_tag " #{ @search . order_directions [ :created_at ] || "unsorted" } _icon.png" %>
</ th >
</ tr >
</ thead >
< tbody >
<% @posts . each do | post | %>
< tr >
< td > <%= link_to post . title , post %> </ td >
< td > <%= post . created_at %> </ td >
</ tr >
<% end %>
</ tbody >
</ table >
Dalam contoh di atas:
PostSearch
menangani tanggung jawab mencari model Post
. Itu dapat menerapkan kombinasi apa pun dari kriteria yang ditentukan, secara otomatis mengabaikan nilai masukan yang hilang, kosong, atau tidak valid. Ia juga dapat mengurutkan hasil berdasarkan salah satu urutan yang ditentukan, baik dalam arah menaik atau menurun.PostsController#index
menggunakan helper model_search
untuk membuat instance PostSearch
, dan menugaskannya ke variabel @search
untuk digunakan nanti dalam tampilan. Hasil pencarian juga ditetapkan ke variabel untuk digunakan dalam tampilan.@search
. Helper link_to_search
digunakan untuk membuat link di header tabel yang mengurutkan hasilnya. Perhatikan bahwa metode toggle_order
yang digunakan di sini mengembalikan objek pencarian baru, membiarkan @search
tidak diubah.Untuk penjelasan mendetail tentang metode yang digunakan dalam contoh ini, lihat dokumentasi API.
Anda dapat menggunakan generator talent_scout:search
untuk menghasilkan definisi kelas pencarian model. Misalnya,
$ rails generate talent_scout:search post
Akan menghasilkan file "app/searches/post_search.rb" yang berisi:
class PostSearch < TalentScout :: ModelSearch
end
Kelas pencarian mewarisi dari TalentScout::ModelSearch
. Kelas model target mereka disimpulkan dari nama kelas pencarian. Misalnya, PostSearch
akan mencari model Post
secara default. Untuk mengesampingkan inferensi ini, gunakan ModelSearch::model_class=
:
class EmployeeSearch < TalentScout :: ModelSearch
self . model_class = Person # search for Person models instead of `Employee`
end
Kriteria pencarian ditentukan dengan metode ModelSearch::criteria
. Definisi kriteria dapat ditentukan dengan salah satu dari tiga cara: dengan klausa Where implisit, dengan blok kueri eksplisit, atau dengan referensi cakupan model. Sebagai ilustrasi, tiga kriteria :title
berikut semuanya setara:
class Post < ActiveRecord :: Base
scope :title_equals , -> ( string ) { where ( title : string ) }
end
class PostSearch < TalentScout :: ModelSearch
criteria :title
criteria :title do | string |
where ( title : string )
end
criteria :title , & :title_equals
end
Perhatikan bahwa blok kueri eksplisit dievaluasi dalam konteks ActiveRecord::Relation
model, sama seperti blok scope
Rekaman Aktif.
Definisi kriteria bisa menentukan tipe data, yang menyebabkan nilai inputnya menjadi typecast sebelum diteruskan ke blok atau cakupan kueri. Sebagai contoh:
class PostSearch < TalentScout :: ModelSearch
criteria :created_on , :date do | date |
where ( created_at : date . beginning_of_day .. date . end_of_day )
end
end
PostSearch . new ( created_on : "Dec 31, 1999" )
Di sini, string "Dec 31, 1999"
yang diteruskan ke konstruktor PostSearch
diketik ke Date
sebelum diteruskan ke blok kueri.
Tipe kriteria default adalah :string
, yang berarti, secara default, semua nilai input akan dimasukkan ke string. Default ini (sebagai lawan dari default tanpa typecasting) memastikan perilaku yang konsisten tidak peduli bagaimana objek pencarian dibuat, baik dari nilai yang diketik dengan kuat atau dari parameter permintaan formulir pencarian.
Tipe kriteria yang tersedia sama dengan atribut Model Aktif: :big_integer
, :boolean
, :date
, :datetime
, :decimal
, :float
, :integer
, :string
, :time
, ditambah tipe khusus apa pun yang Anda tetapkan.
Tipe kenyamanan tambahan juga tersedia: :void
. Jenis :void
mengetik seperti :boolean
, tetapi mencegah kriteria diterapkan ketika nilai yang diketik salah. Misalnya:
class PostSearch < TalentScout :: ModelSearch
criteria :only_edited , :void do
where ( "modified_at > created_at" )
end
end
# The following will apply `only_edited`:
PostSearch . new ( only_edited : true )
PostSearch . new ( only_edited : "1" )
# The following will skip `only_edited`:
PostSearch . new ( only_edited : false )
PostSearch . new ( only_edited : "0" )
PostSearch . new ( only_edited : "" )
Daripada menentukan tipe, definisi kriteria dapat menentukan pilihan. Pilihan menentukan sekumpulan nilai yang dapat diteruskan ke blok kueri.
Pilihan dapat ditentukan sebagai Array nilai-nilai ramah manusia:
class PostSearch < TalentScout :: ModelSearch
criteria :category , choices : %w[ Science Tech Engineering Math ] do | name |
where ( category : name . downcase )
end
end
...Atau sebagai Hash dengan kunci yang ramah manusia:
class PostSearch < TalentScout :: ModelSearch
criteria :within , choices : {
"Last 24 hours" => 24 . hours ,
"Past Week" => 1 . week ,
"Past Month" => 1 . month ,
"Past Year" => 1 . year ,
} do | duration |
where ( "created_at >= ?" , duration . ago )
end
end
Nilai yang diteruskan ke blok kueri akan menjadi salah satu nilai di Array atau salah satu nilai di Hash. Objek pencarian dapat dibuat dengan nilai Array atau kunci Hash atau nilai Hash mana pun:
PostSearch . new ( category : "Math" )
PostSearch . new ( within : "Last 24 hours" )
PostSearch . new ( within : 24 . hours )
Namun jika pilihan yang ditentukan tidak valid, kriteria terkait tidak akan diterapkan:
# The following will skip the criteria, but will not raise an error:
PostSearch . new ( category : "Marketing" )
PostSearch . new ( within : 12 . hours )
Definisi kriteria dapat menentukan nilai default, yang akan diteruskan ke blok kueri ketika nilai input tidak ada. Nilai default juga akan muncul di formulir pencarian.
class PostSearch < TalentScout :: ModelSearch
criteria :within_days , :integer , default : 7 do | num |
where ( "created_at >= ?" , num . days . ago )
end
end
# The following are equivalent:
PostSearch . new ( )
PostSearch . new ( within_days : 7 )
Kriteria tidak akan diterapkan jika salah satu dari hal berikut ini benar:
Nilai masukan kriteria tidak ada, dan tidak ada nilai default yang ditentukan.
Objek pencarian dibuat dengan ActionController::Parameters
(bukan Hash), dan nilai input kriteria blank?
, dan tidak ada nilai default yang ditentukan. (Perilaku ini mencegah kolom formulir penelusuran kosong memengaruhi hasil penelusuran.)
Typecast nilai input kriteria gagal. Misalnya:
class PostSearch < TalentScout :: ModelSearch
criteria :created_on , :date do | date |
where ( created_at : date . beginning_of_day .. date . end_of_day )
end
end
# The following will skip `created_on`, but will not raise an error:
PostSearch . new ( created_on : "BAD" )
Definisi kriteria menentukan tipe :void
, dan nilai input yang diketik adalah falsey.
Definisi kriteria menentukan pilihan, dan nilai input bukanlah pilihan yang valid.
Blok kueri kriteria mengembalikan nil
. Misalnya:
class PostSearch < TalentScout :: ModelSearch
criteria :minimum_upvotes , :integer do | minimum |
where ( "upvotes >= ?" , minimum ) unless minimum <= 0
end
end
# The following will skip the `minimum_upvotes` where clause:
PostSearch . new ( minimum_upvotes : 0 )
Urutan hasil pencarian ditentukan dengan metode ModelSearch::order
:
class PostSearch < TalentScout :: ModelSearch
order :created_at
order :title
order :category
end
PostSearch . new ( order : :created_at )
PostSearch . new ( order : :title )
PostSearch . new ( order : :category )
Hanya satu pesanan yang dapat diterapkan pada satu waktu, namun pesanan dapat terdiri dari beberapa kolom:
class PostSearch < TalentScout :: ModelSearch
order :category , [ :category , :title ]
end
# The following will order by "category, title":
PostSearch . new ( order : :category )
Desain terbatas ini dipilih karena memungkinkan pengurutan multi-kolom yang dikurasi dengan UI pengurutan satu kolom yang lebih sederhana, dan karena mencegah pengurutan multi-kolom ad-hoc yang mungkin tidak didukung oleh indeks database.
Perintah dapat diterapkan dalam arah menaik atau menurun. Metode ModelSearch#toggle_order
akan menerapkan pesanan dalam arah menaik, atau akan mengubah arah pesanan yang diterapkan dari naik ke turun:
class PostSearch < TalentScout :: ModelSearch
order :created_at
order :title
end
# The following will order by "title":
PostSearch . new ( ) . toggle_order ( :title )
PostSearch . new ( order : :created_at ) . toggle_order ( :title )
# The following will order by "title DESC":
PostSearch . new ( order : :title ) . toggle_order ( :title )
Perhatikan bahwa metode toggle_order
tidak mengubah objek pencarian yang ada. Sebaliknya, ia membangun objek pencarian baru dengan urutan baru dan nilai kriteria dari objek pencarian sebelumnya.
Ketika urutan multi-kolom diterapkan dalam arah menurun, semua kolom akan terpengaruh:
class PostSearch < TalentScout :: ModelSearch
order :category , [ :category , :title ]
end
# The following will order by "category DESC, title DESC":
PostSearch . new ( order : :category ) . toggle_order ( :category )
Untuk menghindari perilaku ini dan sebagai gantinya memperbaiki kolom dalam arah statis, tambahkan " ASC"
atau " DESC"
ke nama kolom:
class PostSearch < TalentScout :: ModelSearch
order :category , [ :category , "created_at ASC" ]
end
# The following will order by "category, created_at ASC":
PostSearch . new ( order : :category )
# The following will order by "category DESC, created_at ASC":
PostSearch . new ( order : :category ) . toggle_order ( :category )
Perintah dapat diterapkan dalam arah naik atau turun secara langsung, tanpa menggunakan toggle_order
, dengan menambahkan akhiran yang sesuai:
class PostSearch < TalentScout :: ModelSearch
order :title
end
# The following will order by "title":
PostSearch . new ( order : :title )
PostSearch . new ( order : "title.asc" )
# The following will order by "title DESC":
PostSearch . new ( order : "title.desc" )
Sufiks default, seperti terlihat pada contoh di atas, adalah ".asc"
dan ".desc"
. Ini dipilih karena keramahannya. Mereka dapat ditimpa sebagai bagian dari definisi urutan:
class PostSearch < TalentScout :: ModelSearch
order "Title" , [ :title ] , asc_suffix : " (A-Z)" , desc_suffix : " (Z-A)"
end
# The following will order by "title":
PostSearch . new ( order : "Title" )
PostSearch . new ( order : "Title (A-Z)" )
# The following will order by "title DESC":
PostSearch . new ( order : "Title (Z-A)" )
Suatu pesanan dapat ditetapkan sebagai pesanan default, yang akan menyebabkan pesanan tersebut diterapkan ketika tidak ada pesanan yang ditentukan:
class PostSearch < TalentScout :: ModelSearch
order :created_at , default : :desc
order :title
end
# The following will order by "created_at DESC":
PostSearch . new ( )
# The following will order by "created_at":
PostSearch . new ( order : :created_at )
# The following will order by "title":
PostSearch . new ( order : :title )
Perhatikan bahwa arah pesanan default dapat berupa naik atau turun dengan menentukan masing-masing default: :asc
atau default: :desc
. Selain itu, sama seperti hanya satu pesanan yang dapat diterapkan pada satu waktu, maka hanya satu pesanan yang dapat ditetapkan sebagai default.
Cakupan pencarian default dapat ditentukan dengan ModelSearch::default_scope
:
class PostSearch < TalentScout :: ModelSearch
default_scope { where ( published : true ) }
end
Cakupan default akan diterapkan terlepas dari kriteria atau nilai masukan pesanan.
Pengontrol dapat menggunakan metode pembantu model_search
untuk membuat objek pencarian dengan parameter kueri permintaan saat ini:
class PostsController < ApplicationController
def index
@search = model_search
@posts = @search . results
end
end
Dalam contoh di atas, model_search
membuat objek PostSearch
. Kelas pencarian model secara otomatis diturunkan dari nama kelas pengontrol. Untuk mengganti kelas pencarian model, gunakan ::model_search_class=
:
class EmployeesController < ApplicationController
self . model_search_class = PersonSearch
def index
@search = model_search # will construct PersonSearch instead of `EmployeeSearch`
@employees = @search . results
end
end
Dalam contoh ini, objek pencarian disimpan di @search
untuk digunakan dalam tampilan. Perhatikan bahwa @search.results
mengembalikan ActiveRecord::Relation
, sehingga pelingkupan tambahan apa pun, seperti penomoran halaman, dapat diterapkan.
Formulir pencarian dapat dirender menggunakan pembuat formulir Rails dan objek pencarian:
<%= form_with model: @search, local: true, method: :get do |form| %>
<%= form.label :title_includes %>
<%= form.text_field :title_includes %>
<%= form.label :created_on %>
<%= form.date_field :created_on %>
<%= form.label :only_published %>
<%= form.check_box :only_published %>
<%= form.submit %>
<% end %>
Perhatikan method: :get
argumen ke form_with
. Ini diperlukan.
Bidang formulir akan diisi dengan nilai masukan kriteria (atau default) dengan nama yang sama dari @search
. Bidang formulir yang sesuai dengan jenisnya dapat digunakan, misalnya date_field
untuk jenis :date
, check_box
untuk jenis :boolean
dan :void
, dll.
Secara default, formulir akan dikirimkan ke tindakan indeks pengontrol yang sesuai dengan model_class
objek pencarian. Misalnya, PostSearch.model_class
adalah Post
, sehingga formulir dengan instance PostSearch
akan dikirimkan ke PostsController#index
. Untuk mengubah ke mana formulir dikirimkan, gunakan opsi :url
dari form_with
.
Tautan pencarian dapat dirender menggunakan metode pembantu tampilan link_to_search
:
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc) %>
Tautan akan secara otomatis menunjuk ke pengontrol saat ini dan tindakan saat ini, dengan parameter kueri dari objek pencarian yang diberikan. Untuk menautkan ke pengontrol atau tindakan lain, berikan opsi Hash sebagai pengganti objek pencarian:
<%= link_to_search "Sort by title", { controller: "posts", action: "index",
search: @search.toggle_order(:title, :asc) } %>
Helper link_to_search
juga menerima opsi HTML yang sama seperti yang dilakukan oleh link_to
helper Rails:
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc),
id: "title-sort-link", class: "sort-link" %>
...Serta blok konten:
<%= link_to_search @search.toggle_order(:title, :asc) do %>
Sort by title <%= img_tag "sort_icon.png" %>
<% end %>
ModelSearch
Kelas ModelSearch
menyediakan beberapa metode yang berguna saat merender tampilan.
Salah satu metode tersebut adalah ModelSearch#toggle_order
, yang ditunjukkan pada contoh sebelumnya. Ingatlah bahwa toggle_order
adalah metode bergaya pembangun yang tidak mengubah objek pencarian. Sebaliknya, ini menduplikasi objek pencarian, dan menetapkan urutan pada objek baru. Perilaku seperti ini cocok untuk menghasilkan link ke beberapa varian pencarian, seperti mengurutkan link di header kolom tabel.
ModelSearch#with
dan ModelSearch#without
Dua metode gaya pembangun tambahan adalah ModelSearch#with
dan ModelSearch#without
. Seperti toggle_order
, kedua metode ini mengembalikan objek pencarian baru, sehingga objek pencarian asli tidak diubah. Metode with
menerima nilai masukan kriteria Hash untuk digabungkan di atas kumpulan nilai masukan kriteria asli:
class PostSearch < TalentScout :: ModelSearch
criteria :title
criteria :published , :boolean
end
# The following are equivalent:
PostSearch . new ( title : "Maaaaath!" , published : true )
PostSearch . new ( title : "Maaaaath!" ) . with ( published : true )
PostSearch . new ( title : "Math?" ) . with ( title : "Maaaaath!" , published : true )
Metode without
menerima daftar nilai masukan kriteria yang akan dikecualikan (nilai kriteria default masih berlaku):
class PostSearch < TalentScout :: ModelSearch
criteria :title
criteria :published , :boolean , default : true
end
# The following are equivalent:
PostSearch . new ( title : "Maaaaath!" )
PostSearch . new ( title : "Maaaaath!" , published : false ) . without ( :published )
ModelSearch#each_choice
Metode bermanfaat lainnya adalah ModelSearch#each_choice
, yang akan mengulangi pilihan yang ditentukan untuk kriteria tertentu. Ini dapat digunakan untuk menghasilkan tautan ke varian penelusuran:
class PostSearch < TalentScout :: ModelSearch
criteria :category , choices : %w[ Science Tech Engineering Math ]
end
<% @search.each_choice(:category) do |choice, chosen| %>
<%= link_to_search "Category: #{choice}", @search.with(category: choice),
class: ("active" if chosen) %>
<% end %>
Perhatikan bahwa jika blok yang diteruskan ke each_choice
menerima dua argumen, argumen ke-2 akan menunjukkan apakah pilihan tersebut sedang dipilih.
Jika tidak ada blok yang diteruskan ke each_choice
, ia akan mengembalikan Enumerator
. Ini dapat digunakan untuk menghasilkan opsi untuk kotak pilih:
<%= form_with model: @search, local: true, method: :get do |form| %>
<%= form.select :category, @search.each_choice(:category) %>
<%= form.submit %>
<% end %>
Metode each_choice
juga dapat dipanggil dengan :order
. Melakukan hal ini akan mengulangi setiap arah dari setiap urutan yang ditentukan, menghasilkan label yang sesuai termasuk akhiran arah:
class PostSearch < TalentScout :: ModelSearch
order "Title" , [ :title ] , asc_suffix : " (A-Z)" , desc_suffix : " (Z-A)"
order "Time" , [ :created_at ] , asc_suffix : " (oldest first)" , desc_suffix : " (newest first)"
end
<%= form_with model: @search, local: true, method: :get do |form| %>
<%= form.select :order, @search.each_choice(:order) %>
<%= form.submit %>
<% end %>
Kotak pilih pada formulir di atas akan mencantumkan empat opsi: "Judul (AZ)", "Judul (ZA)", "Waktu (terlama dulu)", "Waktu (terbaru dulu)".
ModelSearch#order_directions
Terakhir, metode pembantu ModelSearch#order_directions
mengembalikan HashWithIndifferentAccess
yang mencerminkan arah yang diterapkan saat ini dari setiap pesanan yang ditentukan. Ini berisi kunci untuk setiap urutan yang ditentukan, dan mengaitkan setiap kunci dengan :asc
, :desc
, atau nil
.
class PostSearch < TalentScout :: ModelSearch
order "Title" , [ :title ]
order "Time" , [ :created_at ]
end
< thead >
< tr >
<% @search . order_directions . each do | order , direction | %>
< th >
<%= link_to_search order , @search . toggle_order ( order ) %>
<%= img_tag " #{ direction || "unsorted" } _icon.png" %>
</ th >
<% end %>
</ tr >
</ thead >
Ingatlah bahwa hanya satu order yang dapat diterapkan pada satu waktu, sehingga hanya satu nilai dalam Hash, paling banyak, yang bernilai non- nil
.
Tambahkan permata ke Gemfile Anda:
$ bundle add talent_scout
Dan jalankan generator instalasi:
$ rails generate talent_scout:install
Jalankan bin/test
untuk menjalankan pengujian.
Lisensi MIT