{"id":13721531,"url":"https://github.com/meilisearch/meilisearch-rails","last_synced_at":"2025-04-13T01:56:30.317Z","repository":{"id":37030997,"uuid":"338307278","full_name":"meilisearch/meilisearch-rails","owner":"meilisearch","description":"Meilisearch integration for Ruby on Rails","archived":false,"fork":false,"pushed_at":"2025-04-02T11:51:20.000Z","size":1366,"stargazers_count":325,"open_issues_count":42,"forks_count":52,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-04-13T01:56:21.706Z","etag":null,"topics":["meilisearch","rails","ruby","ruby-on-rails"],"latest_commit_sha":null,"homepage":"https://www.meilisearch.com","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/meilisearch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"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":"2021-02-12T12:04:00.000Z","updated_at":"2025-04-08T09:14:55.000Z","dependencies_parsed_at":"2023-12-15T20:01:25.526Z","dependency_job_id":"10b49b50-27e5-4c61-a16b-3b29cfdb219f","html_url":"https://github.com/meilisearch/meilisearch-rails","commit_stats":{"total_commits":614,"total_committers":33,"mean_commits":"18.606060606060606","dds":0.7068403908794788,"last_synced_commit":"b67b98d1ce66958b01efe915b4c4d0fb0c16aa3b"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meilisearch%2Fmeilisearch-rails","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meilisearch%2Fmeilisearch-rails/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meilisearch%2Fmeilisearch-rails/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meilisearch%2Fmeilisearch-rails/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/meilisearch","download_url":"https://codeload.github.com/meilisearch/meilisearch-rails/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248654050,"owners_count":21140235,"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","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":["meilisearch","rails","ruby","ruby-on-rails"],"created_at":"2024-08-03T01:01:18.292Z","updated_at":"2025-04-13T01:56:30.285Z","avatar_url":"https://github.com/meilisearch.png","language":"Ruby","funding_links":[],"categories":["Ruby","Search","Integrations"],"sub_categories":["Official Integrations"],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/meilisearch/integration-guides/main/assets/logos/meilisearch_rails.svg\" alt=\"Meilisearch-Rails\" width=\"200\" height=\"200\" /\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eMeilisearch Rails\u003c/h1\u003e\n\n\u003ch4 align=\"center\"\u003e\n  \u003ca href=\"https://github.com/meilisearch/meilisearch\"\u003eMeilisearch\u003c/a\u003e |\n  \u003ca href=\"https://www.meilisearch.com/pricing?utm_campaign=oss\u0026utm_source=integration\u0026utm_medium=meilisearch-rails\"\u003eMeilisearch Cloud\u003c/a\u003e |\n  \u003ca href=\"https://docs.meilisearch.com\"\u003eDocumentation\u003c/a\u003e |\n  \u003ca href=\"https://discord.meilisearch.com\"\u003eDiscord\u003c/a\u003e |\n  \u003ca href=\"https://roadmap.meilisearch.com/tabs/1-under-consideration\"\u003eRoadmap\u003c/a\u003e |\n  \u003ca href=\"https://www.meilisearch.com\"\u003eWebsite\u003c/a\u003e |\n  \u003ca href=\"https://www.meilisearch.com/docs/faq\"\u003eFAQ\u003c/a\u003e\n\u003c/h4\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/meilisearch/meilisearch-rails/actions\"\u003e\u003cimg src=\"https://github.com/meilisearch/meilisearch-rails/workflows/Tests/badge.svg\" alt=\"Test\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://app.codecov.io/gh/meilisearch/meilisearch-rails/tree/main\" \u003e\n    \u003cimg src=\"https://codecov.io/gh/meilisearch/meilisearch-rails/branch/main/graph/badge.svg?token=9J7LRP11IR\"/\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/meilisearch/meilisearch-rails/blob/main/LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-informational\" alt=\"License\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://ms-bors.herokuapp.com/repositories/68\"\u003e\u003cimg src=\"https://bors.tech/images/badge_small.svg\" alt=\"Bors enabled\"\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e⚡ The Meilisearch integration for Ruby on Rails 💎\u003c/p\u003e\n\n**Meilisearch Rails** is the Meilisearch integration for Ruby on Rails developers.\n\n**Meilisearch** is an open-source search engine. [Learn more about Meilisearch.](https://github.com/meilisearch/meilisearch)\n\n## Table of Contents \u003c!-- omit in TOC --\u003e\n\n- [📖 Documentation](#-documentation)\n- [🤖 Compatibility with Meilisearch](#-compatibility-with-meilisearch)\n- [🚀 Getting started](#-getting-started)\n- [Compatibility](#-compatibility)\n- [⚙️ Settings](#️-settings)\n- [🔍 Custom search](#-custom-search)\n- [🔍🔍 Multi search](#-multi-search)\n- [🔍🔍 Federated search](#-federated-search)\n- [🪛 Options](#-options)\n  - [Meilisearch configuration \u0026 environment](#meilisearch-configuration--environment)\n  - [Pagination with `kaminari` or `will_paginate`](#backend-pagination-with-kaminari-or-will_paginate-)\n  - [Pagination with `pagy`](#backend-pagination-with-pagy-)\n  - [Index configuration](#index-configuration)\n    - [Custom attribute definition](#custom-attribute-definition)\n    - [Custom primary key](#custom-primary-key)\n    - [Conditional indexing](#conditional-indexing)\n    - [Share a single index](#share-a-single-index)\n    - [Queues \u0026 background jobs](#queues--background-jobs)\n    - [Relations](#relations)\n    - [Sanitize attributes](#sanitize-attributes)\n    - [UTF-8 encoding](#utf-8-encoding)\n    - [Eager loading](#eager-loading)\n  - [Manual operations](#manual-operations)\n    - [Indexing \u0026 deletion](#indexing--deletion)\n    - [Access the underlying index object](#access-the-underlying-index-object)\n  - [Development \u0026 testing](#development--testing)\n- [⚙️ Development workflow \u0026 contributing](#️-development-workflow--contributing)\n- [👏  Credits](#--credits)\n\n## 📖 Documentation\n\nThe whole usage of this gem is detailed in this README.\n\nTo learn more about Meilisearch, check out our [Documentation](https://www.meilisearch.com/docs/learn/tutorials/getting_started.html) or our [API References](https://www.meilisearch.com/docs/reference/api/).\n\n## 🤖 Compatibility with Meilisearch\n\nThis package guarantees compatibility with [version v1.x of Meilisearch](https://github.com/meilisearch/meilisearch/releases/latest), but some features may not be present. Please check the [issues](https://github.com/meilisearch/meilisearch-rails/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22+label%3Aenhancement) for more info.\n\n## 🔧 Installation \u003c!-- omit in toc --\u003e\n\nThis package requires Ruby version 3.0 or later and Rails 6.1 or later. It may work in older versions but it is not officially supported.\n\nWith `gem` in command line:\n```bash\ngem install meilisearch-rails\n```\n\nIn your `Gemfile` with [bundler](https://bundler.io/):\n```ruby\nsource 'https://rubygems.org'\n\ngem 'meilisearch-rails'\n```\n\n### Run Meilisearch \u003c!-- omit in toc --\u003e\n\n⚡️ **Launch, scale, and streamline in minutes with Meilisearch Cloud**—no maintenance, no commitment, cancel anytime. [Try it free now](https://cloud.meilisearch.com/login?utm_campaign=oss\u0026utm_source=github\u0026utm_medium=meilisearch-rails).\n\n🪨  Prefer to self-host? [Download and deploy](https://www.meilisearch.com/docs/learn/self_hosted/getting_started_with_self_hosted_meilisearch?utm_campaign=oss\u0026utm_source=github\u0026utm_medium=meilisearch-rails) our fast, open-source search engine on your own infrastructure.\n\n## 🚀 Getting started\n\n#### Configuration \u003c!-- omit in toc --\u003e\n\nCreate a new file `config/initializers/meilisearch.rb` to setup your `MEILISEARCH_HOST` and `MEILISEARCH_API_KEY`\n\n```ruby\nMeilisearch::Rails.configuration = {\n  meilisearch_url: ENV.fetch('MEILISEARCH_HOST', 'http://localhost:7700'),\n  meilisearch_api_key: ENV.fetch('MEILISEARCH_API_KEY', 'YourMeilisearchAPIKey')\n}\n```\n\nOr you can run a rake task to create the initializer file for you:\n\n```bash\nbin/rails meilisearch:install\n```\n\nThe gem is compatible with [ActiveRecord](https://github.com/rails/rails/tree/master/activerecord), [Mongoid](https://github.com/mongoid/mongoid) and [Sequel](https://github.com/jeremyevans/sequel).\n\n⚠️ Note that even if you want to use all the default options, you must declare an empty `meilisearch` block in your model.  \n\n#### Add documents \u003c!-- omit in toc --\u003e\n\nThe following code will create a `Book` index and add search capabilities to your `Book` model.\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch do\n    attribute :title, :author # only the attributes 'title', and 'author' will be sent to Meilisearch\n    # all attributes will be sent to Meilisearch if block is left empty\n  end\nend\n```\n\n#### Automatic indexing\n\nAs soon as you configure your model as mentioned above, `meilisearch-rails` will keep your database table data in sync with your Meilisearch instance using the `ActiveRecord` callbacks automatically.\n\n#### Basic Backend Search \u003c!-- omit in toc --\u003e\n\nWe **strongly recommend the use of front-end search** through our [JavaScript API Client](https://github.com/meilisearch/meilisearch-js/) or [Instant Meilisearch plugin](https://github.com/meilisearch/instant-meilisearch)\n\nSearch returns ORM-compliant objects reloaded from your database.\n\n```ruby\n# Meilisearch is typo-tolerant:\nhits = Book.search('harry pottre')\nhits.each do |hit|\n  puts hit.title\n  puts hit.author\nend\n```\n\n#### Extra Configuration \u003c!-- omit in toc --\u003e\n\nRequests made to Meilisearch may timeout and retry. To adapt the behavior to\nyour needs, you can change the parameters during configuration:\n\n```ruby\nMeilisearch::Rails.configuration = {\n  meilisearch_url: 'YourMeilisearchUrl',\n  meilisearch_api_key: 'YourMeilisearchAPIKey',\n  timeout: 2,\n  max_retries: 1,\n}\n```\n\n## Compatibility\n\nIf your model already has methods that meilisearch-rails defines such as `search` and `index`, they will not be redefined. You can target the meilisearch-rails-defined methods by prefixing with `ms_`, e.g. `Book.ms_search('harry potter')`.\n\n## ⚙️ Settings\n\nYou can configure the index settings by adding them inside the `meilisearch` block as shown below:\n\n```ruby\nclass Book \u003c ApplicationRecord\n  include Meilisearch::Rails\n\n  meilisearch do\n    searchable_attributes [:title, :author, :publisher, :description]\n    filterable_attributes [:genre]\n    sortable_attributes [:title]\n    ranking_rules [\n      'proximity',\n      'typo',\n      'words',\n      'attribute',\n      'sort',\n      'exactness',\n      'publication_year:desc'\n    ]\n    synonyms nyc: ['new york']\n\n    # The following parameters are applied when calling the search() method:\n    attributes_to_highlight ['*']\n    attributes_to_crop [:description]\n    crop_length 10\n    faceting max_values_per_facet: 2000\n    pagination max_total_hits: 1000\n    proximity_precision 'byWord'\n  end\nend\n```\n\nCheck the dedicated section of the documentation, for more information on the [settings](https://www.meilisearch.com/docs/reference/api/settings#settings_parameters).\n\n## 🔍 Custom search\n\nAll the supported options are described in the [search parameters](https://www.meilisearch.com/docs/reference/api/search#search-parameters) section of the documentation.\n\n```ruby\nBook.search('Harry', attributes_to_highlight: ['*'])\n```\n\nThen it's possible to retrieve the highlighted or cropped value by using the `formatted` method available in the object.\n\n```ruby\nharry_book.formatted # =\u003e {\"id\"=\u003e\"1\", \"name\"=\u003e\"\u003cem\u003eHarry\u003c/em\u003e Potter\", \"description\"=\u003e…\n```\n\n👉 Don't forget that `attributes_to_highlight`, `attributes_to_crop`, and\n`crop_length` can be set up in the `meilisearch` block of your model.\n\n### 🔍 Sorted search\n\nAs an example of how to use the sort option, here is how you could achieve\nreturning all books sorted by title in ascending order:\n\n```ruby\nBook.search('*', sort: ['title:asc'])\n```\n\n👉 Don't forget to set up the `sortable_attributes` option in the `meilisearch` block of your model.\n\n## 🔍🔍 Multi search\n\nMeilisearch supports searching multiple models at the same time (see [🔍 Custom search](#-custom-search) for search options):\n\n```ruby\nmulti_search_results = Meilisearch::Rails.multi_search(\n  Book =\u003e { q: 'Harry' },\n  Manga =\u003e { q: 'Attack' }\n)\n```\n\nUse `#each_result` to loop through pairs of your provided keys and the results:\n```erb\n\u003c% multi_search_results.each_result do |klass, results| %\u003e\n  \u003cp\u003e\u003c%= klass.name.pluralize %\u003e\u003c/p\u003e\n\n  \u003cul\u003e\n    \u003c% results.each do |record| %\u003e\n      \u003cli\u003e\u003c%= record.title %\u003e\u003c/li\u003e\n    \u003c% end %\u003e\n  \u003c/ul\u003e\n\u003c% end %\u003e\n\n\n\u003cp\u003eBooks\u003c/p\u003e\n\u003cul\u003e\n  \u003cli\u003eHarry Potter and the Philosopher's Stone\u003c/li\u003e\n  \u003cli\u003eHarry Potter and the Chamber of Secrets\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eMangas\u003c/p\u003e\n\u003cul\u003e\n  \u003cli\u003eAttack on Titan\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nRecords are loaded when the keys are models, or when `:scope` option is passed:\n\n```ruby\nmulti_search_results = Meilisearch::Rails.multi_search(\n  # scope may be a relation\n  'books' =\u003e { q: 'Harry', scope: Book.all },\n  # or a model\n  'mangas' =\u003e { q: 'Attack', scope: Manga }\n)\n```\n\nOtherwise, hashes are returned.\n\nThe index to search is inferred from the model if the key is a model, if the key is a string the key is assumed to be the index unless the `:index_uid` option is passed:\n\n```ruby\nmulti_search_results = Meilisearch::Rails.multi_search(\n  'western' =\u003e { q: 'Harry', scope: Book, index_uid: 'books_production' },\n  'japanese' =\u003e { q: 'Attack', scope: Manga, index_uid: 'mangas_production' }\n)\n```\n\n### Multi search the same index \u003c!-- omit in toc --\u003e\n\nYou can search the same index multiple times by specifying `:index_uid`:\n\n```ruby\nquery = 'hero'\n\nmulti_search_results = Meilisearch::Rails.multi_search(\n  'Isekai Manga' =\u003e { q: query, scope: Manga, filters: 'genre:isekai', index_uid: 'mangas_production' }\n  'Shounen Manga' =\u003e { q: query, scope: Manga, filters: 'genre:shounen', index_uid: 'mangas_production' }\n  'Steampunk Manga' =\u003e { q: query, scope: Manga, filters: 'genre:steampunk', index_uid: 'mangas_production' }\n)\n```\n\n### Deprecated #each \u003c!-- omit in toc --\u003e\n\n**DEPRECATED:** You used to be able to iterate through a flattened collection with `.each`:\n\n```erb\n\u003c% multi_search_results.each do |record| %\u003e\n  \u003cp\u003e\u003c%= record.title %\u003e\u003c/p\u003e\n  \u003cp\u003e\u003c%= record.author %\u003e\u003c/p\u003e\n\u003c% end %\u003e\n\n\u003cp\u003eHarry Potter and the Philosopher's Stone\u003c/p\u003e\n\u003cp\u003eJ. K. Rowling\u003c/p\u003e\n\u003cp\u003eHarry Potter and the Chamber of Secrets\u003c/p\u003e\n\u003cp\u003eJ. K. Rowling\u003c/p\u003e\n\u003cp\u003eAttack on Titan\u003c/p\u003e\n\u003cp\u003eIseyama\u003c/p\u003e\n```\n\nBut this has been deprecated in favor of **federated search**.\n\nSee the [official multi search documentation](https://www.meilisearch.com/docs/reference/api/multi_search).\n\n## 🔍🔍 Federated search\n\nFederated search is similar to multi search, except that results are not grouped but sorted by ranking rules.\n\n```ruby\nresults = Meilisearch::Rails.federated_search(\n  queries: [\n    { q: 'Harry', scope: Book.all },\n    { q: 'Attack on Titan', scope: Manga.all }\n  ]\n)\n```\n\nAn enumerable `FederatedSearchResult` is returned, which can be iterated through with `#each`:\n\n```erb\n\u003cul\u003e\n  \u003c% results.each do |record| %\u003e\n    \u003cli\u003e\u003c%= record.title %\u003e\u003c/li\u003e\n  \u003c% end %\u003e\n\u003c/ul\u003e\n\n\n\u003cul\u003e\n  \u003c!-- Attack on Titan appears first even though it was specified second, \n       it's ranked higher because it's a closer match --\u003e\n  \u003cli\u003eAttack on Titan\u003c/li\u003e\n  \u003cli\u003eHarry Potter and the Philosopher's Stone\u003c/li\u003e\n  \u003cli\u003eHarry Potter and the Chamber of Secrets\u003c/li\u003e\n\u003c/ul\u003e\n```\n\nThe `queries` parameter may be a multi-search style hash with keys that are either classes, index names, or neither:\n\n```ruby\nresults = Meilisearch::Rails.federated_search(\n  queries: {\n    Book =\u003e { q: 'Harry' },\n    Manga =\u003e { q: 'Attack on Titan' }\n  }\n)\n```\n\n```ruby\nresults = Meilisearch::Rails.federated_search(\n  queries: {\n    'books_production' =\u003e { q: 'Harry', scope: Book.all },\n    'mangas_production' =\u003e { q: 'Attack on Titan', scope: Manga.all }\n  }\n)\n```\n\n```ruby\nresults = Meilisearch::Rails.federated_search(\n  queries: {\n    'potter' =\u003e { q: 'Harry', scope: Book.all, index_uid: 'books_production' },\n    'titan' =\u003e { q: 'Attack on Titan', scope: Manga.all, index_uid: 'mangas_production' }\n  }\n)\n```\n\n### Loading records \u003c!-- omit in toc --\u003e\n\nRecords are loaded when the `:scope` option is passed (may be a model or a relation), \nor when a hash query is used with models as keys:\n\n```ruby\nresults = Meilisearch::Rails.federated_search(\n  queries: [\n    { q: 'Harry', scope: Book },\n    { q: 'Attack on Titan', scope: Manga },\n  ]\n)\n```\n\n```ruby\nresults = Meilisearch::Rails.federated_search(\n  queries: {\n    Book =\u003e { q: 'Harry' },\n    Manga =\u003e { q: 'Attack on Titan' }\n  }\n)\n```\n\nIf the model is not provided, hashes are returned!\n\n### Scoping records \u003c!-- omit in toc --\u003e\n\nAny relation passed as `:scope` is used as the starting point when loading records:\n\n```ruby\nresults = Meilisearch::Rails.federated_search(\n  queries: [\n    { q: 'Harry', scope: Book.where('year \u003c= 2006') },\n    { q: 'Attack on Titan', scope: Manga.where(author: Author.find_by(name: 'Iseyama')) },\n  ]\n)\n```\n\n### Specifying the search index \u003c!-- omit in toc --\u003e\n\nIn order of precedence, to figure out which index to search, Meilisearch Rails will check:\n\n1. `index_uid` options\n   ```ruby\n   results = Meilisearch::Rails.federated_search(\n     queries: [\n       # Searching the 'fantasy_books' index\n       { q: 'Harry', scope: Book, index_uid: 'fantasy_books' },\n     ]\n   )\n   ```\n2. The index associated with the model\n   ```ruby\n   results = Meilisearch::Rails.federated_search(\n     queries: [\n       # Searching the index associated with the Book model\n       # i. e. Book.index.uid\n       { q: 'Harry', scope: Book },\n     ]\n   )\n   ```\n3. The key when using hash queries\n   ```ruby\n   results = Meilisearch::Rails.federated_search(\n     queries: {\n       # Searching index 'books_production'\n       books_production: { q: 'Harry', scope: Book },\n     }\n   )\n   ```\n\n### Pagination and other options \u003c!-- omit in toc --\u003e\n\nIn addition to queries, federated search also accepts `:federation` parameters which allow for finer control of the search:\n\n```ruby\nresults = Meilisearch::Rails.federated_search(\n  queries: [\n    { q: 'Harry', scope: Book },\n    { q: 'Attack on Titan', scope: Manga },\n  ],\n  federation: { offset: 10, limit: 5 }\n)\n```\nSee a full list of accepted options in [the meilisearch documentation](https://www.meilisearch.com/docs/reference/api/multi_search#federation).\n\n#### Metadata \u003c!-- omit in toc --\u003e\n\nThe returned result from a federated search includes a `.metadata` attribute you can use to access everything other than the search hits:\n\n```ruby\nresult.metadata\n# {\n#   \"processingTimeMs\" =\u003e 0,\n#   \"limit\" =\u003e 20,\n#   \"offset\" =\u003e 0,\n#   \"estimatedTotalHits\" =\u003e 2,\n#   \"semanticHitCount\": 0\n# }\n```\n\nThe metadata contains facet stats and pagination stats, among others. See the full response in [the documentation](https://www.meilisearch.com/docs/reference/api/multi_search#federated-multi-search-requests).\n\nMore details on federated search (such as available `federation:` options) can be found on [the official multi search documentation](https://www.meilisearch.com/docs/reference/api/multi_search).\n\n## 🪛 Options\n\n### Meilisearch configuration \u0026 environment\n\n### Backend Pagination with `kaminari` or `will_paginate` \u003c!-- omit in toc --\u003e\n\nThis gem supports:\n- [kaminari](https://github.com/amatsuda/kaminari)\n- [will_paginate](https://github.com/mislav/will_paginate)\n\nSpecify the `:pagination_backend` in the configuration file:\n\n```ruby\nMeilisearch::Rails.configuration = {\n  meilisearch_url: 'YourMeilisearchUrl',\n  meilisearch_api_key: 'YourMeilisearchAPIKey',\n  pagination_backend: :kaminari # :will_paginate\n}\n```\n\nThen, as soon as you use the `search` method, the returning results will be paginated:\n\n```ruby\n# controller\n@hits = Book.search('harry potter')\n\n# views\n\u003c% @hits.each do |hit| %\u003e\n  \u003c%= hit.title %\u003e\n  \u003c%= hit.author %\u003e\n\u003c% end %\u003e\n\n\u003c%= paginate @hits %\u003e # if using kaminari\n\n\u003c%= will_paginate @hits %\u003e # if using will_paginate\n```\n\nThe **number of hits per page defaults to 20**, you can customize it by adding the `hits_per_page` parameter to your search:\n\n```ruby\nBook.search('harry potter', hits_per_page: 10)\n```\n\n### Backend Pagination with `pagy` \u003c!-- omit in toc --\u003e\n\nThis gem supports [pagy](https://github.com/ddnexus/pagy) to paginate your search results.\n\nTo use `pagy` with your `meilisearch-rails` you need to:\n\nAdd the `pagy` gem to your Gemfile.\nCreate a new initializer `pagy.rb` with this:\n\n```rb\n# config/initializers/pagy.rb\n\nrequire 'pagy/extras/meilisearch'\n```\n\nThen in your model you must extend `Pagy::Meilisearch`:\n\n```rb\nclass Book \u003c ApplicationRecord\n  include Meilisearch::Rails\n  extend Pagy::Meilisearch\n\n  meilisearch # ...\nend\n```\n\nAnd in your controller and view:\n\n```rb\n# controllers/books_controller.rb\ndef search\n  hits = Book.pagy_search(params[:query])\n  @pagy, @hits = pagy_meilisearch(hits, items: 25)\nend\n\n\n# views/books/search.html.rb\n\u003c%== pagy_nav(@pagy) %\u003e\n```\n\n:warning: There is no need to set `pagination_backend` in the configuration block `Meilisearch::Rails.configuration` for `pagy`.\n\nCheck [`ddnexus/pagy`](https://ddnexus.github.io/pagy/extras/meilisearch) for more information.\n\n#### Deactivate Meilisearch in certain moments\n\nBy default, HTTP connections to the Meilisearch URL are always active, but sometimes you want to disable the HTTP requests in a particular moment or environment.\u003cbr\u003e\nyou have multiple ways to achieve this.\n\nBy adding `active: false` in the configuration initializer:\n\n```ruby\nMeilisearch::Rails.configuration = {\n  meilisearch_url: 'YourMeilisearchUrl',\n  meilisearch_api_key: 'YourMeilisearchAPIKey',\n  active: false\n}\n```\n\nOr you can disable programmatically:\n\n```ruby\nMeilisearch::Rails.deactivate! # all the following HTTP calls will be dismissed.\n\n# or you can pass a block to it:\n\nMeilisearch::Rails.deactivate! do\n  # every Meilisearch call here will be dismissed, no error will be raised.\n  # after the block, Meilisearch state will be active. \nend\n```\n\nYou can also activate if you deactivated earlier:\n\n```ruby\nMeilisearch::Rails.activate!\n```\n\n:warning: These calls are persistent, so prefer to use the method with the block. This way, you will not forget to activate it afterward.\n\n#### Custom index_uid \u003c!-- omit in toc --\u003e\n\nBy default, the **index_uid** will be the class name, e.g. `Book`. You can customize the index_uid by using the `index_uid:` option.\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch index_uid: 'MyCustomUID'\nend\n```\n\n#### Index UID according to the environment \u003c!-- omit in toc --\u003e\n\nYou can suffix the index UID with the current Rails environment by setting it globally:\n\n```ruby\nMeilisearch::Rails.configuration = {\n  meilisearch_url: 'YourMeilisearchUrl',\n  meilisearch_api_key: 'YourMeilisearchAPIKey',\n  per_environment: true\n}\n```\n\nThis way your index UID will look like this `\"Book_#{Rails.env}\"`.\n\n### Index configuration\n\n#### Custom attribute definition\n\nYou can add a custom attribute by using the `add_attribute` option or by using a block.\n\n⚠️ When using custom attributes, the gem is not able to detect changes on them. Your record will be pushed to the API even if the custom attribute didn't change. To prevent this behavior, you can create a `will_save_change_to_#{attr_name}?` method.\n\n```ruby\nclass Author \u003c ApplicationRecord\n  include Meilisearch::Rails\n\n  meilisearch do\n    attribute :first_name, :last_name\n    attribute :full_name do\n      \"#{first_name} #{last_name}\"\n    end\n    add_attribute :full_name_reversed\n  end\n\n  def full_name_reversed\n    \"#{last_name} #{first_name}\"\n  end\n\n  def will_save_change_to_full_name?\n    will_save_change_to_first_name? || will_save_change_to_last_name?\n  end\n\n  def will_save_change_to_full_name_reversed?\n    will_save_change_to_first_name? || will_save_change_to_last_name?\n  end\nend\n```\n\n#### Custom primary key\n\nBy default, the primary key is based on your record's id. You can change this behavior by specifying the `primary_key:` option.\n\nNote that the primary key must return a **unique value** otherwise your data could be overwritten.\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch primary_key: :isbn # isbn is a column in your table definition.\nend\n```\n\nYou can also set the `primary_key` as a method, this method will be evaluated in runtime, and its return \nwill be used as the reference to the document when Meilisearch needs it.\n\n```rb\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch primary_key: :my_custom_ms_id\n\n  private\n\n  def my_custom_ms_id\n    \"isbn_#{primary_key}\" # ensure this return is unique, otherwise you'll lose data.\n  end\nend\n```\n\n#### Conditional indexing\n\nYou can control if a record must be indexed by using the `if:` or `unless:` options.\u003cbr\u003e\nAs soon as you use those constraints, `add_documents` and `delete_documents` calls will be performed in order to keep the index synced with the DB. To prevent this behavior, you can create a `will_save_change_to_#{attr_name}?` method.\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch if: :published?, unless: :premium?\n\n  def published?\n    # [...]\n  end\n\n  def premium?\n    # [...]\n  end\n\n  def will_save_change_to_published?\n    # return true only if you know that the 'published' state changed\n  end\nend\n```\n##### Target multiple indexes \u003c!-- omit in toc --\u003e\n\nYou can index a record in several indexes using the `add_index` option:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  PUBLIC_INDEX_UID = 'Books'\n  SECURED_INDEX_UID = 'PrivateBooks'\n\n  # store all books in index 'SECURED_INDEX_UID'\n  meilisearch index_uid: SECURED_INDEX_UID do\n    searchable_attributes [:title, :author]\n\n    # store all 'public' (released and not premium) books in index 'PUBLIC_INDEX_UID'\n    add_index PUBLIC_INDEX_UID, if: :public? do\n      searchable_attributes [:title, :author]\n    end\n  end\n\n  private\n\n  def public?\n    released? \u0026\u0026 !premium?\n  end\nend\n```\n\n#### Share a single index\n\nYou may want to share an index between several models. You'll need to ensure you don't have any conflict with the `primary_key` of the models involved.\n\n```ruby\nclass Cat \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch index_uid: 'Animals', primary_key: :ms_id\n\n  private\n\n  def ms_id\n    \"cat_#{primary_key}\" # ensure the cats \u0026 dogs primary_keys are not conflicting\n  end\nend\n\nclass Dog \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch index_uid: 'Animals', primary_key: :ms_id\n\n  private\n\n  def ms_id\n    \"dog_#{primary_key}\" # ensure the cats \u0026 dogs primary_keys are not conflicting\n  end\nend\n```\n\n#### Queues \u0026 background jobs\n\nYou can configure the auto-indexing \u0026 auto-removal process to use a queue to perform those operations in background. ActiveJob queues are used by default but you can define your own queuing mechanism:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch enqueue: true # ActiveJob will be triggered using a `meilisearch` queue\nend\n```\n\n🤔 If you are performing updates and deletions in the background, a record deletion can be committed to your database prior to the job actually executing. Thus if you were to load the record to remove it from the database then your `ActiveRecord#find` will fail with a `RecordNotFound`.\n\nIn this case you can bypass loading the record from **ActiveRecord** and just communicate with the index directly.\n\nWith **ActiveJob**:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch enqueue: :trigger_job do\n    attribute :title, :author, :description\n  end\n\n  def self.trigger_job(record, remove)\n    MyActiveJob.perform_later(record.id, remove)\n  end\nend\n\nclass MyActiveJob \u003c ApplicationJob\n  def perform(id, remove)\n    if remove\n      # The record has likely already been removed from your database so we cannot\n      # use ActiveRecord#find to load it.\n      # We access the underlying Meilisearch index object.\n      Book.index.delete_document(id)\n    else\n      # The record should be present.\n      Book.find(id).index!\n    end\n  end\nend\n```\n\nWith [**Sidekiq**](https://github.com/mperham/sidekiq):\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch enqueue: :trigger_sidekiq_job do\n    attribute :title, :author, :description\n  end\n\n  def self.trigger_sidekiq_job(record, remove)\n    MySidekiqJob.perform_async(record.id, remove)\n  end\nend\n\nclass MySidekiqJob\n  def perform(id, remove)\n    if remove\n      # The record has likely already been removed from your database so we cannot\n      # use ActiveRecord#find to load it.\n      # We access the underlying Meilisearch index object.\n      Book.index.delete_document(id)\n    else\n      # The record should be present.\n      Book.find(id).index!\n    end\n  end\nend\n```\n\nWith [**DelayedJob**](https://github.com/collectiveidea/delayed_job):\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch enqueue: :trigger_delayed_job do\n    attribute :title, :author, :description\n  end\n\n  def self.trigger_delayed_job(record, remove)\n    if remove\n      record.delay.remove_from_index!\n    else\n      record.delay.index!\n    end\n  end\nend\n```\n\n#### Relations\n\nExtend a change to a related record.\n\n**With ActiveRecord**, you'll need to use `touch` and `after_touch`.\n\n```ruby\nclass Author \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  has_many :books\n  # If your association uses belongs_to\n  # - use `touch: true`\n  # - do not define an `after_save` hook\n  after_save { books.each(\u0026:touch) }\nend\n\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  belongs_to :author\n  after_touch :index!\n\n  meilisearch do\n    attribute :title, :description, :publisher\n    attribute :author do\n      author.name\n    end\n  end\nend\n```\n\nWith **Sequel**, you can use the `touch` plugin to propagate changes.\n\n```ruby\n# app/models/author.rb\nclass Author \u003c Sequel::Model\n  include Meilisearch::Rails\n\n  one_to_many :books\n\n  plugin :timestamps\n  # Can't use the associations since it won't trigger the after_save\n  plugin :touch\n\n  # Define the associations that need to be touched here\n  # Less performant, but allows for the after_save hook to be triggered\n  def touch_associations\n    apps.map(\u0026:touch)\n  end\n\n  def touch\n    super\n    touch_associations\n  end\nend\n\n# app/models/book.rb\nclass Book \u003c Sequel::Model\n  include Meilisearch::Rails\n\n  many_to_one :author\n  after_touch :index!\n\n  plugin :timestamps\n  plugin :touch\n\n  meilisearch do\n    attribute :title, :description, :publisher\n    attribute :author do\n      author.name\n    end\n  end\nend\n```\n\n#### Sanitize attributes\n\nYou can strip all HTML tags from your attributes with the `sanitize` option.\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch sanitize: true\nend\n```\n\n#### UTF-8 encoding\n\nYou can force the UTF-8 encoding of all your attributes using the `force_utf8_encoding` option.\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch force_utf8_encoding: true\nend\n```\n\n#### Eager loading\n\nYou can eager load associations using `meilisearch_import` scope.\n\n```ruby\nclass Author \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  has_many :books\n\n  scope :meilisearch_import, -\u003e { includes(:books) }\nend\n```\n\n### Manual operations\n\n#### Indexing \u0026 deletion\n\nYou can manually index a record by using the `index!` instance method and remove it by using the `remove_from_index!` instance method.\n\n```ruby\nbook = Book.create!(title: 'The Little Prince', author: 'Antoine de Saint-Exupéry')\nbook.index!\nbook.remove_from_index!\nbook.destroy!\n```\n\nTo reindex all your records, use the `reindex!` class method:\n\n```ruby\nBook.reindex!\n\n# You can also index a subset of your records\nBook.where('updated_at \u003e ?', 10.minutes.ago).reindex!\n```\n\nTo delete all your records, use the `clear_index!` class method:\n\n```ruby\nBook.clear_index!\n```\n\n#### Access the underlying index object\n\nTo access the index object and use the [Ruby SDK](https://github.com/meilisearch/meilisearch-ruby) methods for an index, call the `index` class method:\n\n```ruby\nindex = Book.index\n# index.get_settings, index.number_of_documents\n```\n\n### Development \u0026 testing\n\n#### Exceptions \u003c!-- omit in toc --\u003e\n\nYou can disable exceptions that could be raised while trying to reach Meilisearch's API by using the `raise_on_failure` option:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  # Only raise exceptions in development environment.\n  meilisearch raise_on_failure: Rails.env.development?\nend\n```\n\n#### Testing \u003c!-- omit in toc --\u003e\n\n##### Synchronous testing \u003c!-- omit in toc --\u003e\n\nYou can force indexing and removing to be synchronous by setting the following option:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch synchronous: true\nend\n```\n🚨 This is only recommended for testing purposes, the gem will call the `wait_for_task` method that will stop your code execution until the asynchronous task has been processed by MeilSearch.\n\n##### Disable auto-indexing \u0026 auto-removal \u003c!-- omit in toc --\u003e\n\nYou can disable auto-indexing and auto-removing setting the following options:\n\n```ruby\nclass Book \u003c ActiveRecord::Base\n  include Meilisearch::Rails\n\n  meilisearch auto_index: false, auto_remove: false\nend\n```\n\nYou can temporarily disable auto-indexing using the without_auto_index scope:\n\n```ruby\nBook.without_auto_index do\n  # Inside this block, auto indexing task will not run.\n  1.upto(10000) { Book.create! attributes }\nend\n```\n\n## ⚙️ Development workflow \u0026 contributing\n\nAny new contribution is more than welcome in this project!\n\nIf you want to know more about the development workflow or want to contribute, please visit our [contributing guidelines](/CONTRIBUTING.md) for detailed instructions!\n\n## 👏  Credits\n\nThe provided features and the code base is inspired by [algoliasearch-rails](https://github.com/algolia/algoliasearch-rails/).\n\n\u003chr\u003e\n\n**Meilisearch** provides and maintains many **SDKs and Integration tools** like this one. We want to provide everyone with an **amazing search experience for any kind of project**. If you want to contribute, make suggestions, or just know what's going on right now, visit us in the [integration-guides](https://github.com/meilisearch/integration-guides) repository.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeilisearch%2Fmeilisearch-rails","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmeilisearch%2Fmeilisearch-rails","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeilisearch%2Fmeilisearch-rails/lists"}