talent_scout 1.0.0

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