Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ permissions:

jobs:
test:
name: test (typesense ${{ matrix.typesense-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
typesense-version: ['28.0', '30.1']

env:
BUNDLE_JOBS: 4
Expand All @@ -16,7 +21,7 @@ jobs:

services:
typesense:
image: typesense/typesense:28.0
image: typesense/typesense:${{ matrix.typesense-version }}
ports:
- 8108:8108
volumes:
Expand Down
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
source "http://rubygems.org"

gem 'json', '>= 1.5.1'
gem "typesense", "~> 0.13.0"
gem "typesense", ">= 5.0.0", "< 6.0.0"


if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ Special thanks to the Algolia team for their original implementation, which prov
- Environment-specific indexing
- Support for faceted search and filtering
- Customizable schema with predefined fields
- Multi-way and one-way synonyms support
- Multi-way and one-way synonyms support across Typesense v29 and v30+
- Direct attachment of v30+ synonym sets and curation sets
- Rake tasks for index management

## Installation
Expand Down Expand Up @@ -91,6 +92,10 @@ class Product < ApplicationRecord
}
]

# Attach existing global resources on Typesense v30+
synonym_sets ["catalog-synonyms"]
curation_sets ["catalog-curations"]

# Symbols to index
symbols_to_index ["-", "_"]

Expand All @@ -103,6 +108,9 @@ class Product < ApplicationRecord
end
```

For Typesense v29, `multi_way_synonyms` and `one_way_synonyms` continue to use the legacy collection-level synonym APIs.
For Typesense v30 and newer, this gem stores those DSL synonyms in a collection-specific synonym set and attaches it automatically, while also supporting explicit `synonym_sets` / `curation_sets`.

### Working with Relationships

```ruby
Expand Down
118 changes: 113 additions & 5 deletions lib/typesense-rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class IndexSettings
OPTIONS = [
:multi_way_synonyms,
:one_way_synonyms,
:synonym_sets,
:curation_sets,
:predefined_fields,
:fields,
:default_sorting_field,
Expand Down Expand Up @@ -271,11 +273,13 @@ def collection_name_with_timestamp(options)
"#{typesense_collection_name(options)}_#{Time.now.to_i}"
end

def typesense_create_collection(collection_name, settings = nil)
def typesense_create_collection(collection_name, settings = nil, existing_collection: nil)
fields = settings.get_setting(:predefined_fields) || settings.get_setting(:fields)
default_sorting_field = settings.get_setting(:default_sorting_field)
multi_way_synonyms = settings.get_setting(:multi_way_synonyms)
one_way_synonyms = settings.get_setting(:one_way_synonyms)
synonym_sets = settings.get_setting(:synonym_sets)
curation_sets = settings.get_setting(:curation_sets)
symbols_to_index = settings.get_setting(:symbols_to_index)
token_separators = settings.get_setting(:token_separators)
enable_nested_fields = settings.get_setting(:enable_nested_fields)
Expand All @@ -300,9 +304,14 @@ def typesense_create_collection(collection_name, settings = nil)
)
Typesense.log(:debug, "Collection '#{collection_name}' created!")

typesense_multi_way_synonyms(collection_name, multi_way_synonyms) if multi_way_synonyms

typesense_one_way_synonyms(collection_name, one_way_synonyms) if one_way_synonyms
apply_typesense_collection_resources(
collection_name,
multi_way_synonyms: multi_way_synonyms,
one_way_synonyms: one_way_synonyms,
synonym_sets: synonym_sets,
curation_sets: curation_sets,
existing_collection: existing_collection
)
end

def typesense_upsert_alias(collection_name, alias_name)
Expand Down Expand Up @@ -385,6 +394,103 @@ def typesense_one_way_synonyms(collection, synonyms)
end
end

def apply_typesense_collection_resources(collection_name, multi_way_synonyms: nil, one_way_synonyms: nil, synonym_sets: nil, curation_sets: nil, existing_collection: nil)
if typesense_server_major_version >= 30
apply_v30_collection_resources(
collection_name,
multi_way_synonyms: multi_way_synonyms,
one_way_synonyms: one_way_synonyms,
synonym_sets: synonym_sets,
curation_sets: curation_sets,
existing_collection: existing_collection
)
else
ensure_v30_resource_support!(synonym_sets, curation_sets)
typesense_multi_way_synonyms(collection_name, multi_way_synonyms) if multi_way_synonyms
typesense_one_way_synonyms(collection_name, one_way_synonyms) if one_way_synonyms
end
end

def apply_v30_collection_resources(collection_name, multi_way_synonyms: nil, one_way_synonyms: nil, synonym_sets: nil, curation_sets: nil, existing_collection: nil)
inline_synonyms_present = multi_way_synonyms || one_way_synonyms
attached_synonym_sets = Array(existing_collection && existing_collection["synonym_sets"]) + Array(synonym_sets)
attached_curation_sets = Array(existing_collection && existing_collection["curation_sets"]) + Array(curation_sets)

if inline_synonyms_present
synonym_set_name = default_synonym_set_name(collection_name)
ensure_synonym_set_exists(synonym_set_name)
upsert_synonym_set_items(synonym_set_name, multi_way_synonyms, one_way_synonyms)
attached_synonym_sets << synonym_set_name
end

collection_patch = {}
collection_patch["synonym_sets"] = attached_synonym_sets.uniq if attached_synonym_sets.any?
collection_patch["curation_sets"] = attached_curation_sets.uniq if attached_curation_sets.any?

return if collection_patch.empty?

typesense_client.collections[collection_name].update(collection_patch)
end

def ensure_v30_resource_support!(synonym_sets, curation_sets)
unsupported = []
unsupported << "synonym_sets" if synonym_sets
unsupported << "curation_sets" if curation_sets
return if unsupported.empty?

raise Typesense::BadConfiguration, "#{unsupported.join(' and ')} require Typesense v30.0 or newer"
end

def default_synonym_set_name(collection_name)
"#{collection_name}_synonyms_index"
end

def ensure_synonym_set_exists(synonym_set_name)
typesense_client.synonym_sets.upsert(synonym_set_name, { "items" => [] })
end

def upsert_synonym_set_items(synonym_set_name, multi_way_synonyms, one_way_synonyms)
items = []

Array(multi_way_synonyms).each do |synonym_hash|
synonym_hash.each do |synonym_name, synonym|
items << { "id" => synonym_name, "synonyms" => synonym }
end
end

Array(one_way_synonyms).each do |synonym_hash|
synonym_hash.each do |synonym_name, synonym|
items << synonym.merge("id" => synonym_name)
end
end

typesense_client.synonym_sets.upsert(synonym_set_name, { "items" => items })
end

def typesense_server_major_version
Typesense.server_major_version
end

def reset_typesense_server_major_version!
Typesense.reset_server_version_cache!
end

def typesense_debug_info
Typesense.debug_info
end

def typesense_collection_resources(collection_name)
return {} if typesense_server_major_version < 30

collection = typesense_get_collection(collection_name)
return {} unless collection

{
"synonym_sets" => Array(collection["synonym_sets"]),
"curation_sets" => Array(collection["curation_sets"])
}
end

def typesense(options = {}, &block)
self.typesense_settings = IndexSettings.new(options, &block)
self.typesense_options = { type: typesense_full_const_get(model_name.to_s) }.merge(options) # :per_page => typesense_settings.get_setting(:hitsPerPage) || 10, :page => 1
Expand Down Expand Up @@ -528,8 +634,10 @@ def typesense_reindex(batch_size = Typesense::IndexSettings::DEFAULT_BATCH_SIZE)
typesense_configurations.each do |options, settings|
next if typesense_indexing_disabled?(options)

existing_collection_resources = {}
begin
master_index = typesense_ensure_init(options, settings, false)
existing_collection_resources = typesense_collection_resources(master_index[:alias_name])
delete_collection(master_index[:alias_name])
rescue ArgumentError
@typesense_indexes[settings] = { collection_name: "", alias_name: typesense_collection_name(options) }
Expand All @@ -542,7 +650,7 @@ def typesense_reindex(batch_size = Typesense::IndexSettings::DEFAULT_BATCH_SIZE)
tmp_options.delete(:per_environment) # already included in the temporary index_name
tmp_settings = settings.dup

create_collection(src_index_name, settings)
create_collection(src_index_name, settings, existing_collection: existing_collection_resources)

typesense_find_in_batches(batch_size) do |group|
if typesense_conditional_index?(options)
Expand Down
18 changes: 18 additions & 0 deletions lib/typesense/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ def configuration=(configuration)
@@pagination_backend = configuration[:pagination_backend] if configuration.key?(:pagination_backend)
@@log_level = configuration[:log_level] if configuration.key?(:log_level)
@@configuration = configuration
@client = nil
reset_server_version_cache!
end

def pagination_backend
Expand Down Expand Up @@ -66,5 +68,21 @@ def client
def setup_client
@client = Typesense::Client.new(@@configuration)
end

def server_major_version
@server_major_version ||= begin
version = debug_info.fetch("version", "")
version == "nightly" ? 30 : version.split(".").first.to_i
end
end

def debug_info
@debug_info ||= client.debug.retrieve
end

def reset_server_version_cache!
@server_major_version = nil
@debug_info = nil
end
end
end
2 changes: 1 addition & 1 deletion lib/typesense/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Typesense
GEM_VERSION = "1.0.0.rc5"
GEM_VERSION = "1.0.0.rc6"
end
Loading