Class: TypesenseModel::Base
- Inherits:
-
Object
- Object
- TypesenseModel::Base
- Defined in:
- lib/typesense_model/base.rb
Direct Known Subclasses
Class Attribute Summary collapse
-
._collection_name ⇒ Object
Returns the value of attribute _collection_name.
-
._schema_definition ⇒ Object
Returns the value of attribute _schema_definition.
Instance Attribute Summary collapse
-
#attributes ⇒ Object
Returns the value of attribute attributes.
Class Method Summary collapse
-
.collection_exists? ⇒ Boolean
Check if collection exists.
- .collection_name(name = nil) ⇒ Object
-
.collection_stats ⇒ Object
Get collection stats.
-
.count ⇒ Object
Get number of documents in collection.
- .create(attributes = {}) ⇒ Object
-
.create_collection(force = false) ⇒ Object
Create the collection in Typesense.
-
.create_or_update_collection ⇒ Object
Create or update collection.
-
.default_query_by ⇒ Object
Comma-separated list of indexed string fields (excluding id) used as the default ‘query_by` when a search doesn’t specify one.
- .define_schema(&block) ⇒ Object
-
.delete(id) ⇒ Object
Delete a record by ID.
-
.delete_by(filter_by) ⇒ Object
Delete multiple records by query.
-
.delete_collection ⇒ Object
Delete the collection from Typesense.
- .delete_override(id) ⇒ Object
- .delete_synonym(id) ⇒ Object
- .find(id) ⇒ Object
-
.import(documents, options = {}) ⇒ Hash
Import multiple records.
-
.import_from_model(model_class, batch_size, transform_method = :as_json, preloads = nil, import_options = {}) ⇒ Hash
Import records from an ActiveRecord model.
-
.multi_search(searches, common_params = {}) ⇒ Array<SearchResults>
Perform several searches in a single request.
- .overrides ⇒ Object
-
.retrieve_collection ⇒ Object
Retrieve collection details.
- .schema_definition ⇒ Object
- .search(query, options = {}) ⇒ Object
- .synonyms ⇒ Object
-
.update_collection ⇒ Object
Update the collection schema in Typesense to match the model schema.
-
.upsert_override(id, override) ⇒ Object
— Overrides (curation) —————————————— Thin wrappers over the Typesense overrides API for this collection.
-
.upsert_synonym(id, synonym) ⇒ Object
— Synonyms ——————————————————- Thin wrappers over the Typesense synonyms API for this collection.
Instance Method Summary collapse
-
#delete ⇒ Object
Delete the current record from Typesense.
- #id ⇒ Object
-
#initialize(attributes = {}) ⇒ Base
constructor
A new instance of Base.
- #method_missing(method_name, *args) ⇒ Object
- #respond_to_missing?(method_name, include_private = false) ⇒ Boolean
- #save ⇒ Object
Constructor Details
#initialize(attributes = {}) ⇒ Base
Returns a new instance of Base.
329 330 331 |
# File 'lib/typesense_model/base.rb', line 329 def initialize(attributes = {}) @attributes = attributes.transform_keys(&:to_s) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method_name, *args) ⇒ Object
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/typesense_model/base.rb', line 353 def method_missing(method_name, *args) attribute_name = method_name.to_s # Handle setters (e.g., name=) if attribute_name.end_with?('=') attribute_name = attribute_name.chop # Remove the '=' from the end return set_attribute(attribute_name, args.first) end # Handle getters (e.g., name) if attributes.key?(attribute_name) return attributes[attribute_name] end nil end |
Class Attribute Details
._collection_name ⇒ Object
Returns the value of attribute _collection_name.
6 7 8 |
# File 'lib/typesense_model/base.rb', line 6 def _collection_name @_collection_name end |
._schema_definition ⇒ Object
Returns the value of attribute _schema_definition.
6 7 8 |
# File 'lib/typesense_model/base.rb', line 6 def _schema_definition @_schema_definition end |
Instance Attribute Details
#attributes ⇒ Object
Returns the value of attribute attributes.
327 328 329 |
# File 'lib/typesense_model/base.rb', line 327 def attributes @attributes end |
Class Method Details
.collection_exists? ⇒ Boolean
Check if collection exists
91 92 93 94 95 96 |
# File 'lib/typesense_model/base.rb', line 91 def collection_exists? client.collections[collection_name].retrieve true rescue Typesense::Error::ObjectNotFound false end |
.collection_name(name = nil) ⇒ Object
8 9 10 11 12 13 14 |
# File 'lib/typesense_model/base.rb', line 8 def collection_name(name = nil) if name @_collection_name = name else @_collection_name ||= self.name.underscore.pluralize end end |
.collection_stats ⇒ Object
Get collection stats
156 157 158 159 |
# File 'lib/typesense_model/base.rb', line 156 def collection_stats return nil unless collection_exists? client.collections[collection_name].stats end |
.count ⇒ Object
Get number of documents in collection
162 163 164 |
# File 'lib/typesense_model/base.rb', line 162 def count collection_stats&.dig('num_documents') || 0 end |
.create(attributes = {}) ⇒ Object
25 26 27 |
# File 'lib/typesense_model/base.rb', line 25 def create(attributes = {}) new(attributes).save end |
.create_collection(force = false) ⇒ Object
Create the collection in Typesense
74 75 76 77 78 79 80 81 82 83 |
# File 'lib/typesense_model/base.rb', line 74 def create_collection(force = false) delete_collection if force return if collection_exists? schema = schema_definition.to_hash.merge( name: collection_name ) client.collections.create(schema) end |
.create_or_update_collection ⇒ Object
Create or update collection
145 146 147 |
# File 'lib/typesense_model/base.rb', line 145 def create_or_update_collection collection_exists? ? update_collection : create_collection end |
.default_query_by ⇒ Object
Comma-separated list of indexed string fields (excluding id) used as the default ‘query_by` when a search doesn’t specify one.
64 65 66 67 68 69 70 71 |
# File 'lib/typesense_model/base.rb', line 64 def default_query_by return '' unless schema_definition schema_definition.fields .select { |f| f[:index] && f[:type] == 'string' && f[:name] != 'id' } .map { |f| f[:name] } .join(',') end |
.define_schema(&block) ⇒ Object
16 17 18 19 |
# File 'lib/typesense_model/base.rb', line 16 def define_schema(&block) @_schema_definition = Schema.new @_schema_definition.instance_eval(&block) end |
.delete(id) ⇒ Object
Delete a record by ID
227 228 229 230 231 232 233 |
# File 'lib/typesense_model/base.rb', line 227 def delete(id) client.collections[collection_name] .documents[id] .delete rescue Typesense::Error::ObjectNotFound false end |
.delete_by(filter_by) ⇒ Object
Delete multiple records by query. Returns the Typesense response (e.g. { “num_deleted” => N }); when the collection is missing, returns { “num_deleted” => 0 } for symmetry with the singular #delete.
238 239 240 241 242 243 244 |
# File 'lib/typesense_model/base.rb', line 238 def delete_by(filter_by) client.collections[collection_name] .documents .delete({ filter_by: filter_by }) rescue Typesense::Error::ObjectNotFound { "num_deleted" => 0 } end |
.delete_collection ⇒ Object
Delete the collection from Typesense
86 87 88 |
# File 'lib/typesense_model/base.rb', line 86 def delete_collection client.collections[collection_name].delete if collection_exists? end |
.delete_override(id) ⇒ Object
272 273 274 275 276 |
# File 'lib/typesense_model/base.rb', line 272 def delete_override(id) client.collections[collection_name].overrides[id].delete rescue Typesense::Error::ObjectNotFound false end |
.delete_synonym(id) ⇒ Object
256 257 258 259 260 |
# File 'lib/typesense_model/base.rb', line 256 def delete_synonym(id) client.collections[collection_name].synonyms[id].delete rescue Typesense::Error::ObjectNotFound false end |
.find(id) ⇒ Object
29 30 31 32 33 34 |
# File 'lib/typesense_model/base.rb', line 29 def find(id) response = client.collections[collection_name].documents[id].retrieve new(response) rescue Typesense::Error::ObjectNotFound nil end |
.import(documents, options = {}) ⇒ Hash
Import multiple records
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/typesense_model/base.rb', line 168 def import(documents, = {}) sanitized_documents = Array(documents).map { |doc| sanitize_document(doc) } response = client.collections[collection_name] .documents .import(sanitized_documents, ) # Typesense returns one result hash per document. Guard against an # unexpected non-array shape (e.g. an error payload) so we never blow up # in the tally below. unless response.is_a?(Array) return { success: 0, failed: sanitized_documents.size, errors: [{ code: nil, error: "Unexpected import response: #{response.inspect}", document: nil }] } end results = response.each_with_object({ success: 0, failed: 0, errors: [] }) do |result, counts| if result['success'] counts[:success] += 1 else counts[:failed] += 1 counts[:errors] << { code: result['code'], error: result['error'], document: result['document'] } end end results end |
.import_from_model(model_class, batch_size, transform_method = :as_json, preloads = nil, import_options = {}) ⇒ Hash
Import records from an ActiveRecord model
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/typesense_model/base.rb', line 206 def import_from_model(model_class, batch_size, transform_method = :as_json, preloads = nil, = {}) total_results = { success: 0, failed: 0, errors: [] } transformer = transform_method.is_a?(Proc) ? transform_method : ->(record) { record.send(transform_method) } relation = model_class.all relation = relation.preload(preloads) if preloads relation.find_in_batches(batch_size: batch_size) do |batch| documents = batch.map(&transformer) results = import(documents, ) total_results[:success] += results[:success] total_results[:failed] += results[:failed] total_results[:errors].concat(results[:errors]) if results[:errors].is_a?(Array) end total_results end |
.multi_search(searches, common_params = {}) ⇒ Array<SearchResults>
Perform several searches in a single request.
48 49 50 51 52 53 54 55 56 57 58 59 60 |
# File 'lib/typesense_model/base.rb', line 48 def multi_search(searches, common_params = {}) payload = Array(searches).map do |params| params = params.transform_keys(&:to_sym) { collection: params[:collection] || collection_name, q: params[:q] || '*', query_by: params[:query_by] || default_query_by }.merge(params.except(:collection, :q, :query_by)) end response = client.multi_search.perform({ searches: payload }, common_params) (response['results'] || []).map { |result| SearchResults.new(result, self) } end |
.overrides ⇒ Object
268 269 270 |
# File 'lib/typesense_model/base.rb', line 268 def overrides client.collections[collection_name].overrides.retrieve end |
.retrieve_collection ⇒ Object
Retrieve collection details
150 151 152 153 |
# File 'lib/typesense_model/base.rb', line 150 def retrieve_collection return nil unless collection_exists? client.collections[collection_name].retrieve end |
.schema_definition ⇒ Object
21 22 23 |
# File 'lib/typesense_model/base.rb', line 21 def schema_definition @_schema_definition end |
.search(query, options = {}) ⇒ Object
36 37 38 |
# File 'lib/typesense_model/base.rb', line 36 def search(query, = {}) Search.new(self, query, ).execute end |
.synonyms ⇒ Object
252 253 254 |
# File 'lib/typesense_model/base.rb', line 252 def synonyms client.collections[collection_name].synonyms.retrieve end |
.update_collection ⇒ Object
Update the collection schema in Typesense to match the model schema.
Typesense only accepts a ‘fields` diff on update: a field can be added, or dropped (`drop: true`), and a change is expressed as a drop followed by a re-add. Re-sending an existing, unchanged field raises an error, so we diff the desired schema against the live collection and send only the additions, modifications, and removals. The implicit `id` field cannot be altered and is always skipped.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/typesense_model/base.rb', line 106 def update_collection return create_collection unless collection_exists? live_fields = (retrieve_collection&.dig('fields') || []).each_with_object({}) do |f, h| h[f['name'].to_s] = f end desired_fields = (schema_definition.to_hash[:fields] || []).reject do |f| (f[:name] || f['name']).to_s == 'id' end changes = [] desired_names = [] desired_fields.each do |field| name = (field[:name] || field['name']).to_s desired_names << name existing = live_fields[name] if existing.nil? changes << field elsif field_changed?(field, existing) changes << { 'name' => name, 'drop' => true } changes << field end end # Drop fields that exist in Typesense but are no longer in the schema. live_fields.each_key do |name| next if name == 'id' || name == '.*' changes << { 'name' => name, 'drop' => true } unless desired_names.include?(name) end return retrieve_collection if changes.empty? client.collections[collection_name].update(fields: changes) end |
.upsert_override(id, override) ⇒ Object
— Overrides (curation) —————————————— Thin wrappers over the Typesense overrides API for this collection.
264 265 266 |
# File 'lib/typesense_model/base.rb', line 264 def upsert_override(id, override) client.collections[collection_name].overrides.upsert(id, override) end |
.upsert_synonym(id, synonym) ⇒ Object
— Synonyms ——————————————————- Thin wrappers over the Typesense synonyms API for this collection.
248 249 250 |
# File 'lib/typesense_model/base.rb', line 248 def upsert_synonym(id, synonym) client.collections[collection_name].synonyms.upsert(id, synonym) end |
Instance Method Details
#delete ⇒ Object
Delete the current record from Typesense. Returns true if a document was removed, false if there was no id or nothing to delete.
346 347 348 349 350 351 |
# File 'lib/typesense_model/base.rb', line 346 def delete return false unless id response = self.class.delete(id) !response.nil? && response != false end |
#id ⇒ Object
340 341 342 |
# File 'lib/typesense_model/base.rb', line 340 def id attributes['id'] end |
#respond_to_missing?(method_name, include_private = false) ⇒ Boolean
370 371 372 373 374 375 |
# File 'lib/typesense_model/base.rb', line 370 def respond_to_missing?(method_name, include_private = false) attribute_name = method_name.to_s return true if attribute_name.end_with?('=') && attributes.key?(attribute_name.chop) return true if attributes.key?(attribute_name) super end |
#save ⇒ Object
333 334 335 336 337 338 |
# File 'lib/typesense_model/base.rb', line 333 def save response = self.class.send(:client).collections[self.class.collection_name].documents.upsert(attributes) @attributes = response.transform_keys(&:to_s) self end |