{"id":13879410,"url":"https://github.com/npezza93/redi_search","last_synced_at":"2025-10-16T15:15:12.540Z","repository":{"id":34876890,"uuid":"183129948","full_name":"npezza93/redi_search","owner":"npezza93","description":"Ruby wrapper around RediSearch that can integrate with Rails","archived":false,"fork":false,"pushed_at":"2024-08-28T14:24:41.000Z","size":844,"stargazers_count":46,"open_issues_count":2,"forks_count":8,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-02T14:17:53.065Z","etag":null,"topics":["rails","redis","redisearch","ruby","search","search-engine"],"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/npezza93.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2019-04-24T02:17:22.000Z","updated_at":"2024-08-28T14:24:45.000Z","dependencies_parsed_at":"2024-11-24T08:15:12.793Z","dependency_job_id":null,"html_url":"https://github.com/npezza93/redi_search","commit_stats":{"total_commits":491,"total_committers":5,"mean_commits":98.2,"dds":"0.21995926680244404","last_synced_commit":"a2ab1e601cfad41c2ec6f6376a198f0562f05839"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/npezza93%2Fredi_search","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/npezza93%2Fredi_search/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/npezza93%2Fredi_search/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/npezza93%2Fredi_search/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/npezza93","download_url":"https://codeload.github.com/npezza93/redi_search/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244066179,"owners_count":20392406,"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":["rails","redis","redisearch","ruby","search","search-engine"],"created_at":"2024-08-06T08:02:20.205Z","updated_at":"2025-10-16T15:15:07.502Z","avatar_url":"https://github.com/npezza93.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/npezza93/redi_search\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/npezza93/redi_search/main/.github/logo.svg?sanitize=true\" width=\"350\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n# RediSearch\n\nA simple, but powerful, Ruby wrapper around RediSearch, a search engine on top of\nRedis.\n\n## Installation\n\nFirstly, Redis and RediSearch need to be installed.\n\nYou can download Redis from https://redis.io/download, and check out\ninstallation instructions\n[here](https://github.com/antirez/redis#installing-redis). Alternatively, on\nmacOS or Linux you can install via Homebrew.\n\nTo install RediSearch check out,\n[https://oss.redislabs.com/redisearch/Quick_Start.html](https://oss.redislabs.com/redisearch/Quick_Start.html).\nOnce you have RediSearch built, if you are not using Docker, you can update your\nredis.conf file to always load the RediSearch module with\n`loadmodule /path/to/redisearch.so`. (On macOS the redis.conf file can be found\nat `/usr/local/etc/redis.conf`)\n\nAfter Redis and RediSearch are up and running, add the following line to your\nGemfile:\n\n```ruby\ngem 'redi_search'\n```\n\nAnd then:\n```bash\n❯ bundle\n````\n\nOr install it yourself:\n```bash\n❯ gem install redi_search\n```\n\nand require it:\n```ruby\nrequire 'redi_search'\n```\n\nOnce the gem is installed and required you'll need to configure it with your\nRedis configuration. If you're on Rails, this should go in an initializer\n(`config/initializers/redi_search.rb`).\n\n```ruby\nRediSearch.configure do |config|\n  config.redis_config = {\n    host: \"127.0.0.1\",\n    port: \"6379\"\n  }\nend\n```\n\n## Table of Contents\n   - [Preface](#preface)\n   - [Schema](#schema)\n   - [Document](#document)\n   - [Index](#index)\n   - [Searching](#searching)\n   - [Spellcheck](#spellcheck)\n   - [Rails Integration](#rails-integration)\n\n\n## Preface\nRediSearch revolves around a search index, so lets start with\ndefining what a search index is. According to [Swiftype](https://swiftype.com):\n\u003e A search index is a body of structured data that a search engine refers to\n\u003e when looking for results that are relevant to a specific query. Indexes are a\n\u003e critical piece of any search system, since they must be tailored to the\n\u003e specific information retrieval method of the search engine’s algorithm. In\n\u003e this manner, the algorithm and the index are inextricably linked to one\n\u003e another. Index can also be used as a verb (indexing), referring to the process\n\u003e of collecting unstructured website data in a structured format that is\n\u003e tailored for the search engine algorithm.\n\u003e\n\u003e One way to think about indices is to consider the following analogy between a\n\u003e search infrastructure and an office filing system. Imagine you hand an intern\n\u003e a stack of thousands of pieces of paper (documents) and tell them to organize\n\u003e these pieces of paper in a filing cabinet (index) to help the company find\n\u003e information more efficiently. The intern will first have to sort through the\n\u003e papers and get a sense of all the information contained within them, then they\n\u003e will have to decide on a system for arranging them in the filing cabinet, then\n\u003e finally they’ll need to decide what is the most effective manner for searching\n\u003e through and selecting from the files once they are in the cabinet. In this\n\u003e example, the process of organizing and filing the papers corresponds to the\n\u003e process of indexing website content, and the method for searching across these\n\u003e organized files and finding those that are most relevant corresponds to the\n\u003e search algorithm.\n\n\n## Schema\n\nThis defines the fields and the properties of those fields in the index. A\nschema is a simple DSL. Each field can be one of four types: geo, numeric, tag,\nor text and can have many options. A simple example of a schema is:\n```ruby\nRediSearch::Schema.new do\n  text_field :first_name\n  text_field :last_name\nend\n```\n\nThe supported options for each type are as follows:\n\n##### Text field\nWith no options: `text_field :name`\n\n\u003cdetails\u003e\n  \u003csummary\u003eOptions\u003c/summary\u003e\n  \u003cul\u003e\n    \u003cli\u003e\n      \u003cb\u003eweight\u003c/b\u003e (default: 1.0)\n      \u003cul\u003e\n        \u003cli\u003eDeclares the importance of this field when calculating result accuracy. This is a multiplication factor.\u003c/li\u003e\n        \u003cli\u003eEx: \u003ccode\u003etext_field :name, weight: 2\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n      \u003cb\u003ephonetic\u003c/b\u003e\n      \u003cul\u003e\n        \u003cli\u003eWill perform phonetic matching on field in searches by default. The obligatory {matcher} argument specifies the phonetic algorithm and language used. The following matchers are supported:\n          \u003cul\u003e\n            \u003cli\u003edm:en - Double Metaphone for English\u003c/li\u003e\n            \u003cli\u003edm:fr - Double Metaphone for French\u003c/li\u003e\n            \u003cli\u003edm:pt - Double Metaphone for Portuguese\u003c/li\u003e\n            \u003cli\u003edm:es - Double Metaphone for Spanish\u003c/li\u003e\n          \u003c/ul\u003e\n        \u003c/li\u003e\n        \u003cli\u003e\n          Ex: \u003ccode\u003etext_field :name, phonetic: 'dm:en'\u003c/code\u003e\n        \u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n      \u003cb\u003esortable\u003c/b\u003e (default: false)\n      \u003cul\u003e\n        \u003cli\u003eAllows the user to later sort the results by the value of this field (this adds memory overhead so do not declare it on large text fields).\u003c/li\u003e\n        \u003cli\u003eEx: \u003ccode\u003etext_field :name, sortable: true\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n      \u003cb\u003eno_index\u003c/b\u003e (default: false)\n      \u003cul\u003e\n        \u003cli\u003eField will not be indexed. This is useful in conjunction with \u003ccode\u003esortable\u003c/code\u003e, to create fields whose update using PARTIAL will not cause full reindexing of the document. If a field has \u003ccode\u003eno_index\u003c/code\u003e and doesn't have \u003ccode\u003esortable\u003c/code\u003e, it will just be ignored by the index.\u003c/li\u003e\n        \u003cli\u003eEx: \u003ccode\u003etext_field :name, no_index: true\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n      \u003cb\u003eno_stem\u003c/b\u003e (default: false)\n      \u003cul\u003e\n        \u003cli\u003eDisable stemming when indexing its values. This may be ideal for things like proper names.\u003c/li\u003e\n        \u003cli\u003eEx: \u003ccode\u003etext_feidl :name, no_stem: true\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/details\u003e\n\n##### Numeric field\nWith no options: `numeric_field :price`\n\n\u003cdetails\u003e\n  \u003csummary\u003eOptions\u003c/summary\u003e\n  \u003cul\u003e\n    \u003cli\u003e\n      \u003cb\u003esortable\u003c/b\u003e (default: false)\n      \u003cul\u003e\n        \u003cli\u003eAllows the user to later sort the results by the value of this field (this adds memory overhead so do not declare it on large text fields).\u003c/li\u003e\n        \u003cli\u003eEx: \u003ccode\u003enumeric_field :id, sortable: true\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n      \u003cb\u003eno_index\u003c/b\u003e (default: false)\n      \u003cul\u003e\n        \u003cli\u003eField will not be indexed. This is useful in conjunction with \u003ccode\u003esortable\u003c/code\u003e, to create fields whose update using PARTIAL will not cause full reindexing of the document. If a field has \u003ccode\u003eno_index\u003c/code\u003e and doesn't have \u003ccode\u003esortable\u003c/code\u003e, it will just be ignored by the index.\u003c/li\u003e\n        \u003cli\u003eEx: \u003ccode\u003enumeric_field :id, no_index: true\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/details\u003e\n\n##### Tag field\nWith no options: `tag_field :tag`\n\n\u003cdetails\u003e\n  \u003csummary\u003eOptions\u003c/summary\u003e\n  \u003cul\u003e\n    \u003cli\u003e\n      \u003cb\u003esortable\u003c/b\u003e (default: false)\n      \u003cul\u003e\n        \u003cli\u003eAllows the user to later sort the results by the value of this field (this adds memory overhead so do not declare it on large text fields).\u003c/li\u003e\n        \u003cli\u003eEx: \u003ccode\u003etag_field :tag, sortable: true\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n      \u003cb\u003eno_index\u003c/b\u003e (default: false)\n      \u003cul\u003e\n        \u003cli\u003eField will not be indexed. This is useful in conjunction with \u003ccode\u003esortable\u003c/code\u003e, to create fields whose update using PARTIAL will not cause full reindexing of the document. If a field has \u003ccode\u003eno_index\u003c/code\u003e and doesn't have \u003ccode\u003esortable\u003c/code\u003e, it will just be ignored by the index.\u003c/li\u003e\n        \u003cli\u003eEx: \u003ccode\u003etag_field :tag, no_index: true\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n      \u003cb\u003eseparator\u003c/b\u003e (default: \",\")\n      \u003cul\u003e\n        \u003cli\u003eIndicates how the text contained in the field is to be split into individual tags. The default is ,. The value must be a single character.\u003c/li\u003e\n        \u003cli\u003eEx: \u003ccode\u003etag_field :tag, separator: ','\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/details\u003e\n\n##### Geo field\nWith no options: `geo_field :place`\n\n\u003cdetails\u003e\n  \u003csummary\u003eOptions\u003c/summary\u003e\n  \u003cul\u003e\n    \u003cli\u003e\n      \u003cb\u003esortable\u003c/b\u003e (default: false)\n      \u003cul\u003e\n        \u003cli\u003eAllows the user to later sort the results by the value of this field (this adds memory overhead so do not declare it on large text fields).\u003c/li\u003e\n        \u003cli\u003eEx: \u003ccode\u003egeo_field :place, sortable: true\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n      \u003cb\u003eno_index\u003c/b\u003e (default: false)\n      \u003cul\u003e\n        \u003cli\u003eField will not be indexed. This is useful in conjunction with \u003ccode\u003esortable\u003c/code\u003e, to create fields whose update using PARTIAL will not cause full reindexing of the document. If a field has \u003ccode\u003eno_index\u003c/code\u003e and doesn't have \u003ccode\u003esortable\u003c/code\u003e, it will just be ignored by the index.\u003c/li\u003e\n        \u003cli\u003eEx: \u003ccode\u003egeo_field :place, no_index: true\u003c/code\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/li\u003e\n  \u003c/ul\u003e\n\u003c/details\u003e\n\n## Document\n\nA `Document` is the Ruby representation of a Redis hash.\n\nYou can fetch a `Document` using `.get` class methods.\n- `get(index, document_id)` fetches a single `Document` in an `Index` for a\ngiven `document_id`.\n\nYou can also make a `Document` instance using the\n`.for_object(index, record, only: [])` class method. It takes\nan `Index` instance and a Ruby object. That object must respond to all the\nfields specified in the `Index`'s `Schema`. `only` accepts an array of fields\nfrom the schema and limits the fields that are passed to the `Document`.\n\nOnce you have an instance of a `Document`, it responds to all the fields\nspecified in the `Index`'s `Schema` as methods and `document_id`. `document_id`\nis automatically prepended with the `Index`'s names unless it already is to\nensure uniqueness. We prepend the `Index` name because if you have two\n`Document`s with the same id in different `Index`s we don't want the `Document`s\nto override each other. There is also a `#document_id_without_index` method\nwhich removes the prepended index name.\n\nFinally there is a `#del` method that will remove the `Document` from the\n`Index`.\n\n## Index\n\nTo initialize an `Index`, pass the name of the `Index` as a string or symbol\nand the `Schema` block.\n\n```ruby\nRediSearch::Index.new(name_of_index) do\n  text_field :foobar\nend\n```\n\n#### Available Commands\n\n- `create`\n  - Creates the index in the Redis instance, returns a boolean. Has an\n    accompanying bang method that will raise an exception upon failure. Will\n    return `false` if the index already exists. Accepts a few options:\n      - `max_text_fields: #{true || false}`\n        - For efficiency, RediSearch encodes indexes differently if they are\n          created with less than 32 text fields. This option forces RediSearch\n          to encode indexes as if there were more than 32 text fields, which\n          allows you to add additional fields (beyond 32) using `add_field`.\n      - `no_offsets: #{true || false}`\n        - If set, we do not store term offsets for documents (saves memory, does\n          not allow exact searches or highlighting). Implies `no_highlight`.\n      - `temporary: #{seconds}`\n        - Create a lightweight temporary index which will expire after `seconds`\n          seconds of inactivity. The internal idle timer is reset whenever the\n          index is searched or added to. Because such indexes are lightweight,\n          you can create thousands of such indexes without negative performance\n          implications.\n      - `no_highlight: #{true || false}`\n        - Conserves storage space and memory by disabling highlighting support.\n          If set, we do not store corresponding byte offsets for term positions.\n          `no_highlight` is also implied by `no_offsets`.\n      - `no_fields: #{true || false}`\n        - If set, we do not store field bits for each term. Saves memory, does\n          not allow filtering by specific fields.\n      - `no_frequencies: #{true || false}`\n        - If set, we avoid saving the term frequencies in the index. This saves\n          memory but does not allow sorting based on the frequencies of a given\n          term within the document.\n- `drop(keep_docs: false)`\n  - Drops the `Index` from the Redis instance, returns a boolean. Has an\n    accompanying bang method that will raise an exception upon failure. Will\n    return `false` if the `Index` has already been dropped. Takes an option\n    keyword arg, `keep_docs`, that will by default remove all the document\n    hashes in Redis.\n- `exist?`\n  - Returns a boolean signifying `Index` existence.\n- `info`\n  - Returns a struct object with all the information about the `Index`.\n- `fields`\n  - Returns an array of the field names in the `Index`.\n- `add(document)`\n  - Takes a `Document` object. Has an\n    accompanying bang method that will raise an exception upon failure.\n- `add_multiple(documents)`\n  - Takes an array of `Document` objects. This provides a more performant way to\n    add multiple documents to the `Index`. Accepts the same options as `add`.\n- `del(document)`\n  - Removes a `Document` from the `Index`.\n- `document_count`\n  - Returns the number of `Document`s in the `Index`\n- `add_field(name, type, **options, \u0026block)`\n  - Adds a new field to the `Index`.\n  - The block and options are optional.\n  - Ex: `index.add_field(:first_name, :text, phonetic: \"dm:en\")`\n- `reindex(documents, recreate: false)`\n   - If `recreate` is `true` the `Index` will be dropped and recreated\n\n\n## Searching\n\nSearching is initiated off a `RediSearch::Index` instance with clauses that can\nbe chained together. When searching, an array of `Document`s is returned\nwhich has public reader methods for all the schema fields.\n\n```ruby\nmain ❯ index = RediSearch::Index.new(\"user_idx\") { text_field :name, phonetic: \"dm:en\" }\nmain ❯ index.add RediSearch::Document.for_object(index, User.new(\"10039\", \"Gene\", \"Volkman\"))\nmain ❯ index.add RediSearch::Document.for_object(index, User.new(\"9998\", \"Jeannie\", \"Ledner\"))\nmain ❯ index.search(\"john\")\n  RediSearch (1.1ms)  FT.SEARCH user_idx `john`\n=\u003e [#\u003cRediSearch::Document:0x00007f862e241b78 first: \"Gene\", last: \"Volkman\", document_id: \"10039\"\u003e,\n#\u003cRediSearch::Document:0x00007f862e2417b8 first: \"Jeannie\", last: \"Ledner\", document_id: \"9998\"\u003e]\n```\n**Simple phrase query** - `hello AND world`\n```ruby\nindex.search(\"hello\").and(\"world\")\n```\n**Exact phrase query** - `hello FOLLOWED BY world`\n```ruby\nindex.search(\"hello world\")\n```\n**Union query** - `hello OR world`\n```ruby\nindex.search(\"hello\").or(\"world\")\n```\n**Negation query** - `hello AND NOT world`\n```ruby\nindex.search(\"hello\").and.not(\"world\")\n```\n\nComplex intersections and unions:\n```ruby\n# Intersection of unions\nindex.search(index.search(\"hello\").or(\"halo\")).and(index.search(\"world\").or(\"werld\"))\n# Negation of union\nindex.search(\"hello\").and.not(index.search(\"world\").or(\"werld\"))\n# Union inside phrase\nindex.search(\"hello\").and(index.search(\"world\").or(\"werld\"))\n```\n\nAll terms support a few options that can be applied.\n\n**Prefix terms**: match all terms starting with a prefix.\n(Akin to `like term%` in SQL)\n```ruby\nindex.search(\"hel\", prefix: true)\nindex.search(\"hello worl\", prefix: true)\nindex.search(\"hel\", prefix: true).and(\"worl\", prefix: true)\nindex.search(\"hello\").and.not(\"worl\", prefix: true)\n```\n\n**Optional terms**: documents containing the optional terms will rank higher\nthan those without\n```ruby\nindex.search(\"foo\").and(\"bar\", optional: true).and(\"baz\", optional: true)\n```\n\n**Fuzzy terms**: matches are performed based on Levenshtein distance (LD). The\nmaximum Levenshtein distance supported is 3.\n```ruby\nindex.search(\"zuchini\", fuzziness: 1)\n```\n\nSearch terms can also be scoped to specific fields using a `where` clause:\n```ruby\n# Simple field specific query\nindex.search.where(name: \"john\")\n# Using where with options\nindex.search.where(first: \"jon\", fuzziness: 1)\n# Using where with more complex query\nindex.search.where(first: index.search(\"bill\").or(\"bob\"))\n```\n\nSearching for numeric fields takes a range:\n```ruby\nindex.search.where(number: 0..100)\n# Searching to infinity\nindex.search.where(number: 0..Float::INFINITY)\nindex.search.where(number: -Float::INFINITY..0)\n```\n\n##### Query level clauses\n- `slop(level)`\n  - We allow a maximum of N intervening number of unmatched offsets between\n  phrase terms. (i.e the slop for exact phrases is 0)\n- `in_order`\n  - Usually used in conjunction with `slop`. We make sure the query terms appear\n    in the same order in the `Document` as in the query, regardless of the\n    offsets between them.\n- `no_content`\n  - Only return the `Document` ids and not the content. This is useful if\n    RediSearch is being used on a Rails model where the `Document` attributes\n    don't matter and it's being converted into `ActiveRecord` objects.\n- `language(language)`\n  - Stemmer to use for the supplied language during search for query expansion.\n    If querying `Document`s in Chinese, this should be set to chinese in order to\n    properly tokenize the query terms. If an unsupported language is sent, the\n    command returns an error.\n- `sort_by(field, order: :asc)`\n  - If the supplied field is a sortable field, the results are ordered by the\n    value of this field. This applies to both text and numeric fields. Available\n    orders are `:asc` or `:desc`\n- `limit(num, offset = 0)`\n  - Limit the results to the specified `num` at the `offset`. The default limit\n    is set to `10`.\n- `count`\n  - Returns the number of `Document`s found in the search query\n- `highlight(fields: [], opening_tag: \"\u003cb\u003e\", closing_tag: \"\u003c/b\u003e\")`\n  - Use this option to format occurrences of matched text. `fields` are an\n    array of fields to be highlighted.\n- `verbatim`\n  - Do not try to use stemming for query expansion but search the query terms\n    verbatim.\n- `no_stop_words`\n  - Do not filter stopwords from the query.\n- `with_scores`\n  - Include the relative internal score of each `Document`. This can be used to\n    merge results from multiple instances. This will add a `score` method to the\n    returned `Document` instances.\n- `return(*fields)`\n  - Limit which fields from the `Document` are returned.\n- `explain`\n  - Returns the execution plan for a complex query. In the returned response,\n    a + on a term is an indication of stemming.\n\n\n## Spellcheck\n\nSpellchecking is initiated off a `RediSearch::Index` instance and provides\nsuggestions for misspelled search terms. It takes an optional `distance`\nargument which is the maximal Levenshtein distance for spelling suggestions. It\nreturns an array where each element contains suggestions for each search term\nand a normalized score based on its occurrences in the index.\n\n```ruby\nmain ❯ index = RediSearch::Index.new(\"user_idx\") { text_field :name, phonetic: \"dm:en\" }\nmain ❯ index.spellcheck(\"jimy\")\n  RediSearch (1.1ms)  FT.SPELLCHECK user_idx jimy DISTANCE 1\n  =\u003e [#\u003cRediSearch::Spellcheck::Result:0x00007f805591c670\n    term: \"jimy\",\n    suggestions:\n     [#\u003cstruct RediSearch::Spellcheck::Suggestion score=0.0006849315068493151, suggestion=\"jimmy\"\u003e,\n      #\u003cstruct RediSearch::Spellcheck::Suggestion score=0.00019569471624266145, suggestion=\"jim\"\u003e]\u003e]\nmain ❯ index.spellcheck(\"jimy\", distance: 2).first.suggestions\n  RediSearch (0.5ms)  FT.SPELLCHECK user_idx jimy DISTANCE 2\n=\u003e [#\u003cstruct RediSearch::Spellcheck::Suggestion score=0.0006849315068493151, suggestion=\"jimmy\"\u003e,\n #\u003cstruct RediSearch::Spellcheck::Suggestion score=0.00019569471624266145, suggestion=\"jim\"\u003e]\n```\n\n\n## Rails Integration\n\nIntegration with Rails is super easy! Call `redi_search` with the `schema`\nkeyword argument from inside your model. Ex:\n\n```ruby\nclass User \u003c ApplicationRecord\n  redi_search do\n    text_field :first, phonetic: \"dm:en\"\n    text_field :last, phonetic: \"dm:en\"\n  end\nend\n```\n\nThis will automatically add `User.search` and `User.spellcheck`\nmethods which behave the same as if you called them on an `Index` instance.\n\n`User.reindex(recreate: false, only: [])` is also added and behaves\nsimilarly to `RediSearch::Index#reindex`. Some of the differences include:\n  - `Document`s do not need to be passed as the first parameter. The `search_import`\n    scope is automatically called and all the records are converted\n    to `Document`s.\n  - Accepts an optional `only` parameter where you can specify a limited number\n    of fields to update. Useful if you alter the schema and only need to index a\n    particular field.\n\n\nWhile defining the schema you can optionally pass it a block. If no block is\npassed the `name` will called on the model to get the value. If a block is\npassed the value for the field is obtained through calling the block.\n\n```ruby\nclass User \u003c ApplicationRecord\n  redi_search do\n    text_field :name do\n      \"#{first_name} #{last_name}\"\n    end\n  end\nend\n```\n\nYou can override the `search_import` scope on the model to eager load\nrelationships when indexing or it can be used to limit the records to index.\n\n```ruby\nclass User \u003c ApplicationRecord\n  scope :search_import, -\u003e { includes(:posts) }\nend\n```\n\nWhen searching, by default a collection of `Document`s is returned. Calling\n`#results` on the search query will execute the search, and then look up all the\nfound records in the database and return an ActiveRecord relation.\n\nThe default `Index` name for model `Index`s is\n`#{model_name.plural}_#{RediSearch.env}`. The `redi_search` method takes an\noptional `index_prefix` argument which gets prepended to the index name:\n\n```ruby\nclass User \u003c ApplicationRecord\n  redi_search index_prefix: 'prefix' do\n    text_field :first, phonetic: \"dm:en\"\n    text_field :last, phonetic: \"dm:en\"\n  end\nend\n\nUser.search_index.name\n# =\u003e prefix_users_development\n```\n\nWhen integrating RediSearch into a model, records will automatically be indexed\nafter creating and updating and will be removed from the `Index` upon\ndestruction.\n\nThere are a few more convenience methods that are publicly available:\n- `search_document`\n  - Returns the record as a `RediSearch::Document` instance\n- `remove_from_index`\n  - Removes the record from the `Index`\n- `add_to_index`\n  - Adds the record to the `Index`\n- `search_index`\n  - Returns the `RediSearch::Index` instance\n\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run\n`rake test` to run the both unit and integration tests. To run them individually\nyou can run `rake test:unit` or `rake test:integration`. You can also run\n`bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To\nrelease a new version, execute `bin/publish (major|minor|patch)` which will\nupdate the version number in `version.rb`, create a git tag for the version,\npush git commits and tags, and push the `.gem` file to\n[rubygems.org](https://rubygems.org) and GitHub.\n\n## Contributing\n\nBug reports and pull requests are welcome on\n[GitHub](https://github.com/npezza93/redi_search). This project is intended to\nbe a safe, welcoming space for collaboration, and contributors are expected to\nadhere to the [Contributor Covenant](http://contributor-covenant.org) code of\nconduct.\n\n## License\n\nThe gem is available as open source under the terms of the\n[MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the RediSearch project’s codebases, issue trackers, chat\nrooms and mailing lists is expected to follow the [code of\nconduct](https://github.com/npezza93/redi_search/blob/main/CODE_OF_CONDUCT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnpezza93%2Fredi_search","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnpezza93%2Fredi_search","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnpezza93%2Fredi_search/lists"}