44require 'meilisearch/rails/utilities'
55require 'meilisearch/rails/errors'
66require 'meilisearch/rails/multi_search'
7+ require 'meilisearch/rails/index_settings'
8+ require 'meilisearch/rails/safe_index'
79
810if 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