{"id":13879164,"url":"https://github.com/jonathanhefner/talent_scout","last_synced_at":"2025-11-11T20:25:15.776Z","repository":{"id":59157223,"uuid":"154551001","full_name":"jonathanhefner/talent_scout","owner":"jonathanhefner","description":"Model-backed searches in Rails","archived":false,"fork":false,"pushed_at":"2023-03-18T17:36:11.000Z","size":134,"stargazers_count":32,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-23T10:54:29.285Z","etag":null,"topics":["rails","ruby","ruby-on-rails","search","searching"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonathanhefner.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-10-24T18:43:52.000Z","updated_at":"2024-10-13T11:11:01.000Z","dependencies_parsed_at":"2024-09-25T03:26:42.798Z","dependency_job_id":"64e561b5-75cd-4fb3-9994-869c5f0a1571","html_url":"https://github.com/jonathanhefner/talent_scout","commit_stats":{"total_commits":145,"total_committers":1,"mean_commits":145.0,"dds":0.0,"last_synced_commit":"faa470cdd175b3296397083a941da0dd58b62067"},"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/jonathanhefner/talent_scout","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanhefner%2Ftalent_scout","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanhefner%2Ftalent_scout/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanhefner%2Ftalent_scout/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanhefner%2Ftalent_scout/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonathanhefner","download_url":"https://codeload.github.com/jonathanhefner/talent_scout/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanhefner%2Ftalent_scout/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273287679,"owners_count":25078575,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-02T02:00:09.530Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["rails","ruby","ruby-on-rails","search","searching"],"created_at":"2024-08-06T08:02:11.882Z","updated_at":"2025-11-11T20:25:15.716Z","avatar_url":"https://github.com/jonathanhefner.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# talent_scout\n\nModel-backed searches in Rails.  A whiz-bang example:\n\n```ruby\n## app/searches/post_search.rb\n\nclass PostSearch \u003c TalentScout::ModelSearch\n  criteria :title_includes do |string|\n    where(\"title LIKE ?\", \"%#{string}%\")\n  end\n\n  criteria :within, choices: {\n    \"Last 24 hours\" =\u003e 24.hours,\n    \"Past Week\" =\u003e 1.week,\n    \"Past Month\" =\u003e 1.month,\n    \"Past Year\" =\u003e 1.year,\n  } do |duration|\n    where(\"created_at \u003e= ?\", duration.ago)\n  end\n\n  criteria :only_published, :boolean, default: true do |only|\n    where(\"published\") if only\n  end\n\n  order :created_at, default: :desc\n  order :title\nend\n```\n\n```ruby\n## app/controllers/posts_controller.rb\n\nclass PostsController \u003c ApplicationController\n  def index\n    @search = model_search\n    @posts = @search.results\n  end\nend\n```\n\n```html+erb\n\u003c!-- app/views/posts/index.html.erb --\u003e\n\n\u003c%= form_with model: @search, local: true, method: :get do |form| %\u003e\n  \u003c%= form.label :title_includes %\u003e\n  \u003c%= form.text_field :title_includes %\u003e\n\n  \u003c%= form.label :within %\u003e\n  \u003c%= form.select :within, @search.each_choice(:within), include_blank: true %\u003e\n\n  \u003c%= form.label :only_published %\u003e\n  \u003c%= form.check_box :only_published %\u003e\n\n  \u003c%= form.submit %\u003e\n\u003c% end %\u003e\n\n\u003ctable\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n      \u003cth\u003e\n        \u003c%= link_to_search \"Title\", @search.toggle_order(:title) %\u003e\n        \u003c%= img_tag \"#{@search.order_directions[:title] || \"unsorted\"}_icon.png\" %\u003e\n      \u003c/th\u003e\n      \u003cth\u003e\n        \u003c%= link_to_search \"Time\", @search.toggle_order(:created_at) %\u003e\n        \u003c%= img_tag \"#{@search.order_directions[:created_at] || \"unsorted\"}_icon.png\" %\u003e\n      \u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003c% @posts.each do |post| %\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003c%= link_to post.title, post %\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003c%= post.created_at %\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n    \u003c% end %\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n```\n\nIn the above example:\n\n* The `PostSearch` class handles the responsibility of searching for\n  `Post` models.  It can apply any combination of its defined criteria,\n  automatically ignoring missing, blank, or invalid input values.  It\n  can also order the results by one of its defined orders, in either\n  ascending or descending direction.\n* `PostsController#index` uses the `model_search` helper to construct a\n  `PostSearch` instance, and assigns it to the `@search` variable for\n  later use in the view.  The search results are also assigned to a\n  variable for use in the view.\n* The view uses Rails' stock form builder to build a search form with\n  the `@search` variable.  The `link_to_search` helper is used to create\n  links in the table header which sort the results.  Note that the\n  `toggle_order` method used here returns a new search object, leaving\n  `@search` unmodified.\n\nFor a detailed explanation of the methods used in this example, see\nthe [API documentation](https://www.rubydoc.info/gems/talent_scout/).\n\n\n## Search Classes\n\nYou can use the `talent_scout:search` generator to generate a model\nsearch class definition.  For example,\n\n```bash\n$ rails generate talent_scout:search post\n```\n\nWill generate a file \"app/searches/post_search.rb\" containing:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\nend\n```\n\nSearch classes inherit from `TalentScout::ModelSearch`.  Their target\nmodel class is inferred from the search class name.  For example,\n`PostSearch` will search for `Post` models by default.  To override this\ninference, use `ModelSearch::model_class=`:\n\n```ruby\nclass EmployeeSearch \u003c TalentScout::ModelSearch\n  self.model_class = Person # search for Person models instead of `Employee`\nend\n```\n\n\n### Criteria\n\nSearch criteria are defined with the `ModelSearch::criteria` method.\nCriteria definitions can be specified in one of three ways: with an\nimplicit where clause, with an explicit query block, or with a model\nscope reference.  To illustrate, the following three `:title` criteria\nare all equivalent:\n\n```ruby\nclass Post \u003c ActiveRecord::Base\n  scope :title_equals, -\u003e(string){ where(title: string) }\nend\n\nclass PostSearch \u003c TalentScout::ModelSearch\n  criteria :title\n\n  criteria :title do |string|\n    where(title: string)\n  end\n\n  criteria :title, \u0026:title_equals\nend\n```\n\nNote that explicit query blocks are evaluated in the context of the\nmodel's `ActiveRecord::Relation`, just like Active Record `scope` blocks\nare.\n\n\n#### Criteria Type\n\nA criteria definition can specify a data type, which causes its input\nvalue to be typecast before being passed to the query block or scope.\nAs an example:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  criteria :created_on, :date do |date|\n    where(created_at: date.beginning_of_day..date.end_of_day)\n  end\nend\n\nPostSearch.new(created_on: \"Dec 31, 1999\")\n```\n\nHere, the string `\"Dec 31, 1999\"` passed to the `PostSearch` constructor\nis typecast to a `Date` before being passed to the query block.\n\nThe default criteria type is `:string`, which means, by default, all\ninput values will be cast to strings.  This default (as opposed to a\ndefault of no typecasting) ensures consistent behavior no matter how the\nsearch object is constructed, whether from strongly-typed values or from\nsearch form request params.\n\nAvailable criteria types are the same as for Active Model attributes:\n`:big_integer`, `:boolean`, `:date`, `:datetime`, `:decimal`, `:float`,\n`:integer`, `:string`, `:time`, plus any custom types you define.\n\nAn additional convenience type is also available: `:void`.  The `:void`\ntype typecasts like `:boolean`, but prevents the criteria from being\napplied when the typecasted value is falsey.  For example:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  criteria :only_edited, :void do\n    where(\"modified_at \u003e created_at\")\n  end\nend\n\n# The following will apply `only_edited`:\nPostSearch.new(only_edited: true)\nPostSearch.new(only_edited: \"1\")\n\n# The following will skip `only_edited`:\nPostSearch.new(only_edited: false)\nPostSearch.new(only_edited: \"0\")\nPostSearch.new(only_edited: \"\")\n```\n\n\n#### Criteria Choices\n\nInstead of specifying a type, a criteria definition may specify choices.\nChoices define a set of values which can be passed to the query block.\n\nChoices can either be specified as an Array of human-friendly values:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  criteria :category, choices: %w[Science Tech Engineering Math] do |name|\n    where(category: name.downcase)\n  end\nend\n```\n\n...Or as a Hash with human-friendly keys:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  criteria :within, choices: {\n    \"Last 24 hours\" =\u003e 24.hours,\n    \"Past Week\" =\u003e 1.week,\n    \"Past Month\" =\u003e 1.month,\n    \"Past Year\" =\u003e 1.year,\n  } do |duration|\n    where(\"created_at \u003e= ?\", duration.ago)\n  end\nend\n```\n\nThe value passed to the query block will be one of the values in the\nArray or one of the values in the Hash.  The search object may be\nconstructed with any of the Array values or Hash keys or Hash values:\n\n```ruby\nPostSearch.new(category: \"Math\")\nPostSearch.new(within: \"Last 24 hours\")\nPostSearch.new(within: 24.hours)\n```\n\nBut if an invalid choice is specified, the corresponding criteria will\nnot be applied:\n\n```ruby\n# The following will skip the criteria, but will not raise an error:\nPostSearch.new(category: \"Marketing\")\nPostSearch.new(within: 12.hours)\n```\n\n\n#### Criteria Default Value\n\nA criteria definition can specify a default value, which will be passed\nto the query block when the input value is missing.  Default values will\nalso appear in search forms.\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  criteria :within_days, :integer, default: 7 do |num|\n    where(\"created_at \u003e= ?\", num.days.ago)\n  end\nend\n\n# The following are equivalent:\nPostSearch.new()\nPostSearch.new(within_days: 7)\n```\n\n\n#### Criteria Resolution\n\nA criteria will not be applied if any of the following are true:\n\n* The criteria input value is missing, and no default value has been\n  specified.\n\n* The search object was constructed with an `ActionController::Parameters`\n  (instead of a Hash), and the criteria input value is `blank?`, and no\n  default value has been specified.  (This behavior prevents empty\n  search form fields from affecting search results.)\n\n* The typecast of the criteria input value fails.  For example:\n\n  ```ruby\n  class PostSearch \u003c TalentScout::ModelSearch\n    criteria :created_on, :date do |date|\n      where(created_at: date.beginning_of_day..date.end_of_day)\n    end\n  end\n\n  # The following will skip `created_on`, but will not raise an error:\n  PostSearch.new(created_on: \"BAD\")\n  ```\n\n* The criteria definition specifies type `:void`, and the typecasted\n  input value is falsey.\n\n* The criteria definition specifies choices, and the input value is not\n  a valid choice.\n\n* The criteria query block returns `nil`.  For example:\n\n  ```ruby\n  class PostSearch \u003c TalentScout::ModelSearch\n    criteria :minimum_upvotes, :integer do |minimum|\n      where(\"upvotes \u003e= ?\", minimum) unless minimum \u003c= 0\n    end\n  end\n\n  # The following will skip the `minimum_upvotes` where clause:\n  PostSearch.new(minimum_upvotes: 0)\n  ```\n\n\n### Orders\n\nSearch result orders are defined with the `ModelSearch::order` method:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  order :created_at\n  order :title\n  order :category\nend\n\nPostSearch.new(order: :created_at)\nPostSearch.new(order: :title)\nPostSearch.new(order: :category)\n```\n\nOnly one order can be applied at a time, but an order can comprise\nmultiple columns:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  order :category, [:category, :title]\nend\n\n# The following will order by \"category, title\":\nPostSearch.new(order: :category)\n```\n\nThis restricted design was chosen because it allows curated multi-column\nsorts with simpler single-column sorting UIs, and because it prevents\nad-hoc multi-column sorts that may not be backed by a database index.\n\n\n#### Order Direction\n\nAn order can be applied in ascending or descending direction.  The\n`ModelSearch#toggle_order` method will apply an order in ascending\ndirection, or will change an applied order direction from ascending to\ndescending:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  order :created_at\n  order :title\nend\n\n# The following will order by \"title\":\nPostSearch.new().toggle_order(:title)\nPostSearch.new(order: :created_at).toggle_order(:title)\n\n# The following will order by \"title DESC\":\nPostSearch.new(order: :title).toggle_order(:title)\n```\n\nNote that the `toggle_order` method does not modify the existing search\nobject.  Instead, it builds a new search object with the new order and\nthe criteria values of the previous search object.\n\nWhen a multi-column order is applied in descending direction, all\ncolumns are affected:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  order :category, [:category, :title]\nend\n\n# The following will order by \"category DESC, title DESC\":\nPostSearch.new(order: :category).toggle_order(:category)\n```\n\nTo circumvent this behavior and instead fix a column in a static\ndirection, append `\" ASC\"` or `\" DESC\"` to the column name:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  order :category, [:category, \"created_at ASC\"]\nend\n\n# The following will order by \"category, created_at ASC\":\nPostSearch.new(order: :category)\n\n# The following will order by \"category DESC, created_at ASC\":\nPostSearch.new(order: :category).toggle_order(:category)\n```\n\n\n#### Order Direction Suffixes\n\nAn order can be applied in ascending or descending direction directly,\nwithout using `toggle_order`, by appending an appropriate suffix:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  order :title\nend\n\n# The following will order by \"title\":\nPostSearch.new(order: :title)\nPostSearch.new(order: \"title.asc\")\n\n# The following will order by \"title DESC\":\nPostSearch.new(order: \"title.desc\")\n```\n\nThe default suffixes, as seen in the above example, are `\".asc\"` and\n`\".desc\"`.  These were chosen for their I18n-friendliness.  They can be\noverridden as part of the order definition:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  order \"Title\", [:title], asc_suffix: \" (A-Z)\", desc_suffix: \" (Z-A)\"\nend\n\n# The following will order by \"title\":\nPostSearch.new(order: \"Title\")\nPostSearch.new(order: \"Title (A-Z)\")\n\n# The following will order by \"title DESC\":\nPostSearch.new(order: \"Title (Z-A)\")\n```\n\n\n#### Default Order\n\nAn order can be designated as the default order, which will cause that\norder to be applied when no order is otherwise specified:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  order :created_at, default: :desc\n  order :title\nend\n\n# The following will order by \"created_at DESC\":\nPostSearch.new()\n\n# The following will order by \"created_at\":\nPostSearch.new(order: :created_at)\n\n# The following will order by \"title\":\nPostSearch.new(order: :title)\n```\n\nNote that the default order direction can be either ascending or\ndescending by specifing `default: :asc` or `default: :desc`,\nrespectively.  Also, just as only one order can be applied at a time,\nonly one order can be designated default.\n\n\n### Default Scope\n\nA default search scope can be defined with `ModelSearch::default_scope`:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  default_scope { where(published: true) }\nend\n```\n\nThe default scope will be applied regardless of the criteria or order\ninput values.\n\n\n## Controllers\n\nControllers can use the `model_search` helper method to construct a\nsearch object with the current request's query params:\n\n```ruby\nclass PostsController \u003c ApplicationController\n  def index\n    @search = model_search\n    @posts = @search.results\n  end\nend\n```\n\nIn the above example, `model_search` constructs a `PostSearch` object.\nThe model search class is automatically derived from the controller\nclass name.  To override the model search class, use `::model_search_class=`:\n\n```ruby\nclass EmployeesController \u003c ApplicationController\n  self.model_search_class = PersonSearch\n\n  def index\n    @search = model_search # will construct PersonSearch instead of `EmployeeSearch`\n    @employees = @search.results\n  end\nend\n```\n\nIn these examples, the search object is stored in `@search` for use in\nthe view.  Note that `@search.results` returns an `ActiveRecord::Relation`,\nso any additional scoping, such as pagination, can be applied.\n\n\n## Search Forms\n\nSearch forms can be rendered using Rails' form builder and a search\nobject:\n\n```html+erb\n\u003c%= form_with model: @search, local: true, method: :get do |form| %\u003e\n  \u003c%= form.label :title_includes %\u003e\n  \u003c%= form.text_field :title_includes %\u003e\n\n  \u003c%= form.label :created_on %\u003e\n  \u003c%= form.date_field :created_on %\u003e\n\n  \u003c%= form.label :only_published %\u003e\n  \u003c%= form.check_box :only_published %\u003e\n\n  \u003c%= form.submit %\u003e\n\u003c% end %\u003e\n```\n\n**Notice the `method: :get` argument to `form_with`.  This is required.**\n\nForm fields will be populated with the criteria input (or default)\nvalues of the same name from `@search`.  Type-appropriate form fields\ncan be used, e.g. `date_field` for type `:date`, `check_box` for types\n`:boolean` and `:void`, etc.\n\nBy default, the form will submit to the index action of the controller\nthat corresponds to the `model_class` of the search object.  For\nexample, `PostSearch.model_class` is `Post`, so a form with an instance\nof `PostSearch` will submit to `PostsController#index`.  To change where\nthe form submits to, use the `:url` option of `form_with`.\n\n\n## Search Links\n\nSearch links can be rendered using the `link_to_search` view helper\nmethod:\n\n```html+erb\n\u003c%= link_to_search \"Sort by title\", @search.toggle_order(:title, :asc) %\u003e\n```\n\nThe link will automatically point to the current controller and current\naction, with query parameters from the given search object.  To link to\na different controller or action, pass an options Hash in place of the\nsearch object:\n\n```html+erb\n\u003c%= link_to_search \"Sort by title\", { controller: \"posts\", action: \"index\",\n      search: @search.toggle_order(:title, :asc) } %\u003e\n```\n\nThe `link_to_search` helper also accepts the same HTML options that\nRails' `link_to` helper does:\n\n```html+erb\n\u003c%= link_to_search \"Sort by title\", @search.toggle_order(:title, :asc),\n      id: \"title-sort-link\", class: \"sort-link\" %\u003e\n```\n\n...As well as a content block:\n\n```html+erb\n\u003c%= link_to_search @search.toggle_order(:title, :asc) do %\u003e\n  Sort by title \u003c%= img_tag \"sort_icon.png\" %\u003e\n\u003c% end %\u003e\n```\n\n\n## `ModelSearch` Helper Methods\n\nThe `ModelSearch` class provides several methods that are helpful when\nrendering the view.\n\nOne such method is `ModelSearch#toggle_order`, which was shown in\n[previous examples](#order-direction).  Remember that `toggle_order` is\na builder-style method that does not modify the search object.  Instead,\nit duplicates the search object, and sets the order on the new object.\nSuch behavior is suitable to generating links to multiple variants of a\nsearch, such as sort links in table column headers.\n\n\n### `ModelSearch#with` and `ModelSearch#without`\n\nTwo additional builder-style methods are `ModelSearch#with` and\n`ModelSearch#without`.  Like `toggle_order`, both of these methods\nreturn a new search object, leaving the original search object\nunmodified.  The `with` method accepts a Hash of criteria input values\nto merge on top of the original set of criteria input values:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  criteria :title\n  criteria :published, :boolean\nend\n\n# The following are equivalent:\nPostSearch.new(title: \"Maaaaath!\", published: true)\nPostSearch.new(title: \"Maaaaath!\").with(published: true)\nPostSearch.new(title: \"Math?\").with(title: \"Maaaaath!\", published: true)\n```\n\nThe `without` method accepts a list of criteria input values to exclude\n(default criteria values still apply):\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  criteria :title\n  criteria :published, :boolean, default: true\nend\n\n# The following are equivalent:\nPostSearch.new(title: \"Maaaaath!\")\nPostSearch.new(title: \"Maaaaath!\", published: false).without(:published)\n```\n\n\n### `ModelSearch#each_choice`\n\nAnother helpful method is `ModelSearch#each_choice`, which will iterate\nover the defined choices for a given criteria.  This can be used to\ngenerate links to variants of a search:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  criteria :category, choices: %w[Science Tech Engineering Math]\nend\n```\n\n```html+erb\n\u003c% @search.each_choice(:category) do |choice, chosen| %\u003e\n  \u003c%= link_to_search \"Category: #{choice}\", @search.with(category: choice),\n        class: (\"active\" if chosen) %\u003e\n\u003c% end %\u003e\n```\n\nNotice that if the block passed to `each_choice` accepts two arguments,\nthe 2nd argument will indicate if the choice is currently chosen.\n\nIf no block is passed to `each_choice`, it will return an `Enumerator`.\nThis can be used to generate options for a select box:\n\n```html+erb\n\u003c%= form_with model: @search, local: true, method: :get do |form| %\u003e\n  \u003c%= form.select :category, @search.each_choice(:category) %\u003e\n  \u003c%= form.submit %\u003e\n\u003c% end %\u003e\n```\n\nThe `each_choice` method can also be invoked with `:order`.  Doing so\nwill iterate over each direction of each defined order, yielding the\nappropriate labels including direction suffix:\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  order \"Title\", [:title], asc_suffix: \" (A-Z)\", desc_suffix: \" (Z-A)\"\n  order \"Time\", [:created_at], asc_suffix: \" (oldest first)\", desc_suffix: \" (newest first)\"\nend\n```\n\n```html+erb\n\u003c%= form_with model: @search, local: true, method: :get do |form| %\u003e\n  \u003c%= form.select :order, @search.each_choice(:order) %\u003e\n  \u003c%= form.submit %\u003e\n\u003c% end %\u003e\n```\n\nThe select box in the above form will list four options: \"Title (A-Z)\",\n\"Title (Z-A)\", \"Time (oldest first)\", \"Time (newest first)\".\n\n\n### `ModelSearch#order_directions`\n\nFinally, the `ModelSearch#order_directions` helper method returns a\n`HashWithIndifferentAccess` reflecting the currently applied direction\nof each defined order.  It contains a key for each defined order, and\nassociates each key with either `:asc`, `:desc`, or `nil`.\n\n```ruby\nclass PostSearch \u003c TalentScout::ModelSearch\n  order \"Title\", [:title]\n  order \"Time\", [:created_at]\nend\n```\n\n```html+erb\n\u003cthead\u003e\n  \u003ctr\u003e\n    \u003c% @search.order_directions.each do |order, direction| %\u003e\n      \u003cth\u003e\n        \u003c%= link_to_search order, @search.toggle_order(order) %\u003e\n        \u003c%= img_tag \"#{direction || \"unsorted\"}_icon.png\" %\u003e\n      \u003c/th\u003e\n    \u003c% end %\u003e\n  \u003c/tr\u003e\n\u003c/thead\u003e\n```\n\nRemember that only one order can be applied at a time, so only one value\nin the Hash, at most, will be non-`nil`.\n\n\n## Installation\n\nAdd the gem to your Gemfile:\n\n```bash\n$ bundle add talent_scout\n```\n\nAnd run the installation generator:\n\n```bash\n$ rails generate talent_scout:install\n```\n\n\n## Contributing\n\nRun `bin/test` to run the tests.\n\n\n## License\n\n[MIT License](MIT-LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonathanhefner%2Ftalent_scout","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonathanhefner%2Ftalent_scout","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonathanhefner%2Ftalent_scout/lists"}