From c4d7b61964d4e1e5b414c956e1af3d2990c5f2a9 Mon Sep 17 00:00:00 2001 From: CaroFG Date: Wed, 2 Jun 2021 13:45:48 +0200 Subject: [PATCH 01/14] Add getting started, settings and custom search --- README.md | 199 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 197 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 78496f57..45048156 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,197 @@ -# ⚠️ WIP ⚠️ meilisearch-rails -A MeiliSearch integration for Ruby on Rails +

+ MeiliSearch-Rails +

+ +

MeiliSearch Rails

+ +

+ MeiliSearch | + Documentation | + Slack | + Roadmap | + Website | + FAQ +

+ +

+ Test + License + Bors enabled +

+ +

⚡ The MeiliSearch integration for Ruby on Rails 💎

+ +**MeiliSearch Rails** is the MeiliSearch integration for Ruby on Rails developers. + +**MeiliSearch** is an open-source search engine. [Discover what MeiliSearch is!](https://github.com/meilisearch/MeiliSearch) + +## Table of Contents + +- [📖 Documentation](#-documentation) +- [🔧 Installation](#-installation) +- [🚀 Getting Started](#-getting-started) +- [⚙️ Settings](#-settings) +- [🔍 Custom search](#-custom-search) +- [🪛 Options](#-options) + +## 📖 Documentation + +See our [Documentation](https://docs.meilisearch.com/learn/tutorials/getting_started.html) or our [API References](https://docs.meilisearch.com/reference/api/). + +## 🔧 Installation + +This package requires Ruby version 2.6.0 or later and Rails 5.2 or later. + +With `gem` in command line: +```bash +gem install meilisearch-rails +``` + +In your `Gemfile` with [bundler](https://bundler.io/): +```ruby +source 'https://rubygems.org' + +gem 'meilisearch-rails' +``` + +### Run MeiliSearch + +There are many easy ways to [download and run a MeiliSearch instance](https://docs.meilisearch.com/reference/features/installation.html#download-and-launch). + +For example, if you use Docker: + +```bash +docker pull getmeili/meilisearch:latest # Fetch the latest version of MeiliSearch image from Docker Hub +docker run -it --rm -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --master-key=masterKey +``` + +NB: you can also download MeiliSearch from **Homebrew** or **APT**. + +## 🚀 Getting Started + +#### Configuration + +Create a new file `config/initializers/meilisearch.rb` to setup your `MEILISEARCH_HOST` and `MEILISEARCH_API_KEY` + +```ruby + +MeiliSearch.configuration = { + meilisearch_host: 'YourMeiliSearchHost', + meilisearch_api_key: 'YourMeiliSearchAPIKey', +} +``` + +The 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). + + +#### Add documents + +The following code will create a `Book` index and add search capabilities to your `Book` model + +```ruby +class Book < ActiveRecord::Base + include MeiliSearch + + meilisearch do + # all attributes will be sent to MeiliSearch if block is left empty + end +end +``` + +#### Basic Backend Search + +We **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) + +Search returns ORM-compliant objects reloaded from your database. + +```ruby +# MeiliSearch is typo-tolerant: +hits = Book.search('harry pottre') +hits.each do |hit| + puts hit.title + puts hit.author +end +``` + +#### Backend Pagination + +We support both [kaminari](https://github.com/amatsuda/kaminari) and [will_paginate](https://github.com/mislav/will_paginate). + +To use `:kaminari`, specify the `:pagination_backend` in the configuration file: + +```ruby +MeiliSearch.configuration = { + meilisearch_host: 'YourMeiliSearchHost', + meilisearch_api_key: 'YourMeiliSearchAPIKey', + pagination_backend: :kaminari +} +``` + +Then, as soon as you use the `search` method, the returning results will be paginated: + +```ruby +# controller +@hits = Book.search('harry potter') + + +# views +@hits.each do |hit| + puts hit.title + puts hit.author +end + +<%= paginate @hits %> # if using kaminari + +<%= will_paginate @hits %> # if using will_paginate +``` + +The **number of hits per page defaults to 20**, you can customize by adding the `hitsPerPage` parameter to your search: + +```ruby +Book.search('harry potter', hitsPerPage: 10) +``` + +## ⚙️ Settings + +You can configure the index settings by adding them inside the meilisearch block as shown bellow: + +```ruby +class Book < ApplicationRecord + include MeiliSearch + + meilisearch do + searchableAttributes ['title', 'author', 'publisher', 'description'] + attributesForFaceting ['genre'] + rankingRules [ + "proximity", + "typo", + "words", + "attribute", + "wordsPosition", + "exactness", + "desc(publication_year)" + ] + attributesToHighlight ['*'] + attributesToCrop ['description'] + cropLength 10 + synonyms got: ['game of thrones'] + end +end +``` + +Check the dedicated section of the documentation, for more information on the [settings](https://docs.meilisearch.com/reference/features/settings.html). + +🎁 We have added the possibility to use the following search parameters `attributesToHighlight`, `attributesToCrop`, `cropLength` as settings (see example above). + + +## 🔍 Custom search + +All the supported options are described in the [search parameters](https://docs.meilisearch.com/reference/features/search_parameters.html) section of the documentation. + +```ruby +Book.search('Harry', { filters: 'author = J. K. Rowling' }) +``` +👉 Don't forget that `attributesToHighlight`, `attributesToCrop`, `cropLength` can be set up as settings in the MeiliSearch block of your model. + + +## 🪛 Options From b48cc22abdfb753f118e99fe0e9010d43fe9efa4 Mon Sep 17 00:00:00 2001 From: CaroFG Date: Wed, 2 Jun 2021 13:49:33 +0200 Subject: [PATCH 02/14] Correct typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45048156..743c05a4 100644 --- a/README.md +++ b/README.md @@ -145,7 +145,7 @@ end <%= will_paginate @hits %> # if using will_paginate ``` -The **number of hits per page defaults to 20**, you can customize by adding the `hitsPerPage` parameter to your search: +The **number of hits per page defaults to 20**, you can customize it by adding the `hitsPerPage` parameter to your search: ```ruby Book.search('harry potter', hitsPerPage: 10) From 6cd9b7e787a616938dd3a55a7e1cadfc0912ebf5 Mon Sep 17 00:00:00 2001 From: CaroFG Date: Wed, 2 Jun 2021 13:50:00 +0200 Subject: [PATCH 03/14] Correct typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 743c05a4..0879646f 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ Book.search('harry potter', hitsPerPage: 10) ## ⚙️ Settings -You can configure the index settings by adding them inside the meilisearch block as shown bellow: +You can configure the index settings by adding them inside the meilisearch block as shown below: ```ruby class Book < ApplicationRecord From c1bb9bbbca7cfdfd8313e1a139b039f3bdca76c2 Mon Sep 17 00:00:00 2001 From: CaroFG Date: Wed, 2 Jun 2021 13:51:36 +0200 Subject: [PATCH 04/14] Add missing word --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0879646f..8b09b901 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ All the supported options are described in the [search parameters](https://docs. ```ruby Book.search('Harry', { filters: 'author = J. K. Rowling' }) ``` -👉 Don't forget that `attributesToHighlight`, `attributesToCrop`, `cropLength` can be set up as settings in the MeiliSearch block of your model. +👉 Don't forget that `attributesToHighlight`, `attributesToCrop` and `cropLength` can be set up as settings in the MeiliSearch block of your model. ## 🪛 Options From f675a38f70e54ac3f6fc8503525c5ce1c545974e Mon Sep 17 00:00:00 2001 From: CaroFG Date: Wed, 2 Jun 2021 13:54:02 +0200 Subject: [PATCH 05/14] Correct code example --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b09b901..419db123 100644 --- a/README.md +++ b/README.md @@ -136,8 +136,8 @@ Then, as soon as you use the `search` method, the returning results will be pagi # views @hits.each do |hit| - puts hit.title - puts hit.author + <%= hit.title %> + <%= hit.author %> end <%= paginate @hits %> # if using kaminari From b5bda70511296dca046cc546c169e392f482fb74 Mon Sep 17 00:00:00 2001 From: CaroFG Date: Wed, 2 Jun 2021 13:55:12 +0200 Subject: [PATCH 06/14] Correct code example --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 419db123..47b84b61 100644 --- a/README.md +++ b/README.md @@ -135,10 +135,10 @@ Then, as soon as you use the `search` method, the returning results will be pagi # views -@hits.each do |hit| + <% @hits.each do |hit| %> <%= hit.title %> <%= hit.author %> -end +<% end %> <%= paginate @hits %> # if using kaminari From 2139090c3494b25c51bd7bf29ef206c408b950c9 Mon Sep 17 00:00:00 2001 From: CaroFG Date: Wed, 2 Jun 2021 18:49:37 +0200 Subject: [PATCH 07/14] Update README --- README.md | 303 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 301 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 47b84b61..858b9691 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,32 @@ - [📖 Documentation](#-documentation) - [🔧 Installation](#-installation) -- [🚀 Getting Started](#-getting-started) -- [⚙️ Settings](#-settings) +- [🔩 Settings](#-settings) - [🔍 Custom search](#-custom-search) - [🪛 Options](#-options) + - [MeiliSearch configuration & environment](#meilisearch-configuration-&-environment) + - [Custom index_uid](#custom-index_uid) + - [Per-environment index_uid](#per-environment-index_uid) + - [Index configuration](#index-configuration) + - [Custom attribute definition](#custom-attribute-definition) + - [Custom primary key](#custom-primary-key) + - [Conditional indexing](#conditional-indexing) + - [Target multiple indices](#target-multiple-indices) + - [Share a single index](#share-a-single-index) + - [Queues & background jobs](#queues-&-background-jobs) + - [Sanitize attributes](#sanitize-attributes) + - [UTF-8 encoding](#utf-8-encoding) + - [Manual operations](#manual-operations) + - [Indexing & deletion](#indexing-&-deletion) + - [Access the underlying index object](#access-the-underlying-index-object) + - [Best practices](#best-practices) + - [Exceptions](#exceptions) + - [Testing](#testing) + - [Synchronous testing](#synchronous-testing) + - [Disable auto-indexing & auto-removal](#disable-auto-indexing-&-auto-removal) +- [🤖 Compatibility with MeiliSearch](#-compatibility-with-meilisearch) +- [⚙️ Development Workflow and Contributing](#️-development-workflow-and-contributing) +- [👏 Credits](#-credits) ## 📖 Documentation @@ -93,6 +115,7 @@ class Book < ActiveRecord::Base include MeiliSearch meilisearch do + attribute :title, :author # only the attributes 'title', and 'author' will be sent to MeiliSearch # all attributes will be sent to MeiliSearch if block is left empty end end @@ -195,3 +218,279 @@ Book.search('Harry', { filters: 'author = J. K. Rowling' }) ## 🪛 Options + +### MeiliSearch configuration & environment + +#### Custom index_uid + +By default, the **index_uid** will be the class name, e.g. `Book`. You can customize the index_uid by using the `index_uid` option + +```ruby +class Book < ActiveRecord::Base + include MeiliSearch + meilisearch :index_uid => 'MyCustomUID' do + end +end +``` + +#### Per-environment index_uid + +You can suffix the index_uid with the current Rails environment using the following option: + +```ruby +class Book < ActiveRecord::Base + include MeiliSearch + meilisearch per_environment: true do # index name will be "Book_#{Rails.env}" + end +end +``` + +### Index configuration + +#### Custom attribute definition + +You can add a custom attribute by using the `add_attribute` option or by using a block. + +⚠️ 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. + +```ruby +class Author < ApplicationRecord + include MeiliSearch + + meilisearch do + attribute :first_name, :last_name + attribute :full_name do + '#{first_name} #{last_name}' + end + add_attribute :full_name_reversed + end + + def full_name_reversed + '#{last_name} #{first_name}' + end + + def will_save_change_to_full_name? + will_save_change_to_first_name? || will_save_change_to_last_name? + end + + def will_save_change_to_full_name_reversed? + will_save_change_to_first_name? || will_save_change_to_last_name? + end +end +``` + +#### Custom primary key +By default, the `primary key` is based on your record's id. You can change this behavior specifying the `:primary_key` option. + +Note that the primary key must have a **unique value**. + +```ruby +class Book < ActiveRecord::Base + include MeiliSearch + meilisearch :primary_key => 'ISBN' do + end +end +``` +#### Conditional indexing + +You can control if a record must be indexed by using the :if or :unless options +As soon as you use those constraints, add_documents and delete_dpcuments 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. + +```ruby +class Book < ActiveRecord::Base + include MeiliSearch + meilisearch :if published? :unless premium? do + end + + def published? + # [...] + end + + def premium? + # [...] + end + + def will_save_change_to_published? + # return true only if you know that the 'published' state changed + end +end +``` + ##### Target multiple indices + You can index a record in several indexes using the `add_index` option: + ```ruby + class Book < ActiveRecord::Base + + include MeiliSearch + + PUBLIC_INDEX_UID = 'Books' + SECURED_INDEX_UID = 'PrivateBooks' + + # store all books in index 'SECURED_INDEX_UID' + meilisearch index_uid: SECURED_INDEX_UID do + searchableAttributes [:title, :author] + + # store all 'public' (released and not premium) books in index 'PUBLIC_INDEX_UID' + add_index PUBLIC_INDEX_UID, if: :public? do + searchableAttributes [:title, :author] + end + end + + private + def public? + released && !premium + end + +end + ``` +#### Share a single index +You 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. + +```ruby +class Cat < ActiveRecord::Base + include MeiliSearch + + meilisearch :index_name => 'Animals', primary_key: :ms_id do + end + + private + def ms_id + "cat_#{primary_key}" # ensure the cats & dogs primary_keys are not conflicting + end +end + +class Dog < ActiveRecord::Base + include MeiliSearch + + meilisearch :index_name => 'Animals', primary_key: :ms_id do + end + + private + def ms_id + "dog_#{primary_key}" # ensure the cats & dogs primary_keys are not conflicting + end +end +``` +#### Queues & background jobs + +You can configure the auto-indexing & 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: + +```ruby +class Book < ActiveRecord::Base + include MeiliSearch + + meilisearch enqueue: true do # ActiveJob will be triggered using a `meilisearch` queue +end +``` + +#### Nested objects/relations +#### Sanitize attributes + +You can strip all HTML tags from your attributes with the `sanitize` option. + +```ruby +class Book < ActiveRecord::Base + include MeiliSearch + + meilisearch :sanitize => true do +end +``` +#### UTF-8 encoding + +You can force the UTF-8 encoding of all your attributes using the `force_utf8_encoding` option. + +```ruby +class Book < ActiveRecord::Base + include MeiliSearch + + meilisearch :force_utf8_encoding => true do +end +``` + +### Manual operations + +#### Manual indexing & deleting + +You can manually index a record by using the `index!` instance method and remove it by using the `remove_from_index!` instance method + +```ruby + book = Book.create!(title: 'The Little Prince', author: 'Antoine de Saint-Exupéry') + book.index! + book.remove_from_index! + book.destroy! +``` + +To reindex all your records, use the `reindex!` class method: + +```ruby + Book.reindex! + + # You can also index a subset of your records + Book.where('updated_at > ?', 10.minutes.ago).reindex! +``` + +To delete all your records, use the `clear_index!` class method + +```ruby + Book.clear_index! +``` + +#### Access the underlying index object + +To access the index object and use the meilisearch-ruby index methods, call the `ìndex` class method: + +```ruby + index = Book.index + # index.get_settings, index.number_of_documents +``` + +### Best practices / Code samples +#### Exceptions + +You can disable exceptions that could be raised while trying to reach MeiliSearch's API by using the `raise_on_failure` option: + +```ruby +class Book < ActiveRecord::Base + include MeiliSearch + + # only raise exceptions in development environment + meilisearch :raise_on_failure => Rails.env.development? do +end +``` + +#### Testing + ##### Synchronous testing + You can force indexing and removing to be synchronous by setting the following option: + + ```ruby + class Book < ActiveRecord::Base + include MeiliSearch + + meilisearch synchronous: true do + end + ``` + 🚨 This is only recommended for testing purposes, the gem will call the `wait_for_pending_update` method that will stop your code execution until the asynchronous task has been processed by MeilSearch. + + ##### Disable auto-indexing & auto-removal + + You can disable auto-indexing and auto-removing setting the following options: + + ```ruby + class Book < ActiveRecord::Base + include MeiliSearch + + meilisearch auto_index: false, auto_remove: false do + end + ``` + + You can temporarily disable auto-indexing using the without_auto_index scope: + + ```ruby + Book.without_auto_index do + 1.upto(10000) { Book.create! attributes } # inside this block, auto indexing task will not run. + end + ``` + + +## Compatibility with MeiliSearch +## Development workflow & contributing +## Credits From c5fde10f7219c13001d104b13c7786f5955a76be Mon Sep 17 00:00:00 2001 From: CaroFG Date: Thu, 3 Jun 2021 15:31:55 +0200 Subject: [PATCH 08/14] Add delayed jobs and relations --- README.md | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 858b9691..503a2da1 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ - [Target multiple indices](#target-multiple-indices) - [Share a single index](#share-a-single-index) - [Queues & background jobs](#queues-&-background-jobs) + - [Relations](#relations) - [Sanitize attributes](#sanitize-attributes) - [UTF-8 encoding](#utf-8-encoding) - [Manual operations](#manual-operations) @@ -379,10 +380,159 @@ class Book < ActiveRecord::Base include MeiliSearch meilisearch enqueue: true do # ActiveJob will be triggered using a `meilisearch` queue + end +end +``` + +🤔 If you are performing updates & deletions in the background then 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. + +In this case you can bypass loading the record from **ActiveRecord** and just communicate with the index directly: + +```ruby +class MyActiveJob < ApplicationJob + def perform(id, remove) + if remove + # the record has likely already been removed from your database so we cannot + # use ActiveRecord#find to load it + # We access the underlying MeiliSearch index object + Book.index.delete_document(id) + else + # the record should be present + c = Book.find(id) + c.index! + end + end +end +``` + +With [**Sidekiq**](https://github.com/mperham/sidekiq) + +```ruby +class Book < ActiveRecord::Base + include MeiliSearch + + algoliasearch enqueue: :trigger_sidekiq_worker do + attribute :title, :author, :description + end + + def self.trigger_sidekiq_worker(record, remove) + MySidekiqWorker.perform_async(record.id, remove) + end +end + +class MySidekiqWorker + def perform(id, remove) + if remove + # the record has likely already been removed from your database so we cannot + # use ActiveRecord#find to load it + # We access the underlying MeiliSearch index object + index = Book.index.delete_document(id) + else + # the record should be present + c = Contact.find(id) + c.index! + end + end +end +``` + +With [**DelayedJob**](https://github.com/collectiveidea/delayed_job) + +```ruby +class Book < ActiveRecord::Base + include MeiliSearch + + algoliasearch enqueue: :trigger_delayed_job do + attribute :title, :author, :description + end + + + def self.trigger_delayed_job(record, remove) + if remove + record.delay.remove_from_index! + else + record.delay.index! + end + end +end +``` + +#### Relations + +Extend a change to a related record + +**With Active Record**, you'll need to use `touch` and `after_touch`. + +```ruby +class Author < ActiveRecord::Base + include MeiliSearch + + has_many :books + # If your association uses belongs_to + # - use `touch: true` + # - do not define an `after_save` hook + after_save { books.each(&:touch) } end + +class Book < ActiveRecord::Base + include MeiliSearch + + belongs_to :author + after_touch :index! + + meilisearch do + attribute :title, :description, :publisher + attribute :author do + author.name + end + end +end + ``` -#### Nested objects/relations +With **Sequel**, you can use the `touch plugin` to propagate changes. + +```ruby +# app/models/author.rb +class Author < Sequel::Model + include MeiliSearch + + one_to_many :books + + plugin :timestamps + # Can't use the associations since it won't trigger the after_save + plugin :touch + + # Define the associations that need to be touched here + # Less performant, but allows for the after_save hook to be triggered + def touch_associations + apps.map(&:touch) + end + + def touch + super + touch_associations + end +end + +# app/models/book.rb +class Book < Sequel::Model + include MeiliSearch + + many_to_one :author + after_touch :index! + + plugin :timestamps + plugin :touch + + meilisearch do + attribute :title, :description, :publisher + attribute :author do + author.name + end + end +end +``` #### Sanitize attributes You can strip all HTML tags from your attributes with the `sanitize` option. From a12885c725ea4c3f1979d3a1bbd8d8a51dd73479 Mon Sep 17 00:00:00 2001 From: CaroFG Date: Thu, 3 Jun 2021 20:23:42 +0200 Subject: [PATCH 09/14] Update README --- README.md | 115 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 67 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 503a2da1..32a171dd 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ - [Custom attribute definition](#custom-attribute-definition) - [Custom primary key](#custom-primary-key) - [Conditional indexing](#conditional-indexing) - - [Target multiple indices](#target-multiple-indices) + - [Target multiple indexes](#target-multiple-indexes) - [Share a single index](#share-a-single-index) - [Queues & background jobs](#queues-&-background-jobs) - [Relations](#relations) @@ -181,25 +181,25 @@ You can configure the index settings by adding them inside the meilisearch block ```ruby class Book < ApplicationRecord - include MeiliSearch + include MeiliSearch - meilisearch do - searchableAttributes ['title', 'author', 'publisher', 'description'] - attributesForFaceting ['genre'] - rankingRules [ - "proximity", - "typo", - "words", - "attribute", - "wordsPosition", - "exactness", - "desc(publication_year)" - ] - attributesToHighlight ['*'] - attributesToCrop ['description'] - cropLength 10 - synonyms got: ['game of thrones'] - end + meilisearch do + searchableAttributes ['title', 'author', 'publisher', 'description'] + attributesForFaceting ['genre'] + rankingRules [ + "proximity", + "typo", + "words", + "attribute", + "wordsPosition", + "exactness", + "desc(publication_year)" + ] + attributesToHighlight ['*'] + attributesToCrop ['description'] + cropLength 10 + synonyms got: ['game of thrones'] + end end ``` @@ -256,27 +256,27 @@ You can add a custom attribute by using the `add_attribute` option or by using a ```ruby class Author < ApplicationRecord - include MeiliSearch + include MeiliSearch - meilisearch do - attribute :first_name, :last_name - attribute :full_name do - '#{first_name} #{last_name}' - end - add_attribute :full_name_reversed + meilisearch do + attribute :first_name, :last_name + attribute :full_name do + '#{first_name} #{last_name}' end + add_attribute :full_name_reversed + end - def full_name_reversed - '#{last_name} #{first_name}' - end + def full_name_reversed + '#{last_name} #{first_name}' + end - def will_save_change_to_full_name? - will_save_change_to_first_name? || will_save_change_to_last_name? - end + def will_save_change_to_full_name? + will_save_change_to_first_name? || will_save_change_to_last_name? + end - def will_save_change_to_full_name_reversed? - will_save_change_to_first_name? || will_save_change_to_last_name? - end + def will_save_change_to_full_name_reversed? + will_save_change_to_first_name? || will_save_change_to_last_name? + end end ``` @@ -300,7 +300,8 @@ As soon as you use those constraints, add_documents and delete_dpcuments calls w ```ruby class Book < ActiveRecord::Base include MeiliSearch - meilisearch :if published? :unless premium? do + + meilisearch :if published?, :unless premium? do end def published? @@ -316,7 +317,7 @@ class Book < ActiveRecord::Base end end ``` - ##### Target multiple indices + ##### Target multiple indexes You can index a record in several indexes using the `add_index` option: ```ruby class Book < ActiveRecord::Base @@ -350,7 +351,7 @@ You may want to share an index between several models. You'll need to ensure you class Cat < ActiveRecord::Base include MeiliSearch - meilisearch :index_name => 'Animals', primary_key: :ms_id do + meilisearch index_uid: 'Animals', primary_key: :ms_id do end private @@ -362,7 +363,7 @@ end class Dog < ActiveRecord::Base include MeiliSearch - meilisearch :index_name => 'Animals', primary_key: :ms_id do + meilisearch index_uid: 'Animals', primary_key: :ms_id do end private @@ -384,9 +385,9 @@ class Book < ActiveRecord::Base end ``` -🤔 If you are performing updates & deletions in the background then 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. +🤔 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. -In this case you can bypass loading the record from **ActiveRecord** and just communicate with the index directly: +In this case you can bypass loading the record from **ActiveRecord** and just communicate with the index directly. ```ruby class MyActiveJob < ApplicationJob @@ -411,7 +412,7 @@ With [**Sidekiq**](https://github.com/mperham/sidekiq) class Book < ActiveRecord::Base include MeiliSearch - algoliasearch enqueue: :trigger_sidekiq_worker do + meilisearch enqueue: :trigger_sidekiq_worker do attribute :title, :author, :description end @@ -442,7 +443,7 @@ With [**DelayedJob**](https://github.com/collectiveidea/delayed_job) class Book < ActiveRecord::Base include MeiliSearch - algoliasearch enqueue: :trigger_delayed_job do + meilisearch enqueue: :trigger_delayed_job do attribute :title, :author, :description end @@ -542,6 +543,7 @@ class Book < ActiveRecord::Base include MeiliSearch meilisearch :sanitize => true do + end end ``` #### UTF-8 encoding @@ -553,12 +555,13 @@ class Book < ActiveRecord::Base include MeiliSearch meilisearch :force_utf8_encoding => true do + end end ``` ### Manual operations -#### Manual indexing & deleting +#### Indexing & deletion You can manually index a record by using the `index!` instance method and remove it by using the `remove_from_index!` instance method @@ -593,7 +596,7 @@ To access the index object and use the meilisearch-ruby index methods, call the # index.get_settings, index.number_of_documents ``` -### Best practices / Code samples +### Best practices #### Exceptions You can disable exceptions that could be raised while trying to reach MeiliSearch's API by using the `raise_on_failure` option: @@ -604,6 +607,7 @@ class Book < ActiveRecord::Base # only raise exceptions in development environment meilisearch :raise_on_failure => Rails.env.development? do + end end ``` @@ -616,6 +620,7 @@ end include MeiliSearch meilisearch synchronous: true do + end end ``` 🚨 This is only recommended for testing purposes, the gem will call the `wait_for_pending_update` method that will stop your code execution until the asynchronous task has been processed by MeilSearch. @@ -629,6 +634,7 @@ end include MeiliSearch meilisearch auto_index: false, auto_remove: false do + end end ``` @@ -640,7 +646,20 @@ end end ``` +## 🤖 Compatibility with MeiliSearch + +This package only guarantees the compatibility with the [version v0.20.0 of MeiliSearch](https://github.com/meilisearch/MeiliSearch/releases/tag/v0.20.0). + +## ⚙️ Development workflow & contributing + +Any new contribution is more than welcome in this project! + +If you want to know more about the development workflow or want to contribute, please visit our [contributing guidelines](/CONTRIBUTING.md) for detailed instructions! + +## 👏 Credits + +The provided features and the code base is inspired by [algoliasearch-rails](https://github.com/algolia/algoliasearch-rails/) + +
-## Compatibility with MeiliSearch -## Development workflow & contributing -## Credits +**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. From 8ae5925839944067baaa7f986c479266749546d5 Mon Sep 17 00:00:00 2001 From: CaroFG Date: Mon, 7 Jun 2021 17:04:29 +0200 Subject: [PATCH 10/14] Modify pagination section --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 32a171dd..25fcbde1 100644 --- a/README.md +++ b/README.md @@ -141,13 +141,13 @@ end We support both [kaminari](https://github.com/amatsuda/kaminari) and [will_paginate](https://github.com/mislav/will_paginate). -To use `:kaminari`, specify the `:pagination_backend` in the configuration file: +Specify the `:pagination_backend` in the configuration file: ```ruby MeiliSearch.configuration = { meilisearch_host: 'YourMeiliSearchHost', meilisearch_api_key: 'YourMeiliSearchAPIKey', - pagination_backend: :kaminari + pagination_backend: :kaminari #:will_paginate } ``` @@ -159,7 +159,7 @@ Then, as soon as you use the `search` method, the returning results will be pagi # views - <% @hits.each do |hit| %> +<% @hits.each do |hit| %> <%= hit.title %> <%= hit.author %> <% end %> From 79e2a54e043e6106e4cebe1e4efa3bccc42e64e5 Mon Sep 17 00:00:00 2001 From: CaroFG Date: Mon, 7 Jun 2021 18:12:03 +0200 Subject: [PATCH 11/14] Update README --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 25fcbde1..ae1ccb63 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ - [Manual operations](#manual-operations) - [Indexing & deletion](#indexing-&-deletion) - [Access the underlying index object](#access-the-underlying-index-object) - - [Best practices](#best-practices) + - [Development & testing](#development-&-testing) - [Exceptions](#exceptions) - [Testing](#testing) - [Synchronous testing](#synchronous-testing) @@ -294,8 +294,8 @@ end ``` #### Conditional indexing -You can control if a record must be indexed by using the :if or :unless options -As soon as you use those constraints, add_documents and delete_dpcuments 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. +You can control if a record must be indexed by using the `:if` or `:unless` options +As 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. ```ruby class Book < ActiveRecord::Base @@ -385,7 +385,7 @@ class Book < ActiveRecord::Base end ``` -🤔 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. +🤔 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`. In this case you can bypass loading the record from **ActiveRecord** and just communicate with the index directly. @@ -426,7 +426,7 @@ class MySidekiqWorker if remove # the record has likely already been removed from your database so we cannot # use ActiveRecord#find to load it - # We access the underlying MeiliSearch index object + # We access the underlying MeiliSearch index object index = Book.index.delete_document(id) else # the record should be present @@ -589,14 +589,14 @@ To delete all your records, use the `clear_index!` class method #### Access the underlying index object -To access the index object and use the meilisearch-ruby index methods, call the `ìndex` class method: +To access the index object and use the meilisearch-ruby index methods, call the `index` class method: ```ruby index = Book.index # index.get_settings, index.number_of_documents ``` -### Best practices +### Development & testing #### Exceptions You can disable exceptions that could be raised while trying to reach MeiliSearch's API by using the `raise_on_failure` option: From 794c91b563a92b2c6284b9f88e19859652d68a40 Mon Sep 17 00:00:00 2001 From: CaroFG Date: Wed, 9 Jun 2021 17:05:45 +0200 Subject: [PATCH 12/14] Update README --- README.md | 90 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index ae1ccb63..175e3990 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ ## Table of Contents - [📖 Documentation](#-documentation) +- [🤖 Compatibility with MeiliSearch](#-compatibility-with-meilisearch) - [🔧 Installation](#-installation) - [🔩 Settings](#-settings) - [🔍 Custom search](#-custom-search) @@ -53,13 +54,19 @@ - [Testing](#testing) - [Synchronous testing](#synchronous-testing) - [Disable auto-indexing & auto-removal](#disable-auto-indexing-&-auto-removal) -- [🤖 Compatibility with MeiliSearch](#-compatibility-with-meilisearch) - [⚙️ Development Workflow and Contributing](#️-development-workflow-and-contributing) - [👏 Credits](#-credits) ## 📖 Documentation -See our [Documentation](https://docs.meilisearch.com/learn/tutorials/getting_started.html) or our [API References](https://docs.meilisearch.com/reference/api/). +The whole usage of this gem is detailed in this README. + +To learn more about MeiliSearch, check out our [Documentation](https://docs.meilisearch.com/learn/tutorials/getting_started.html) or our [API References](https://docs.meilisearch.com/reference/api/). + + +## 🤖 Compatibility with MeiliSearch + +This package only guarantees the compatibility with the [version v0.20.0 of MeiliSearch](https://github.com/meilisearch/MeiliSearch/releases/tag/v0.20.0). ## 🔧 Installation @@ -97,7 +104,6 @@ NB: you can also download MeiliSearch from **Homebrew** or **APT**. Create a new file `config/initializers/meilisearch.rb` to setup your `MEILISEARCH_HOST` and `MEILISEARCH_API_KEY` ```ruby - MeiliSearch.configuration = { meilisearch_host: 'YourMeiliSearchHost', meilisearch_api_key: 'YourMeiliSearchAPIKey', @@ -109,7 +115,7 @@ The gem is compatible with [ActiveRecord](https://github.com/rails/rails/tree/ma #### Add documents -The following code will create a `Book` index and add search capabilities to your `Book` model +The following code will create a `Book` index and add search capabilities to your `Book` model. ```ruby class Book < ActiveRecord::Base @@ -124,7 +130,7 @@ end #### Basic Backend Search -We **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) +We **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) Search returns ORM-compliant objects reloaded from your database. @@ -157,7 +163,6 @@ Then, as soon as you use the `search` method, the returning results will be pagi # controller @hits = Book.search('harry potter') - # views <% @hits.each do |hit| %> <%= hit.title %> @@ -566,25 +571,25 @@ end You can manually index a record by using the `index!` instance method and remove it by using the `remove_from_index!` instance method ```ruby - book = Book.create!(title: 'The Little Prince', author: 'Antoine de Saint-Exupéry') - book.index! - book.remove_from_index! - book.destroy! +book = Book.create!(title: 'The Little Prince', author: 'Antoine de Saint-Exupéry') +book.index! +book.remove_from_index! +book.destroy! ``` To reindex all your records, use the `reindex!` class method: ```ruby - Book.reindex! +Book.reindex! - # You can also index a subset of your records - Book.where('updated_at > ?', 10.minutes.ago).reindex! +# You can also index a subset of your records +Book.where('updated_at > ?', 10.minutes.ago).reindex! ``` To delete all your records, use the `clear_index!` class method ```ruby - Book.clear_index! +Book.clear_index! ``` #### Access the underlying index object @@ -592,11 +597,12 @@ To delete all your records, use the `clear_index!` class method To access the index object and use the meilisearch-ruby index methods, call the `index` class method: ```ruby - index = Book.index - # index.get_settings, index.number_of_documents +index = Book.index +# index.get_settings, index.number_of_documents ``` ### Development & testing + #### Exceptions You can disable exceptions that could be raised while trying to reach MeiliSearch's API by using the `raise_on_failure` option: @@ -612,43 +618,39 @@ end ``` #### Testing - ##### Synchronous testing - You can force indexing and removing to be synchronous by setting the following option: +##### Synchronous testing +You can force indexing and removing to be synchronous by setting the following option: - ```ruby - class Book < ActiveRecord::Base - include MeiliSearch +```ruby +class Book < ActiveRecord::Base + include MeiliSearch - meilisearch synchronous: true do - end + meilisearch synchronous: true do end - ``` - 🚨 This is only recommended for testing purposes, the gem will call the `wait_for_pending_update` method that will stop your code execution until the asynchronous task has been processed by MeilSearch. - - ##### Disable auto-indexing & auto-removal - - You can disable auto-indexing and auto-removing setting the following options: +end +``` +🚨 This is only recommended for testing purposes, the gem will call the `wait_for_pending_update` method that will stop your code execution until the asynchronous task has been processed by MeilSearch. - ```ruby - class Book < ActiveRecord::Base - include MeiliSearch +##### Disable auto-indexing & auto-removal - meilisearch auto_index: false, auto_remove: false do - end - end - ``` +You can disable auto-indexing and auto-removing setting the following options: - You can temporarily disable auto-indexing using the without_auto_index scope: +```ruby +class Book < ActiveRecord::Base + include MeiliSearch - ```ruby - Book.without_auto_index do - 1.upto(10000) { Book.create! attributes } # inside this block, auto indexing task will not run. + meilisearch auto_index: false, auto_remove: false do end - ``` +end +``` -## 🤖 Compatibility with MeiliSearch +You can temporarily disable auto-indexing using the without_auto_index scope: -This package only guarantees the compatibility with the [version v0.20.0 of MeiliSearch](https://github.com/meilisearch/MeiliSearch/releases/tag/v0.20.0). +```ruby +Book.without_auto_index do + 1.upto(10000) { Book.create! attributes } # inside this block, auto indexing task will not run. +end +``` ## ⚙️ Development workflow & contributing @@ -658,7 +660,7 @@ If you want to know more about the development workflow or want to contribute, p ## 👏 Credits -The provided features and the code base is inspired by [algoliasearch-rails](https://github.com/algolia/algoliasearch-rails/) +The provided features and the code base is inspired by [algoliasearch-rails](https://github.com/algolia/algoliasearch-rails/).
From 6207a3b3be13c224d1d2d004e2cd38efc2a3583a Mon Sep 17 00:00:00 2001 From: CaroFG <48251481+CaroFG@users.noreply.github.com> Date: Wed, 9 Jun 2021 17:58:07 +0200 Subject: [PATCH 13/14] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clémentine Urquizar --- README.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 175e3990..82d15344 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,6 @@ The whole usage of this gem is detailed in this README. To learn more about MeiliSearch, check out our [Documentation](https://docs.meilisearch.com/learn/tutorials/getting_started.html) or our [API References](https://docs.meilisearch.com/reference/api/). - ## 🤖 Compatibility with MeiliSearch This package only guarantees the compatibility with the [version v0.20.0 of MeiliSearch](https://github.com/meilisearch/MeiliSearch/releases/tag/v0.20.0). @@ -112,7 +111,6 @@ MeiliSearch.configuration = { The 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). - #### Add documents The following code will create a `Book` index and add search capabilities to your `Book` model. @@ -182,7 +180,7 @@ Book.search('harry potter', hitsPerPage: 10) ## ⚙️ Settings -You can configure the index settings by adding them inside the meilisearch block as shown below: +You can configure the index settings by adding them inside the `meilisearch` block as shown below: ```ruby class Book < ApplicationRecord @@ -200,19 +198,17 @@ class Book < ApplicationRecord "exactness", "desc(publication_year)" ] + synonyms nyc: ['new york'] + // The following parameters are applied when calling the search() method: attributesToHighlight ['*'] attributesToCrop ['description'] cropLength 10 - synonyms got: ['game of thrones'] end end ``` Check the dedicated section of the documentation, for more information on the [settings](https://docs.meilisearch.com/reference/features/settings.html). -🎁 We have added the possibility to use the following search parameters `attributesToHighlight`, `attributesToCrop`, `cropLength` as settings (see example above). - - ## 🔍 Custom search All the supported options are described in the [search parameters](https://docs.meilisearch.com/reference/features/search_parameters.html) section of the documentation. @@ -220,8 +216,7 @@ All the supported options are described in the [search parameters](https://docs. ```ruby Book.search('Harry', { filters: 'author = J. K. Rowling' }) ``` -👉 Don't forget that `attributesToHighlight`, `attributesToCrop` and `cropLength` can be set up as settings in the MeiliSearch block of your model. - +👉 Don't forget that `attributesToHighlight`, `attributesToCrop`, and `cropLength` can be set up in the `meilisearch` block of your model. ## 🪛 Options @@ -229,7 +224,7 @@ Book.search('Harry', { filters: 'author = J. K. Rowling' }) #### Custom index_uid -By default, the **index_uid** will be the class name, e.g. `Book`. You can customize the index_uid by using the `index_uid` option +By default, the **index_uid** will be the class name, e.g. `Book`. You can customize the index_uid by using the `index_uid` option. ```ruby class Book < ActiveRecord::Base @@ -239,14 +234,14 @@ class Book < ActiveRecord::Base end ``` -#### Per-environment index_uid +#### Index UID according to the environment -You can suffix the index_uid with the current Rails environment using the following option: +You can suffix the index UID with the current Rails environment using the following option: ```ruby class Book < ActiveRecord::Base include MeiliSearch - meilisearch per_environment: true do # index name will be "Book_#{Rails.env}" + meilisearch per_environment: true do # The index UID will be "Book_#{Rails.env}" end end ``` @@ -286,6 +281,7 @@ end ``` #### Custom primary key + By default, the `primary key` is based on your record's id. You can change this behavior specifying the `:primary_key` option. Note that the primary key must have a **unique value**. @@ -299,8 +295,8 @@ end ``` #### Conditional indexing -You can control if a record must be indexed by using the `:if` or `:unless` options -As 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. +You can control if a record must be indexed by using the `:if` or `:unless` options.
+As 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. ```ruby class Book < ActiveRecord::Base @@ -349,8 +345,10 @@ end end ``` + #### Share a single index -You 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. + +You 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. ```ruby class Cat < ActiveRecord::Base @@ -377,6 +375,7 @@ class Dog < ActiveRecord::Base end end ``` + #### Queues & background jobs You can configure the auto-indexing & 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: @@ -411,7 +410,7 @@ class MyActiveJob < ApplicationJob end ``` -With [**Sidekiq**](https://github.com/mperham/sidekiq) +With [**Sidekiq**](https://github.com/mperham/sidekiq): ```ruby class Book < ActiveRecord::Base @@ -442,7 +441,7 @@ class MySidekiqWorker end ``` -With [**DelayedJob**](https://github.com/collectiveidea/delayed_job) +With [**DelayedJob**](https://github.com/collectiveidea/delayed_job): ```ruby class Book < ActiveRecord::Base @@ -452,7 +451,6 @@ class Book < ActiveRecord::Base attribute :title, :author, :description end - def self.trigger_delayed_job(record, remove) if remove record.delay.remove_from_index! @@ -465,7 +463,7 @@ end #### Relations -Extend a change to a related record +Extend a change to a related record. **With Active Record**, you'll need to use `touch` and `after_touch`. @@ -493,10 +491,9 @@ class Book < ActiveRecord::Base end end end - ``` -With **Sequel**, you can use the `touch plugin` to propagate changes. +With **Sequel**, you can use the `touch` plugin to propagate changes. ```ruby # app/models/author.rb @@ -539,6 +536,7 @@ class Book < Sequel::Model end end ``` + #### Sanitize attributes You can strip all HTML tags from your attributes with the `sanitize` option. @@ -551,6 +549,7 @@ class Book < ActiveRecord::Base end end ``` + #### UTF-8 encoding You can force the UTF-8 encoding of all your attributes using the `force_utf8_encoding` option. @@ -586,7 +585,7 @@ Book.reindex! Book.where('updated_at > ?', 10.minutes.ago).reindex! ``` -To delete all your records, use the `clear_index!` class method +To delete all your records, use the `clear_index!` class method: ```ruby Book.clear_index! @@ -594,7 +593,7 @@ Book.clear_index! #### Access the underlying index object -To access the index object and use the meilisearch-ruby index methods, call the `index` class method: +To access the index object and use the [Ruby SDK](https://github.com/meilisearch/meilisearch-ruby) methods for an index, call the `index` class method: ```ruby index = Book.index @@ -603,6 +602,7 @@ index = Book.index ### Development & testing + #### Exceptions You can disable exceptions that could be raised while trying to reach MeiliSearch's API by using the `raise_on_failure` option: From 3ff3bb471ac43fd895c96918da5d4b74ccb66219 Mon Sep 17 00:00:00 2001 From: CaroFG Date: Wed, 9 Jun 2021 18:00:21 +0200 Subject: [PATCH 14/14] Correct indentation --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 82d15344..fb587cda 100644 --- a/README.md +++ b/README.md @@ -318,10 +318,12 @@ class Book < ActiveRecord::Base end end ``` - ##### Target multiple indexes - You can index a record in several indexes using the `add_index` option: - ```ruby - class Book < ActiveRecord::Base +##### Target multiple indexes + +You can index a record in several indexes using the `add_index` option: + +```ruby +class Book < ActiveRecord::Base include MeiliSearch @@ -342,9 +344,8 @@ end def public? released && !premium end - end - ``` +``` #### Share a single index