Skip to content

Commit bd18629

Browse files
committed
Move SafeIndex and IndexSettings to own files
1 parent ba4a8c9 commit bd18629

File tree

3 files changed

+303
-295
lines changed

3 files changed

+303
-295
lines changed

lib/meilisearch-rails.rb

Lines changed: 2 additions & 295 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
require 'meilisearch/rails/utilities'
55
require 'meilisearch/rails/errors'
66
require 'meilisearch/rails/multi_search'
7+
require 'meilisearch/rails/index_settings'
8+
require 'meilisearch/rails/safe_index'
79

810
if defined? Rails
911
begin
@@ -46,222 +48,6 @@ def logger
4648
end
4749
end
4850

49-
class IndexSettings
50-
DEFAULT_BATCH_SIZE = 1000
51-
52-
DEFAULT_PRIMARY_KEY = 'id'.freeze
53-
54-
# Meilisearch settings
55-
OPTIONS = %i[
56-
searchable_attributes
57-
filterable_attributes
58-
sortable_attributes
59-
displayed_attributes
60-
distinct_attribute
61-
synonyms
62-
stop_words
63-
ranking_rules
64-
attributes_to_highlight
65-
attributes_to_crop
66-
crop_length
67-
pagination
68-
faceting
69-
typo_tolerance
70-
proximity_precision
71-
].freeze
72-
73-
CAMELIZE_OPTIONS = %i[pagination faceting typo_tolerance].freeze
74-
75-
OPTIONS.each do |option|
76-
define_method option do |value|
77-
instance_variable_set("@#{option}", value)
78-
end
79-
80-
underscored_name = option.to_s.gsub(/(.)([A-Z])/, '\1_\2').downcase
81-
alias_method underscored_name, option if underscored_name != option
82-
end
83-
84-
def initialize(options, &block)
85-
@options = options
86-
instance_exec(&block) if block_given?
87-
warn_searchable_missing_attributes
88-
end
89-
90-
def warn_searchable_missing_attributes
91-
searchables = get_setting(:searchable_attributes)&.map { |searchable| searchable.to_s.split('.').first }
92-
attrs = get_setting(:attributes)&.map { |k, _| k.to_s }
93-
94-
if searchables.present? && attrs.present?
95-
(searchables - attrs).each do |missing_searchable|
96-
warning = <<~WARNING
97-
[meilisearch-rails] #{missing_searchable} declared in searchable_attributes but not in attributes. \
98-
Please add it to attributes if it should be searchable.
99-
WARNING
100-
MeiliSearch::Rails.logger.warn(warning)
101-
end
102-
end
103-
end
104-
105-
def use_serializer(serializer)
106-
@serializer = serializer
107-
# instance_variable_set("@serializer", serializer)
108-
end
109-
110-
def attribute(*names, &block)
111-
raise ArgumentError, 'Cannot pass multiple attribute names if block given' if block_given? && (names.length > 1)
112-
113-
@attributes ||= {}
114-
names.flatten.each do |name|
115-
@attributes[name.to_s] = block_given? ? proc { |d| d.instance_eval(&block) } : proc { |d| d.send(name) }
116-
end
117-
end
118-
alias attributes attribute
119-
120-
def add_attribute(*names, &block)
121-
raise ArgumentError, 'Cannot pass multiple attribute names if block given' if block_given? && (names.length > 1)
122-
123-
@additional_attributes ||= {}
124-
names.each do |name|
125-
@additional_attributes[name.to_s] = block_given? ? proc { |d| d.instance_eval(&block) } : proc { |d| d.send(name) }
126-
end
127-
end
128-
alias add_attributes add_attribute
129-
130-
def mongoid?(document)
131-
defined?(::Mongoid::Document) && document.class.include?(::Mongoid::Document)
132-
end
133-
134-
def sequel?(document)
135-
defined?(::Sequel::Model) && document.class < ::Sequel::Model
136-
end
137-
138-
def active_record?(document)
139-
!mongoid?(document) && !sequel?(document)
140-
end
141-
142-
def get_default_attributes(document)
143-
if mongoid?(document)
144-
# work-around mongoid 2.4's unscoped method, not accepting a block
145-
document.attributes
146-
elsif sequel?(document)
147-
document.to_hash
148-
else
149-
document.class.unscoped do
150-
document.attributes
151-
end
152-
end
153-
end
154-
155-
def get_attribute_names(document)
156-
get_attributes(document).keys
157-
end
158-
159-
def attributes_to_hash(attributes, document)
160-
if attributes
161-
attributes.to_h { |name, value| [name.to_s, value.call(document)] }
162-
else
163-
{}
164-
end
165-
end
166-
167-
def get_attributes(document)
168-
# If a serializer is set, we ignore attributes
169-
# everything should be done via the serializer
170-
if !@serializer.nil?
171-
attributes = @serializer.new(document).attributes
172-
elsif @attributes.blank?
173-
attributes = get_default_attributes(document)
174-
# no `attribute ...` have been configured, use the default attributes of the model
175-
elsif active_record?(document)
176-
# at least 1 `attribute ...` has been configured, therefore use ONLY the one configured
177-
document.class.unscoped do
178-
attributes = attributes_to_hash(@attributes, document)
179-
end
180-
else
181-
attributes = attributes_to_hash(@attributes, document)
182-
end
183-
184-
attributes.merge!(attributes_to_hash(@additional_attributes, document)) if @additional_attributes
185-
186-
if @options[:sanitize]
187-
attributes = sanitize_attributes(attributes)
188-
end
189-
190-
attributes = encode_attributes(attributes) if @options[:force_utf8_encoding]
191-
192-
attributes
193-
end
194-
195-
def sanitize_attributes(value)
196-
case value
197-
when String
198-
ActionView::Base.full_sanitizer.sanitize(value)
199-
when Hash
200-
value.each { |key, val| value[key] = sanitize_attributes(val) }
201-
when Array
202-
value.map { |item| sanitize_attributes(item) }
203-
else
204-
value
205-
end
206-
end
207-
208-
def encode_attributes(value)
209-
case value
210-
when String
211-
value.force_encoding('utf-8')
212-
when Hash
213-
value.each { |key, val| value[key] = encode_attributes(val) }
214-
when Array
215-
value.map { |x| encode_attributes(x) }
216-
else
217-
value
218-
end
219-
end
220-
221-
def get_setting(name)
222-
instance_variable_get("@#{name}")
223-
end
224-
225-
def camelize_keys(hash)
226-
hash.transform_keys { |key| key.to_s.camelize(:lower) }
227-
end
228-
229-
def to_settings
230-
settings = {}
231-
OPTIONS.each do |k|
232-
v = get_setting(k)
233-
next if v.nil?
234-
235-
settings[k] = if CAMELIZE_OPTIONS.include?(k) && v.is_a?(Hash)
236-
v = camelize_keys(v)
237-
238-
# camelize keys of nested hashes
239-
v.each do |key, value|
240-
v[key] = camelize_keys(value) if value.is_a?(Hash)
241-
end
242-
else
243-
v
244-
end
245-
end
246-
settings
247-
end
248-
249-
def add_index(index_uid, options = {}, &block)
250-
raise ArgumentError, 'No block given' unless block_given?
251-
if options[:auto_index] || options[:auto_remove]
252-
raise ArgumentError, 'Options auto_index and auto_remove cannot be set on nested indexes'
253-
end
254-
255-
@additional_indexes ||= {}
256-
options[:index_uid] = index_uid
257-
@additional_indexes[options] = IndexSettings.new(options, &block)
258-
end
259-
260-
def additional_indexes
261-
@additional_indexes || {}
262-
end
263-
end
264-
26551
# Default queueing system
26652
if defined?(::ActiveJob::Base)
26753
# lazy load the ActiveJob class to ensure the
@@ -270,85 +56,6 @@ def additional_indexes
27056
autoload :MSCleanUpJob, 'meilisearch/rails/ms_clean_up_job'
27157
end
27258

273-
# this class wraps an MeiliSearch::Index document ensuring all raised exceptions
274-
# are correctly logged or thrown depending on the `raise_on_failure` option
275-
class SafeIndex
276-
def initialize(index_uid, raise_on_failure, options)
277-
client = MeiliSearch::Rails.client
278-
primary_key = options[:primary_key] || MeiliSearch::Rails::IndexSettings::DEFAULT_PRIMARY_KEY
279-
@raise_on_failure = raise_on_failure.nil? || raise_on_failure
280-
281-
SafeIndex.log_or_throw(nil, @raise_on_failure) do
282-
client.create_index(index_uid, { primary_key: primary_key })
283-
end
284-
285-
@index = client.index(index_uid)
286-
end
287-
288-
::MeiliSearch::Index.instance_methods(false).each do |m|
289-
define_method(m) do |*args, &block|
290-
if m == :update_settings
291-
args[0].delete(:attributes_to_highlight) if args[0][:attributes_to_highlight]
292-
args[0].delete(:attributes_to_crop) if args[0][:attributes_to_crop]
293-
args[0].delete(:crop_length) if args[0][:crop_length]
294-
end
295-
296-
SafeIndex.log_or_throw(m, @raise_on_failure) do
297-
return MeiliSearch::Rails.black_hole unless MeiliSearch::Rails.active?
298-
299-
@index.send(m, *args, &block)
300-
end
301-
end
302-
end
303-
304-
# Maually define facet_search due to complications with **opts in ruby 2.*
305-
def facet_search(*args, **opts)
306-
SafeIndex.log_or_throw(:facet_search, @raise_on_failure) do
307-
return MeiliSearch::Rails.black_hole unless MeiliSearch::Rails.active?
308-
309-
@index.facet_search(*args, **opts)
310-
end
311-
end
312-
313-
# special handling of wait_for_task to handle null task_id
314-
def wait_for_task(task_uid)
315-
return if task_uid.nil? && !@raise_on_failure # ok
316-
317-
SafeIndex.log_or_throw(:wait_for_task, @raise_on_failure) do
318-
@index.wait_for_task(task_uid)
319-
end
320-
end
321-
322-
# special handling of settings to avoid raising errors on 404
323-
def settings(*args)
324-
SafeIndex.log_or_throw(:settings, @raise_on_failure) do
325-
@index.settings(*args)
326-
rescue ::MeiliSearch::ApiError => e
327-
return {} if e.code == 'index_not_found' # not fatal
328-
329-
raise e
330-
end
331-
end
332-
333-
def self.log_or_throw(method, raise_on_failure, &block)
334-
yield
335-
rescue ::MeiliSearch::TimeoutError, ::MeiliSearch::ApiError => e
336-
raise e if raise_on_failure
337-
338-
# log the error
339-
MeiliSearch::Rails.logger.info("[meilisearch-rails] #{e.message}")
340-
# return something
341-
case method.to_s
342-
when 'search'
343-
# some attributes are required
344-
{ 'hits' => [], 'hitsPerPage' => 0, 'page' => 0, 'facetDistribution' => {}, 'error' => e }
345-
else
346-
# empty answer
347-
{ 'error' => e }
348-
end
349-
end
350-
end
351-
35259
# these are the class methods added when MeiliSearch is included
35360
module ClassMethods
35461
def self.extended(base)

0 commit comments

Comments
 (0)