talent_scout 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|