talent_scout 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +741 -0
- data/Rakefile +22 -0
- data/lib/generators/talent_scout/install/install_generator.rb +13 -0
- data/lib/generators/talent_scout/install/templates/config/locales/talent_scout.en.yml.tt +5 -0
- data/lib/generators/talent_scout/search/search_generator.rb +14 -0
- data/lib/generators/talent_scout/search/templates/search.rb.tt +4 -0
- data/lib/generators/test_unit/search_generator.rb +13 -0
- data/lib/generators/test_unit/templates/search_test.rb.tt +4 -0
- data/lib/talent_scout/choice_type.rb +33 -0
- data/lib/talent_scout/controller.rb +61 -0
- data/lib/talent_scout/criteria.rb +40 -0
- data/lib/talent_scout/helper.rb +82 -0
- data/lib/talent_scout/model_name.rb +18 -0
- data/lib/talent_scout/model_search.rb +611 -0
- data/lib/talent_scout/order_definition.rb +37 -0
- data/lib/talent_scout/order_type.rb +32 -0
- data/lib/talent_scout/railtie.rb +14 -0
- data/lib/talent_scout/version.rb +3 -0
- data/lib/talent_scout/void_type.rb +16 -0
- data/lib/talent_scout.rb +17 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1d013c77ffd08bf7bb7d36d4f30c10bc763a4ea79c437d80604985de86fadd74
|
4
|
+
data.tar.gz: a4a996e5b44c64af45806849f3744f06bf7f192d346416943739f172abf45180
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 24e7c840762d376d5dd9353e3f9b4f167424914e1320573fbcc5cf6dcb691ab5a78f4465261fd2ca029784dd5fac25192c8def19b1f2280401dd3a1452baed7a
|
7
|
+
data.tar.gz: b6be4607619b5dcae29c012fb5194b640e5a5bf5ad9de94f1cd80532cf9cfdb12d8d61f615297da0531b8fe2fa9504a589d7f3816da2192f3e6b7f603231cb58
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,741 @@
|
|
1
|
+
# talent_scout
|
2
|
+
|
3
|
+
Model-backed searches in Rails. A whiz-bang example:
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
## app/searches/post_search.rb
|
7
|
+
|
8
|
+
class PostSearch < TalentScout::ModelSearch
|
9
|
+
criteria :title_includes do |string|
|
10
|
+
where("title LIKE ?", "%#{string}%")
|
11
|
+
end
|
12
|
+
|
13
|
+
criteria :within, choices: {
|
14
|
+
"Last 24 hours" => 24.hours,
|
15
|
+
"Past Week" => 1.week,
|
16
|
+
"Past Month" => 1.month,
|
17
|
+
"Past Year" => 1.year,
|
18
|
+
} do |duration|
|
19
|
+
where("created_at >= ?", duration.ago)
|
20
|
+
end
|
21
|
+
|
22
|
+
criteria :only_published, :boolean, default: true do |only|
|
23
|
+
where("published") if only
|
24
|
+
end
|
25
|
+
|
26
|
+
order :created_at, default: :desc
|
27
|
+
order :title
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
## app/controllers/posts_controller.rb
|
33
|
+
|
34
|
+
class PostsController < ApplicationController
|
35
|
+
def index
|
36
|
+
@search = model_search
|
37
|
+
@posts = @search.results
|
38
|
+
end
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
```html+erb
|
43
|
+
<!-- app/views/posts/index.html.erb -->
|
44
|
+
|
45
|
+
<%= form_with model: @search, method: :get do |form| %>
|
46
|
+
<%= form.text_field :title_includes %>
|
47
|
+
<%= form.select :within, @search.each_choice(:within), include_blank: true %>
|
48
|
+
<%= form.check_box :only_published %>
|
49
|
+
<%= form.submit %>
|
50
|
+
<% end %>
|
51
|
+
|
52
|
+
<table>
|
53
|
+
<thead>
|
54
|
+
<tr>
|
55
|
+
<th>
|
56
|
+
<%= link_to_search "Title", @search.toggle_order(:title) %>
|
57
|
+
<%= img_tag "#{@search.order_directions[:title] || "unsorted"}_icon.png" %>
|
58
|
+
</th>
|
59
|
+
<th>
|
60
|
+
<%= link_to_search "Time", @search.toggle_order(:created_at) %>
|
61
|
+
<%= img_tag "#{@search.order_directions[:created_at] || "unsorted"}_icon.png" %>
|
62
|
+
</th>
|
63
|
+
</tr>
|
64
|
+
</thead>
|
65
|
+
<tbody>
|
66
|
+
<% @posts.each do |post| %>
|
67
|
+
<tr>
|
68
|
+
<td><%= link_to post.title, post %></td>
|
69
|
+
<td><%= post.created_at %></td>
|
70
|
+
</tr>
|
71
|
+
<% end %>
|
72
|
+
</tbody>
|
73
|
+
</table>
|
74
|
+
```
|
75
|
+
|
76
|
+
In the above example:
|
77
|
+
|
78
|
+
* The `PostSearch` class handles the responsibility of searching for
|
79
|
+
`Post` models. It can apply any combination of its defined criteria,
|
80
|
+
automatically ignoring missing or blank input values. It can also
|
81
|
+
order the results by one of its defined orders, in either ascending or
|
82
|
+
descending direction.
|
83
|
+
* `PostsController#index` uses the `model_search` helper to construct a
|
84
|
+
`PostSearch`, and assigns it to the `@search` variable for later use
|
85
|
+
in the view. The search results are also assigned to a variable for
|
86
|
+
use in the view.
|
87
|
+
* The view uses Rails' stock form builder to build a search form with
|
88
|
+
the `@search` variable. The `link_to_search` helper is used to create
|
89
|
+
links in the table header which sort the results. Note that the
|
90
|
+
`toggle_order` method used here returns a new search object, leaving
|
91
|
+
`@search` unmodified.
|
92
|
+
|
93
|
+
For a detailed explanation of the methods used in this example, see
|
94
|
+
the [API documentation](http://www.rubydoc.info/gems/talent_scout/).
|
95
|
+
|
96
|
+
|
97
|
+
## Search Objects
|
98
|
+
|
99
|
+
You can use the `talent_scout:search` generator to generate search
|
100
|
+
object class definitions. For example,
|
101
|
+
|
102
|
+
```bash
|
103
|
+
$ rails generate talent_scout:search post
|
104
|
+
```
|
105
|
+
|
106
|
+
Will generate a file "app/searches/post_search.rb" containing:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class PostSearch < TalentScout::ModelSearch
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
Search objects inherit from `TalentScout::ModelSearch`. Their target
|
114
|
+
model class is inferred from the search object's class name. For
|
115
|
+
example, `PostSearch` will search for `Post` models by default. To
|
116
|
+
override this behavior, use `ModelSearch::model_class=`:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
class EmployeeSearch < TalentScout::ModelSearch
|
120
|
+
self.model_class = Person # will search for Person models instead of `Employee`
|
121
|
+
end
|
122
|
+
```
|
123
|
+
|
124
|
+
|
125
|
+
### Criteria
|
126
|
+
|
127
|
+
Search criteria are defined with the `ModelSearch::criteria` method.
|
128
|
+
Criteria definitions can be specified in one of three ways: with an
|
129
|
+
implicit where clause, with an explicit query block, or with a model
|
130
|
+
scope reference. To illustrate, the following three `:title` criteria
|
131
|
+
are all equivalent:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
class Post < ActiveRecord::Base
|
135
|
+
scope :title_equals, ->(string){ where(title: string) }
|
136
|
+
end
|
137
|
+
|
138
|
+
class PostSearch < TalentScout::ModelSearch
|
139
|
+
criteria :title
|
140
|
+
|
141
|
+
criteria :title do |string|
|
142
|
+
where(title: string)
|
143
|
+
end
|
144
|
+
|
145
|
+
criteria :title, &:title_equals
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
Note that explicit query blocks are evaluated in the context of the
|
150
|
+
model's `ActiveRecord::Relation`, just as model scopes are.
|
151
|
+
|
152
|
+
|
153
|
+
#### Criteria Type
|
154
|
+
|
155
|
+
A criteria definition can specify a data type, which causes its input
|
156
|
+
value to be typecast before being passed to the query block or scope.
|
157
|
+
As an example:
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
class PostSearch < TalentScout::ModelSearch
|
161
|
+
criteria :created_on, :date do |date|
|
162
|
+
where(created_at: date.beginning_of_day..date.end_of_day)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
PostSearch.new(created_on: "Dec 31, 1999")
|
167
|
+
```
|
168
|
+
|
169
|
+
Here, the string `"Dec 31, 1999"` passed to the `PostSearch` constructor
|
170
|
+
is typecast to a `Date` before being passed to the query block.
|
171
|
+
|
172
|
+
The default criteria type is `:string`, which means, by default, all
|
173
|
+
input values will be cast to strings. This default (as opposed to a
|
174
|
+
default of no typecasting) ensures consistent behavior no matter how the
|
175
|
+
search object is constructed, whether from strongly-typed values or from
|
176
|
+
search form request params.
|
177
|
+
|
178
|
+
Available criteria types are the same as for Active Model attributes:
|
179
|
+
`:big_integer`, `:boolean`, `:date`, `:datetime`, `:decimal`, `:float`,
|
180
|
+
`:integer`, `:string`, `:time`, plus any custom types you define.
|
181
|
+
|
182
|
+
An additional convenience type is also available: `:void`. The `:void`
|
183
|
+
type is just like `:boolean`, except that the criteria will not be
|
184
|
+
applied when the typecasted value is falsey. For example:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
class PostSearch < TalentScout::ModelSearch
|
188
|
+
criteria :only_edited, :void do
|
189
|
+
where("modified_at > created_at")
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# The following will apply `only_edited`:
|
194
|
+
PostSearch.new(only_edited: true)
|
195
|
+
PostSearch.new(only_edited: "1")
|
196
|
+
|
197
|
+
# The following will skip `only_edited`:
|
198
|
+
PostSearch.new(only_edited: false)
|
199
|
+
PostSearch.new(only_edited: "0")
|
200
|
+
PostSearch.new(only_edited: "")
|
201
|
+
```
|
202
|
+
|
203
|
+
|
204
|
+
#### Criteria Choices
|
205
|
+
|
206
|
+
Instead of specifying a type, a criteria definition may specify choices.
|
207
|
+
Choices define a set of values which can be passed to the query block.
|
208
|
+
|
209
|
+
Choices can either be specified as an Array of human-friendly values:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
class PostSearch < TalentScout::ModelSearch
|
213
|
+
criteria :category, choices: %w[Science Tech Engineering Math] do |name|
|
214
|
+
where(category: name.downcase)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
...Or as a Hash with human-friendly keys:
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
class PostSearch < TalentScout::ModelSearch
|
223
|
+
criteria :within, choices: {
|
224
|
+
"Last 24 hours" => 24.hours,
|
225
|
+
"Past Week" => 1.week,
|
226
|
+
"Past Month" => 1.month,
|
227
|
+
"Past Year" => 1.year,
|
228
|
+
} do |duration|
|
229
|
+
where("created_at >= ?", duration.ago)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
```
|
233
|
+
|
234
|
+
The value passed to the query block will be one of the values in the
|
235
|
+
Array or one of the values in the Hash. The search object may be
|
236
|
+
constructed with any of the Array values or Hash keys or Hash values:
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
PostSearch.new(category: "Math")
|
240
|
+
PostSearch.new(within: "Last 24 hours")
|
241
|
+
PostSearch.new(within: 24.hours)
|
242
|
+
```
|
243
|
+
|
244
|
+
But if an invalid choice is specified, the corresponding criteria will
|
245
|
+
not be applied:
|
246
|
+
|
247
|
+
```ruby
|
248
|
+
# The following will skip the criteria, but will not raise an error:
|
249
|
+
PostSearch.new(category: "Marketing")
|
250
|
+
PostSearch.new(within: 12.hours)
|
251
|
+
```
|
252
|
+
|
253
|
+
|
254
|
+
#### Criteria Default Value
|
255
|
+
|
256
|
+
A criteria definition can specify a default value, which will be passed
|
257
|
+
to the query block when the input value is missing. Default values will
|
258
|
+
also appear in search forms.
|
259
|
+
|
260
|
+
```ruby
|
261
|
+
class PostSearch < TalentScout::ModelSearch
|
262
|
+
criteria :within_days, :integer, default: 7 do |num|
|
263
|
+
where("created_at >= ?", num.days.ago)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# The following are equivalent:
|
268
|
+
PostSearch.new()
|
269
|
+
PostSearch.new(within_days: 7)
|
270
|
+
```
|
271
|
+
|
272
|
+
|
273
|
+
#### Criteria Resolution
|
274
|
+
|
275
|
+
A criteria will not be applied if any of the following are true:
|
276
|
+
|
277
|
+
* The criteria input value is missing, and no default value has been
|
278
|
+
specified.
|
279
|
+
|
280
|
+
* The search object was constructed with an `ActionController::Parameters`
|
281
|
+
(instead of a Hash), and the criteria input value is `blank?`, and no
|
282
|
+
default value has been specified. (This behavior prevents empty
|
283
|
+
search form fields from affecting search results.)
|
284
|
+
|
285
|
+
* The typecast of the criteria input value fails. For example:
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
class PostSearch < TalentScout::ModelSearch
|
289
|
+
criteria :created_on, :date do |date|
|
290
|
+
where(created_at: date.beginning_of_day..date.end_of_day)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# The following will skip `created_on`, but will not raise an error:
|
295
|
+
PostSearch.new(created_on: "BAD")
|
296
|
+
```
|
297
|
+
|
298
|
+
* The criteria definition specifies type `:void`, and the typecasted
|
299
|
+
input value is falsey.
|
300
|
+
|
301
|
+
* The criteria definition specifies choices, and the input value is not
|
302
|
+
a valid choice.
|
303
|
+
|
304
|
+
* The criteria query block returns `nil`. For example:
|
305
|
+
|
306
|
+
```ruby
|
307
|
+
class PostSearch < TalentScout::ModelSearch
|
308
|
+
criteria :minimum_upvotes, :integer do |minimum|
|
309
|
+
where("upvotes >= ?", minimum) unless minimum <= 0
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# The following will skip the `minimum_upvotes` where clause:
|
314
|
+
PostSearch.new(minimum_upvotes: 0)
|
315
|
+
```
|
316
|
+
|
317
|
+
|
318
|
+
### Orders
|
319
|
+
|
320
|
+
Search result orders are defined with the `ModelSearch::order` method:
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
class PostSearch < TalentScout::ModelSearch
|
324
|
+
order :created_at
|
325
|
+
order :title
|
326
|
+
order :category
|
327
|
+
end
|
328
|
+
|
329
|
+
PostSearch.new(order: :created_at)
|
330
|
+
PostSearch.new(order: :title)
|
331
|
+
PostSearch.new(order: :category)
|
332
|
+
```
|
333
|
+
|
334
|
+
Only one order can be applied at a time, but an order can be defined
|
335
|
+
over multiple columns:
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
class PostSearch < TalentScout::ModelSearch
|
339
|
+
order :category, [:category, :title]
|
340
|
+
end
|
341
|
+
|
342
|
+
# The following will order by "category, title":
|
343
|
+
PostSearch.new(order: :category)
|
344
|
+
```
|
345
|
+
|
346
|
+
This restricted design was chosen because it allows curated multi-column
|
347
|
+
sorts with simpler single-column sorting UIs, and because it prevents
|
348
|
+
ad-hoc multi-column sorts that may not be backed by a database index.
|
349
|
+
|
350
|
+
|
351
|
+
#### Order Direction
|
352
|
+
|
353
|
+
An order can be applied in ascending or descending direction. The
|
354
|
+
`ModelSearch#toggle_order` method will apply an order in ascending
|
355
|
+
direction, or will change an applied order direction from ascending to
|
356
|
+
descending:
|
357
|
+
|
358
|
+
```ruby
|
359
|
+
class PostSearch < TalentScout::ModelSearch
|
360
|
+
order :created_at
|
361
|
+
order :title
|
362
|
+
end
|
363
|
+
|
364
|
+
# The following will order by "title":
|
365
|
+
PostSearch.new().toggle_order(:title)
|
366
|
+
PostSearch.new(order: :created_at).toggle_order(:title)
|
367
|
+
|
368
|
+
# The following will order by "title DESC":
|
369
|
+
PostSearch.new(order: :title).toggle_order(:title)
|
370
|
+
```
|
371
|
+
|
372
|
+
Note that the `toggle_order` method does not modify the existing search
|
373
|
+
object. Instead, it builds a new search object with the new order and
|
374
|
+
the criteria values of the previous search object.
|
375
|
+
|
376
|
+
When a multi-column order is applied in descending direction, all
|
377
|
+
columns are affected:
|
378
|
+
|
379
|
+
```ruby
|
380
|
+
class PostSearch < TalentScout::ModelSearch
|
381
|
+
order :category, [:category, :title]
|
382
|
+
end
|
383
|
+
|
384
|
+
# The following will order by "category DESC, title DESC":
|
385
|
+
PostSearch.new(order: :category).toggle_order(:category)
|
386
|
+
```
|
387
|
+
|
388
|
+
To circumvent this behavior, and instead fix a column in a static
|
389
|
+
direction, append `" ASC"` or `" DESC"` to the column name:
|
390
|
+
|
391
|
+
```ruby
|
392
|
+
class PostSearch < TalentScout::ModelSearch
|
393
|
+
order :category, [:category, "created_at ASC"]
|
394
|
+
end
|
395
|
+
|
396
|
+
# The following will order by "category, created_at ASC":
|
397
|
+
PostSearch.new(order: :category)
|
398
|
+
|
399
|
+
# The following will order by "category DESC, created_at ASC":
|
400
|
+
PostSearch.new(order: :category).toggle_order(:category)
|
401
|
+
```
|
402
|
+
|
403
|
+
|
404
|
+
#### Order Direction Suffixes
|
405
|
+
|
406
|
+
An order can be applied in ascending or descending direction directly,
|
407
|
+
without using `toggle_order`, by appending an appropriate suffix:
|
408
|
+
|
409
|
+
```ruby
|
410
|
+
class PostSearch < TalentScout::ModelSearch
|
411
|
+
order :title
|
412
|
+
end
|
413
|
+
|
414
|
+
# The following will order by "title":
|
415
|
+
PostSearch.new(order: :title)
|
416
|
+
PostSearch.new(order: "title.asc")
|
417
|
+
|
418
|
+
# The following will order by "title DESC":
|
419
|
+
PostSearch.new(order: "title.desc")
|
420
|
+
```
|
421
|
+
|
422
|
+
The default suffixes, as seen in the above example, are `".asc"` and
|
423
|
+
`".desc"`. These were chosen for their I18n-friendliness. They can be
|
424
|
+
overridden as part of the order definition:
|
425
|
+
|
426
|
+
```ruby
|
427
|
+
class PostSearch < TalentScout::ModelSearch
|
428
|
+
order "Title", [:title], asc_suffix: " (A-Z)", desc_suffix: " (Z-A)"
|
429
|
+
end
|
430
|
+
|
431
|
+
# The following will order by "title":
|
432
|
+
PostSearch.new(order: "Title")
|
433
|
+
PostSearch.new(order: "Title (A-Z)")
|
434
|
+
|
435
|
+
# The following will order by "title DESC":
|
436
|
+
PostSearch.new(order: "Title (Z-A)")
|
437
|
+
```
|
438
|
+
|
439
|
+
|
440
|
+
#### Default Order
|
441
|
+
|
442
|
+
An order can be designated as the default order, which will cause that
|
443
|
+
order to be applied when no order is otherwise specified:
|
444
|
+
|
445
|
+
```ruby
|
446
|
+
class PostSearch < TalentScout::ModelSearch
|
447
|
+
order :created_at, default: :desc
|
448
|
+
order :title
|
449
|
+
end
|
450
|
+
|
451
|
+
# The following will order by "created_at DESC":
|
452
|
+
PostSearch.new()
|
453
|
+
|
454
|
+
# The following will order by "created_at":
|
455
|
+
PostSearch.new(order: :created_at)
|
456
|
+
|
457
|
+
# The following will order by "title":
|
458
|
+
PostSearch.new(order: :title)
|
459
|
+
```
|
460
|
+
|
461
|
+
Note that the default order direction can be either ascending or
|
462
|
+
descending, by specifing `default: :asc` or `default: :desc`,
|
463
|
+
respectively. Also, just as only one order can be applied at a time,
|
464
|
+
only one order can be designated default.
|
465
|
+
|
466
|
+
|
467
|
+
### Default Scope
|
468
|
+
|
469
|
+
A default search scope can be defined with `ModelSearch::default_scope`:
|
470
|
+
|
471
|
+
```ruby
|
472
|
+
class PostSearch < TalentScout::ModelSearch
|
473
|
+
default_scope { where(published: true) }
|
474
|
+
end
|
475
|
+
```
|
476
|
+
|
477
|
+
The default scope will be applied regardless of the criteria or order
|
478
|
+
input values.
|
479
|
+
|
480
|
+
|
481
|
+
## Controllers
|
482
|
+
|
483
|
+
Controllers can use the `model_search` helper method to construct a
|
484
|
+
search object with the current request's query params:
|
485
|
+
|
486
|
+
```ruby
|
487
|
+
class PostsController < ApplicationController
|
488
|
+
def index
|
489
|
+
@search = model_search
|
490
|
+
@posts = @search.results
|
491
|
+
end
|
492
|
+
end
|
493
|
+
```
|
494
|
+
|
495
|
+
In the above example, `model_search` constructs a `PostSearch` object.
|
496
|
+
The model search class is automatically derived from the controller
|
497
|
+
class name. To override the model search class, use `::model_search_class=`:
|
498
|
+
|
499
|
+
```ruby
|
500
|
+
class EmployeesController < ApplicationController
|
501
|
+
self.model_search_class = PersonSearch
|
502
|
+
|
503
|
+
def index
|
504
|
+
@search = model_search # will construct PersonSearch instead of `EmployeeSearch`
|
505
|
+
@employees = @search.results
|
506
|
+
end
|
507
|
+
end
|
508
|
+
```
|
509
|
+
|
510
|
+
In these examples, the search object is stored in a variable for use in
|
511
|
+
the view, as are the search results. The search results will be an
|
512
|
+
`ActiveRecord::Relation`, so any additional scoping, such as pagination,
|
513
|
+
can be applied to `@search.results`.
|
514
|
+
|
515
|
+
|
516
|
+
## Search Forms
|
517
|
+
|
518
|
+
Search forms can be rendered using Rails' form builder and a search
|
519
|
+
object:
|
520
|
+
|
521
|
+
```html+erb
|
522
|
+
<%= form_with model: @search, method: :get do |form| %>
|
523
|
+
<%= form.text_field :title_includes %>
|
524
|
+
<%= form.date_field :created_on %>
|
525
|
+
<%= form.check_box :only_published %>
|
526
|
+
<%= form.submit %>
|
527
|
+
<% end %>
|
528
|
+
```
|
529
|
+
|
530
|
+
Notice the `method: :get` argument to `form_with`; this is **required**.
|
531
|
+
|
532
|
+
Form fields will be populated with the criteria input (or default)
|
533
|
+
values of the same name from `@search`. Type-appropriate form fields
|
534
|
+
can be used, e.g. `date_field` for type `:date`, `check_box` for types
|
535
|
+
`:boolean` and `:void`, etc.
|
536
|
+
|
537
|
+
By default, the form will submit to the index action of the controller
|
538
|
+
that corresponds to the `model_class` of the search object. For
|
539
|
+
example, `PostSearch.model_class` is `Post`, so a form with an instance
|
540
|
+
of `PostSearch` will submit to `PostsController#index`. To change where
|
541
|
+
the form submits to, use the `:url` option of `form_with`.
|
542
|
+
|
543
|
+
|
544
|
+
## Search Links
|
545
|
+
|
546
|
+
Search links can be rendered using the `link_to_search` view helper
|
547
|
+
method:
|
548
|
+
|
549
|
+
```html+erb
|
550
|
+
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc) %>
|
551
|
+
```
|
552
|
+
|
553
|
+
The link will automatically point to current controller and current
|
554
|
+
action, with query parameters from the given search object. To link to
|
555
|
+
a different controller or action, pass an options Hash in place of the
|
556
|
+
search object:
|
557
|
+
|
558
|
+
```html+erb
|
559
|
+
<%= link_to_search "Sort by title", { controller: "posts", action: "index",
|
560
|
+
search: @search.toggle_order(:title, :asc) } %>
|
561
|
+
```
|
562
|
+
|
563
|
+
The `link_to_search` helper also accepts the same HTML options that
|
564
|
+
Rails' `link_to` helper does:
|
565
|
+
|
566
|
+
```html+erb
|
567
|
+
<%= link_to_search "Sort by title", @search.toggle_order(:title, :asc),
|
568
|
+
id: "title-sort-link", class: "sort-link" %>
|
569
|
+
```
|
570
|
+
|
571
|
+
...As well as a content block:
|
572
|
+
|
573
|
+
```html+erb
|
574
|
+
<%= link_to_search @search.toggle_order(:title, :asc) do %>
|
575
|
+
Sort by title <%= img_tag "sort_icon.png" %>
|
576
|
+
<% end %>
|
577
|
+
```
|
578
|
+
|
579
|
+
|
580
|
+
## `ModelSearch` Helper Methods
|
581
|
+
|
582
|
+
The `ModelSearch` class provides several methods that are helpful when
|
583
|
+
rendering the view.
|
584
|
+
|
585
|
+
One such method is `ModelSearch#toggle_order`, which was shown in
|
586
|
+
[previous examples](#order-direction). Remember that `toggle_order` is
|
587
|
+
a builder-style method that does not modify the search object. Instead,
|
588
|
+
it duplicates the search object, and sets the order on the new search
|
589
|
+
object. Such behavior is suitable to generating links to multiple
|
590
|
+
variants of a search, such as sort links in table column headers.
|
591
|
+
|
592
|
+
|
593
|
+
### `ModelSearch#with` and `ModelSearch#without`
|
594
|
+
|
595
|
+
Two additional builder-style methods are `ModelSearch#with` and
|
596
|
+
`ModelSearch#without`. Like `toggle_order`, both of these methods
|
597
|
+
return a new search object, leaving the original search object
|
598
|
+
unmodified. The `with` method accepts a Hash of criteria input values
|
599
|
+
to merge on top of the original set of criteria input values:
|
600
|
+
|
601
|
+
```ruby
|
602
|
+
class PostSearch < TalentScout::ModelSearch
|
603
|
+
criteria :title
|
604
|
+
criteria :published, :boolean
|
605
|
+
end
|
606
|
+
|
607
|
+
# The following are equivalent:
|
608
|
+
PostSearch.new(title: "Maaaaath!", published: true)
|
609
|
+
PostSearch.new(title: "Maaaaath!").with(published: true)
|
610
|
+
PostSearch.new(title: "Math?").with(title: "Maaaaath!", published: true)
|
611
|
+
```
|
612
|
+
|
613
|
+
The `without` method accepts a list of criteria input values to exclude
|
614
|
+
(default criteria values still apply):
|
615
|
+
|
616
|
+
```ruby
|
617
|
+
class PostSearch < TalentScout::ModelSearch
|
618
|
+
criteria :title
|
619
|
+
criteria :published, :boolean, default: true
|
620
|
+
end
|
621
|
+
|
622
|
+
# The following are equivalent:
|
623
|
+
PostSearch.new(title: "Maaaaath!")
|
624
|
+
PostSearch.new(title: "Maaaaath!", published: false).without(:published)
|
625
|
+
```
|
626
|
+
|
627
|
+
|
628
|
+
### `ModelSearch#each_choice`
|
629
|
+
|
630
|
+
Another helpful method is `ModelSearch#each_choice`, which will iterate
|
631
|
+
over the defined choices for a given criteria. This can be used to
|
632
|
+
generate links to variants of a search, or to generate options for a
|
633
|
+
select box:
|
634
|
+
|
635
|
+
```ruby
|
636
|
+
class PostSearch < TalentScout::ModelSearch
|
637
|
+
criteria :category, choices: %w[Science Tech Engineering Math]
|
638
|
+
end
|
639
|
+
```
|
640
|
+
|
641
|
+
```html+erb
|
642
|
+
<% @search.each_choice(:category) do |choice, chosen| %>
|
643
|
+
<%= link_to_search "Category: #{choice}", @search.with(category: choice),
|
644
|
+
class: ("active" if chosen) %>
|
645
|
+
<% end %>
|
646
|
+
```
|
647
|
+
|
648
|
+
```html+erb
|
649
|
+
<%= form_with model: @search, method: :get do |form| %>
|
650
|
+
<%= form.select :category, @search.each_choice(:category) %>
|
651
|
+
<%= form.submit %>
|
652
|
+
<% end %>
|
653
|
+
```
|
654
|
+
|
655
|
+
If the block passed to `each_choice` accepts two arguments, the 2nd
|
656
|
+
argument will indicate if the choice is currently chosen. If no block
|
657
|
+
is passed to `each_choice`, it will return an `Enumerator`.
|
658
|
+
|
659
|
+
The `each_choice` method can also be invoked with `:order`. Doing so
|
660
|
+
will iterate over each direction of each defined order, yielding the
|
661
|
+
appropriate labels including direction suffix:
|
662
|
+
|
663
|
+
```ruby
|
664
|
+
class PostSearch < TalentScout::ModelSearch
|
665
|
+
order "Title", [:title], asc_suffix: " (A-Z)", desc_suffix: " (Z-A)"
|
666
|
+
order "Time", [:created_at], asc_suffix: " (oldest first)", desc_suffix: " (newest first)"
|
667
|
+
end
|
668
|
+
```
|
669
|
+
|
670
|
+
```html+erb
|
671
|
+
<%= form_with model: @search, method: :get do |form| %>
|
672
|
+
<%= form.select :order, @search.each_choice(:order) %>
|
673
|
+
<%= form.submit %>
|
674
|
+
<% end %>
|
675
|
+
```
|
676
|
+
|
677
|
+
The select box in the above form will list four options: "Title (A-Z)",
|
678
|
+
"Title (Z-A)", "Time (oldest first)", "Time (newest first)".
|
679
|
+
|
680
|
+
|
681
|
+
### `ModelSearch#order_directions`
|
682
|
+
|
683
|
+
Finally, the `ModelSearch#order_directions` helper method returns a
|
684
|
+
`HashWithIndifferentAccess` reflecting the currently applied direction
|
685
|
+
of each defined order. It contains a key for each defined order, and
|
686
|
+
associates each key with either `:asc`, `:desc`, or `nil`.
|
687
|
+
|
688
|
+
```ruby
|
689
|
+
class PostSearch < TalentScout::ModelSearch
|
690
|
+
order "Title", [:title]
|
691
|
+
order "Time", [:created_at]
|
692
|
+
end
|
693
|
+
```
|
694
|
+
|
695
|
+
```html+erb
|
696
|
+
<thead>
|
697
|
+
<tr>
|
698
|
+
<% @search.order_directions.each do |order, direction| %>
|
699
|
+
<th>
|
700
|
+
<%= link_to_search order, @search.toggle_order(order) %>
|
701
|
+
<%= img_tag "#{direction || "unsorted"}_icon.png" %>
|
702
|
+
</th>
|
703
|
+
<% end %>
|
704
|
+
</tr>
|
705
|
+
</thead>
|
706
|
+
```
|
707
|
+
|
708
|
+
Remember that only one order can be applied at a time, so only one value
|
709
|
+
in the Hash, at most, will be non-`nil`.
|
710
|
+
>>>>>>> b0d201c... Update README
|
711
|
+
|
712
|
+
|
713
|
+
## Installation
|
714
|
+
|
715
|
+
Add this line to your application's Gemfile:
|
716
|
+
|
717
|
+
```ruby
|
718
|
+
gem "talent_scout"
|
719
|
+
```
|
720
|
+
|
721
|
+
Then execute:
|
722
|
+
|
723
|
+
```bash
|
724
|
+
$ bundle install
|
725
|
+
```
|
726
|
+
|
727
|
+
And finally, run the installation generator:
|
728
|
+
|
729
|
+
```bash
|
730
|
+
$ rails generate talent_scout:install
|
731
|
+
```
|
732
|
+
|
733
|
+
|
734
|
+
## Contributing
|
735
|
+
|
736
|
+
Run `rake test` to run the tests.
|
737
|
+
|
738
|
+
|
739
|
+
## License
|
740
|
+
|
741
|
+
[MIT License](https://opensource.org/licenses/MIT)
|