Commit cdf7be657ba72418a7207dbf25f52f70bf435ba5
1 parent
c0c8a77e
Exists in
master
and in
21 other branches
Refactored Searchkick::Query#prepare
Showing
1 changed file
with
266 additions
and
238 deletions
Show diff stats
lib/searchkick/query.rb
... | ... | @@ -134,28 +134,7 @@ module Searchkick |
134 | 134 | end |
135 | 135 | |
136 | 136 | def prepare |
137 | - boost_fields = {} | |
138 | - fields = options[:fields] || searchkick_options[:searchable] | |
139 | - fields = | |
140 | - if fields | |
141 | - if options[:autocomplete] | |
142 | - fields.map { |f| "#{f}.autocomplete" } | |
143 | - else | |
144 | - fields.map do |value| | |
145 | - k, v = value.is_a?(Hash) ? value.to_a.first : [value, options[:match] || searchkick_options[:match] || :word] | |
146 | - k2, boost = k.to_s.split("^", 2) | |
147 | - field = "#{k2}.#{v == :word ? 'analyzed' : v}" | |
148 | - boost_fields[field] = boost.to_f if boost | |
149 | - field | |
150 | - end | |
151 | - end | |
152 | - else | |
153 | - if options[:autocomplete] | |
154 | - (searchkick_options[:autocomplete] || []).map { |f| "#{f}.autocomplete" } | |
155 | - else | |
156 | - ["_all"] | |
157 | - end | |
158 | - end | |
137 | + boost_fields, fields = set_fields | |
159 | 138 | |
160 | 139 | operator = options[:operator] || (options[:partial] ? "or" : "and") |
161 | 140 | |
... | ... | @@ -172,7 +151,7 @@ module Searchkick |
172 | 151 | personalize_field = searchkick_options[:personalize] |
173 | 152 | |
174 | 153 | all = term == "*" |
175 | - facet_limits = {} | |
154 | + | |
176 | 155 | |
177 | 156 | options[:json] ||= options[:body] |
178 | 157 | if options[:json] |
... | ... | @@ -309,52 +288,11 @@ module Searchkick |
309 | 288 | custom_filters = [] |
310 | 289 | multiply_filters = [] |
311 | 290 | |
312 | - boost_by = options[:boost_by] || {} | |
313 | - | |
314 | - if boost_by.is_a?(Array) | |
315 | - boost_by = Hash[boost_by.map { |f| [f, {factor: 1}] }] | |
316 | - elsif boost_by.is_a?(Hash) | |
317 | - multiply_by, boost_by = boost_by.partition { |_, v| v[:boost_mode] == "multiply" }.map { |i| Hash[i] } | |
318 | - end | |
319 | - boost_by[options[:boost]] = {factor: 1} if options[:boost] | |
320 | - | |
321 | - custom_filters.concat boost_filters(boost_by, log: true) | |
322 | - multiply_filters.concat boost_filters(multiply_by || {}) | |
291 | + set_boost_by(multiply_filters, custom_filters) | |
323 | 292 | |
324 | - boost_where = options[:boost_where] || {} | |
325 | - if options[:user_id] && personalize_field | |
326 | - boost_where[personalize_field] = options[:user_id] | |
327 | - end | |
328 | - if options[:personalize] | |
329 | - boost_where = boost_where.merge(options[:personalize]) | |
330 | - end | |
331 | - boost_where.each do |field, value| | |
332 | - if value.is_a?(Array) && value.first.is_a?(Hash) | |
333 | - value.each do |value_factor| | |
334 | - custom_filters << custom_filter(field, value_factor[:value], value_factor[:factor]) | |
335 | - end | |
336 | - elsif value.is_a?(Hash) | |
337 | - custom_filters << custom_filter(field, value[:value], value[:factor]) | |
338 | - else | |
339 | - factor = 1000 | |
340 | - custom_filters << custom_filter(field, value, factor) | |
341 | - end | |
342 | - end | |
293 | + set_boost_where(custom_filters, personalize_field) | |
343 | 294 | |
344 | - boost_by_distance = options[:boost_by_distance] | |
345 | - if boost_by_distance | |
346 | - boost_by_distance = {function: :gauss, scale: "5mi"}.merge(boost_by_distance) | |
347 | - if !boost_by_distance[:field] || !boost_by_distance[:origin] | |
348 | - raise ArgumentError, "boost_by_distance requires :field and :origin" | |
349 | - end | |
350 | - function_params = boost_by_distance.select { |k, _| [:origin, :scale, :offset, :decay].include?(k) } | |
351 | - function_params[:origin] = location_value(function_params[:origin]) | |
352 | - custom_filters << { | |
353 | - boost_by_distance[:function] => { | |
354 | - boost_by_distance[:field] => function_params | |
355 | - } | |
356 | - } | |
357 | - end | |
295 | + set_boost_by_distance(custom_filters) if options[:boost_by_distance] | |
358 | 296 | |
359 | 297 | if custom_filters.any? |
360 | 298 | payload = { |
... | ... | @@ -384,187 +322,24 @@ module Searchkick |
384 | 322 | payload[:explain] = options[:explain] if options[:explain] |
385 | 323 | |
386 | 324 | # order |
387 | - if options[:order] | |
388 | - order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc} | |
389 | - # TODO id transformation for arrays | |
390 | - payload[:sort] = order.is_a?(Array) ? order : Hash[order.map { |k, v| [k.to_s == "id" ? :_id : k, v] }] | |
391 | - end | |
325 | + set_order(payload) if options[:order] | |
392 | 326 | |
393 | 327 | # filters |
394 | 328 | filters = where_filters(options[:where]) |
395 | - if filters.any? | |
396 | - if options[:facets] || options[:aggs] | |
397 | - payload[:filter] = { | |
398 | - and: filters | |
399 | - } | |
400 | - else | |
401 | - # more efficient query if no facets | |
402 | - payload[:query] = { | |
403 | - filtered: { | |
404 | - query: payload[:query], | |
405 | - filter: { | |
406 | - and: filters | |
407 | - } | |
408 | - } | |
409 | - } | |
410 | - end | |
411 | - end | |
329 | + set_filters(payload, filters) if filters.any? | |
412 | 330 | |
413 | 331 | # facets |
414 | - if options[:facets] | |
415 | - facets = options[:facets] || {} | |
416 | - facets = Hash[facets.map { |f| [f, {}] }] if facets.is_a?(Array) # convert to more advanced syntax | |
417 | - | |
418 | - payload[:facets] = {} | |
419 | - facets.each do |field, facet_options| | |
420 | - # ask for extra facets due to | |
421 | - # https://github.com/elasticsearch/elasticsearch/issues/1305 | |
422 | - size = facet_options[:limit] ? facet_options[:limit] + 150 : 1_000 | |
423 | - | |
424 | - if facet_options[:ranges] | |
425 | - payload[:facets][field] = { | |
426 | - range: { | |
427 | - field.to_sym => facet_options[:ranges] | |
428 | - } | |
429 | - } | |
430 | - elsif facet_options[:stats] | |
431 | - payload[:facets][field] = { | |
432 | - terms_stats: { | |
433 | - key_field: field, | |
434 | - value_script: below14? ? "doc.score" : "_score", | |
435 | - size: size | |
436 | - } | |
437 | - } | |
438 | - else | |
439 | - payload[:facets][field] = { | |
440 | - terms: { | |
441 | - field: facet_options[:field] || field, | |
442 | - size: size | |
443 | - } | |
444 | - } | |
445 | - end | |
446 | - | |
447 | - facet_limits[field] = facet_options[:limit] if facet_options[:limit] | |
448 | - | |
449 | - # offset is not possible | |
450 | - # http://elasticsearch-users.115913.n3.nabble.com/Is-pagination-possible-in-termsStatsFacet-td3422943.html | |
451 | - | |
452 | - facet_options.deep_merge!(where: options.fetch(:where, {}).reject { |k| k == field }) if options[:smart_facets] == true | |
453 | - facet_filters = where_filters(facet_options[:where]) | |
454 | - if facet_filters.any? | |
455 | - payload[:facets][field][:facet_filter] = { | |
456 | - and: { | |
457 | - filters: facet_filters | |
458 | - } | |
459 | - } | |
460 | - end | |
461 | - end | |
462 | - end | |
332 | + set_facets(payload) if options[:facets] | |
463 | 333 | |
464 | 334 | # aggregations |
465 | - if options[:aggs] | |
466 | - aggs = options[:aggs] | |
467 | - payload[:aggs] = {} | |
468 | - | |
469 | - aggs = Hash[aggs.map { |f| [f, {}] }] if aggs.is_a?(Array) # convert to more advanced syntax | |
470 | - | |
471 | - aggs.each do |field, agg_options| | |
472 | - size = agg_options[:limit] ? agg_options[:limit] : 1_000 | |
473 | - shared_agg_options = agg_options.slice(:order) | |
474 | - | |
475 | - if agg_options[:ranges] | |
476 | - payload[:aggs][field] = { | |
477 | - range: { | |
478 | - field: agg_options[:field] || field, | |
479 | - ranges: agg_options[:ranges] | |
480 | - }.merge(shared_agg_options) | |
481 | - } | |
482 | - elsif agg_options[:date_ranges] | |
483 | - payload[:aggs][field] = { | |
484 | - date_range: { | |
485 | - field: agg_options[:field] || field, | |
486 | - ranges: agg_options[:date_ranges] | |
487 | - }.merge(shared_agg_options) | |
488 | - } | |
489 | - else | |
490 | - payload[:aggs][field] = { | |
491 | - terms: { | |
492 | - field: agg_options[:field] || field, | |
493 | - size: size | |
494 | - }.merge(shared_agg_options) | |
495 | - } | |
496 | - end | |
497 | - | |
498 | - where = {} | |
499 | - where = (options[:where] || {}).reject { |k| k == field } unless options[:smart_aggs] == false | |
500 | - agg_filters = where_filters(where.merge(agg_options[:where] || {})) | |
501 | - if agg_filters.any? | |
502 | - payload[:aggs][field] = { | |
503 | - filter: { | |
504 | - bool: { | |
505 | - must: agg_filters | |
506 | - } | |
507 | - }, | |
508 | - aggs: { | |
509 | - field => payload[:aggs][field] | |
510 | - } | |
511 | - } | |
512 | - end | |
513 | - end | |
514 | - end | |
515 | - | |
335 | + set_aggregations(payload) if options[:aggs] | |
336 | + | |
516 | 337 | # suggestions |
517 | - if options[:suggest] | |
518 | - suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s) | |
519 | - | |
520 | - # intersection | |
521 | - if options[:fields] | |
522 | - suggest_fields &= options[:fields].map { |v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first } | |
523 | - end | |
524 | - | |
525 | - if suggest_fields.any? | |
526 | - payload[:suggest] = {text: term} | |
527 | - suggest_fields.each do |field| | |
528 | - payload[:suggest][field] = { | |
529 | - phrase: { | |
530 | - field: "#{field}.suggest" | |
531 | - } | |
532 | - } | |
533 | - end | |
534 | - end | |
535 | - end | |
338 | + set_suggestions(payload) if options[:suggest] | |
536 | 339 | |
537 | 340 | # highlight |
538 | - if options[:highlight] | |
539 | - payload[:highlight] = { | |
540 | - fields: Hash[fields.map { |f| [f, {}] }] | |
541 | - } | |
542 | - | |
543 | - if options[:highlight].is_a?(Hash) | |
544 | - if (tag = options[:highlight][:tag]) | |
545 | - payload[:highlight][:pre_tags] = [tag] | |
546 | - payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")] | |
547 | - end | |
548 | - | |
549 | - if (fragment_size = options[:highlight][:fragment_size]) | |
550 | - payload[:highlight][:fragment_size] = fragment_size | |
551 | - end | |
552 | - if (encoder = options[:highlight][:encoder]) | |
553 | - payload[:highlight][:encoder] = encoder | |
554 | - end | |
555 | - | |
556 | - highlight_fields = options[:highlight][:fields] | |
557 | - if highlight_fields | |
558 | - payload[:highlight][:fields] = {} | |
559 | - | |
560 | - highlight_fields.each do |name, opts| | |
561 | - payload[:highlight][:fields]["#{name}.#{@match_suffix}"] = opts || {} | |
562 | - end | |
563 | - end | |
564 | - end | |
341 | + set_highlights(payload, fields) if options[:highlight] | |
565 | 342 | |
566 | - @highlighted_fields = payload[:highlight][:fields].keys | |
567 | - end | |
568 | 343 | |
569 | 344 | # An empty array will cause only the _id and _type for each hit to be returned |
570 | 345 | # doc for :select - http://www.elasticsearch.org/guide/reference/api/search/fields/ |
... | ... | @@ -591,13 +366,266 @@ module Searchkick |
591 | 366 | end |
592 | 367 | |
593 | 368 | @body = payload |
594 | - @facet_limits = facet_limits | |
369 | + @facet_limits = @facet_limits || {} | |
595 | 370 | @page = page |
596 | 371 | @per_page = per_page |
597 | 372 | @padding = padding |
598 | 373 | @load = load |
599 | 374 | end |
600 | 375 | |
376 | + def set_fields | |
377 | + boost_fields = {} | |
378 | + fields = options[:fields] || searchkick_options[:searchable] | |
379 | + fields = | |
380 | + if fields | |
381 | + if options[:autocomplete] | |
382 | + fields.map { |f| "#{f}.autocomplete" } | |
383 | + else | |
384 | + fields.map do |value| | |
385 | + k, v = value.is_a?(Hash) ? value.to_a.first : [value, options[:match] || searchkick_options[:match] || :word] | |
386 | + k2, boost = k.to_s.split("^", 2) | |
387 | + field = "#{k2}.#{v == :word ? 'analyzed' : v}" | |
388 | + boost_fields[field] = boost.to_f if boost | |
389 | + field | |
390 | + end | |
391 | + end | |
392 | + else | |
393 | + if options[:autocomplete] | |
394 | + (searchkick_options[:autocomplete] || []).map { |f| "#{f}.autocomplete" } | |
395 | + else | |
396 | + ["_all"] | |
397 | + end | |
398 | + end | |
399 | + [boost_fields, fields] | |
400 | + end | |
401 | + | |
402 | + def set_boost_by_distance(custom_filters) | |
403 | + boost_by_distance = options[:boost_by_distance] || {} | |
404 | + boost_by_distance = {function: :gauss, scale: "5mi"}.merge(boost_by_distance) | |
405 | + if !boost_by_distance[:field] || !boost_by_distance[:origin] | |
406 | + raise ArgumentError, "boost_by_distance requires :field and :origin" | |
407 | + end | |
408 | + function_params = boost_by_distance.select { |k, _| [:origin, :scale, :offset, :decay].include?(k) } | |
409 | + function_params[:origin] = location_value(function_params[:origin]) | |
410 | + custom_filters << { | |
411 | + boost_by_distance[:function] => { | |
412 | + boost_by_distance[:field] => function_params | |
413 | + } | |
414 | + } | |
415 | + end | |
416 | + | |
417 | + def set_boost_by(multiply_filters, custom_filters) | |
418 | + boost_by = options[:boost_by] || {} | |
419 | + if boost_by.is_a?(Array) | |
420 | + boost_by = Hash[boost_by.map { |f| [f, {factor: 1}] }] | |
421 | + elsif boost_by.is_a?(Hash) | |
422 | + multiply_by, boost_by = boost_by.partition { |_, v| v[:boost_mode] == "multiply" }.map { |i| Hash[i] } | |
423 | + end | |
424 | + boost_by[options[:boost]] = {factor: 1} if options[:boost] | |
425 | + | |
426 | + custom_filters.concat boost_filters(boost_by, log: true) | |
427 | + multiply_filters.concat boost_filters(multiply_by || {}) | |
428 | + end | |
429 | + | |
430 | + def set_boost_where(custom_filters, personalize_field) | |
431 | + boost_where = options[:boost_where] || {} | |
432 | + if options[:user_id] && personalize_field | |
433 | + boost_where[personalize_field] = options[:user_id] | |
434 | + end | |
435 | + if options[:personalize] | |
436 | + boost_where = boost_where.merge(options[:personalize]) | |
437 | + end | |
438 | + boost_where.each do |field, value| | |
439 | + if value.is_a?(Array) && value.first.is_a?(Hash) | |
440 | + value.each do |value_factor| | |
441 | + custom_filters << custom_filter(field, value_factor[:value], value_factor[:factor]) | |
442 | + end | |
443 | + elsif value.is_a?(Hash) | |
444 | + custom_filters << custom_filter(field, value[:value], value[:factor]) | |
445 | + else | |
446 | + factor = 1000 | |
447 | + custom_filters << custom_filter(field, value, factor) | |
448 | + end | |
449 | + end | |
450 | + end | |
451 | + | |
452 | + def set_suggestions(payload) | |
453 | + suggest_fields = (searchkick_options[:suggest] || []).map(&:to_s) | |
454 | + | |
455 | + # intersection | |
456 | + if options[:fields] | |
457 | + suggest_fields &= options[:fields].map { |v| (v.is_a?(Hash) ? v.keys.first : v).to_s.split("^", 2).first } | |
458 | + end | |
459 | + | |
460 | + if suggest_fields.any? | |
461 | + payload[:suggest] = {text: term} | |
462 | + suggest_fields.each do |field| | |
463 | + payload[:suggest][field] = { | |
464 | + phrase: { | |
465 | + field: "#{field}.suggest" | |
466 | + } | |
467 | + } | |
468 | + end | |
469 | + end | |
470 | + end | |
471 | + | |
472 | + def set_highlights(payload, fields) | |
473 | + payload[:highlight] = { | |
474 | + fields: Hash[fields.map { |f| [f, {}] }] | |
475 | + } | |
476 | + | |
477 | + if options[:highlight].is_a?(Hash) | |
478 | + if (tag = options[:highlight][:tag]) | |
479 | + payload[:highlight][:pre_tags] = [tag] | |
480 | + payload[:highlight][:post_tags] = [tag.to_s.gsub(/\A</, "</")] | |
481 | + end | |
482 | + | |
483 | + if (fragment_size = options[:highlight][:fragment_size]) | |
484 | + payload[:highlight][:fragment_size] = fragment_size | |
485 | + end | |
486 | + if (encoder = options[:highlight][:encoder]) | |
487 | + payload[:highlight][:encoder] = encoder | |
488 | + end | |
489 | + | |
490 | + highlight_fields = options[:highlight][:fields] | |
491 | + if highlight_fields | |
492 | + payload[:highlight][:fields] = {} | |
493 | + | |
494 | + highlight_fields.each do |name, opts| | |
495 | + payload[:highlight][:fields]["#{name}.#{@match_suffix}"] = opts || {} | |
496 | + end | |
497 | + end | |
498 | + end | |
499 | + | |
500 | + @highlighted_fields = payload[:highlight][:fields].keys | |
501 | + end | |
502 | + | |
503 | + def set_aggregations(payload) | |
504 | + aggs = options[:aggs] | |
505 | + payload[:aggs] = {} | |
506 | + | |
507 | + aggs = Hash[aggs.map { |f| [f, {}] }] if aggs.is_a?(Array) # convert to more advanced syntax | |
508 | + | |
509 | + aggs.each do |field, agg_options| | |
510 | + size = agg_options[:limit] ? agg_options[:limit] : 1_000 | |
511 | + shared_agg_options = agg_options.slice(:order) | |
512 | + | |
513 | + if agg_options[:ranges] | |
514 | + payload[:aggs][field] = { | |
515 | + range: { | |
516 | + field: agg_options[:field] || field, | |
517 | + ranges: agg_options[:ranges] | |
518 | + }.merge(shared_agg_options) | |
519 | + } | |
520 | + elsif agg_options[:date_ranges] | |
521 | + payload[:aggs][field] = { | |
522 | + date_range: { | |
523 | + field: agg_options[:field] || field, | |
524 | + ranges: agg_options[:date_ranges] | |
525 | + }.merge(shared_agg_options) | |
526 | + } | |
527 | + else | |
528 | + payload[:aggs][field] = { | |
529 | + terms: { | |
530 | + field: agg_options[:field] || field, | |
531 | + size: size | |
532 | + }.merge(shared_agg_options) | |
533 | + } | |
534 | + end | |
535 | + | |
536 | + where = {} | |
537 | + where = (options[:where] || {}).reject { |k| k == field } unless options[:smart_aggs] == false | |
538 | + agg_filters = where_filters(where.merge(agg_options[:where] || {})) | |
539 | + if agg_filters.any? | |
540 | + payload[:aggs][field] = { | |
541 | + filter: { | |
542 | + bool: { | |
543 | + must: agg_filters | |
544 | + } | |
545 | + }, | |
546 | + aggs: { | |
547 | + field => payload[:aggs][field] | |
548 | + } | |
549 | + } | |
550 | + end | |
551 | + end | |
552 | + end | |
553 | + | |
554 | + def set_facets(payload) | |
555 | + facets = options[:facets] || {} | |
556 | + facets = Hash[facets.map { |f| [f, {}] }] if facets.is_a?(Array) # convert to more advanced syntax | |
557 | + facet_limits = {} | |
558 | + payload[:facets] = {} | |
559 | + facets.each do |field, facet_options| | |
560 | + # ask for extra facets due to | |
561 | + # https://github.com/elasticsearch/elasticsearch/issues/1305 | |
562 | + size = facet_options[:limit] ? facet_options[:limit] + 150 : 1_000 | |
563 | + | |
564 | + if facet_options[:ranges] | |
565 | + payload[:facets][field] = { | |
566 | + range: { | |
567 | + field.to_sym => facet_options[:ranges] | |
568 | + } | |
569 | + } | |
570 | + elsif facet_options[:stats] | |
571 | + payload[:facets][field] = { | |
572 | + terms_stats: { | |
573 | + key_field: field, | |
574 | + value_script: below14? ? "doc.score" : "_score", | |
575 | + size: size | |
576 | + } | |
577 | + } | |
578 | + else | |
579 | + payload[:facets][field] = { | |
580 | + terms: { | |
581 | + field: facet_options[:field] || field, | |
582 | + size: size | |
583 | + } | |
584 | + } | |
585 | + end | |
586 | + | |
587 | + facet_limits[field] = facet_options[:limit] if facet_options[:limit] | |
588 | + | |
589 | + # offset is not possible | |
590 | + # http://elasticsearch-users.115913.n3.nabble.com/Is-pagination-possible-in-termsStatsFacet-td3422943.html | |
591 | + | |
592 | + facet_options.deep_merge!(where: options.fetch(:where, {}).reject { |k| k == field }) if options[:smart_facets] == true | |
593 | + facet_filters = where_filters(facet_options[:where]) | |
594 | + if facet_filters.any? | |
595 | + payload[:facets][field][:facet_filter] = { | |
596 | + and: { | |
597 | + filters: facet_filters | |
598 | + } | |
599 | + } | |
600 | + end | |
601 | + end | |
602 | + @facet_limits = facet_limits | |
603 | + end | |
604 | + | |
605 | + def set_filters(payload, filters) | |
606 | + if options[:facets] || options[:aggs] | |
607 | + payload[:filter] = { | |
608 | + and: filters | |
609 | + } | |
610 | + else | |
611 | + # more efficient query if no facets | |
612 | + payload[:query] = { | |
613 | + filtered: { | |
614 | + query: payload[:query], | |
615 | + filter: { | |
616 | + and: filters | |
617 | + } | |
618 | + } | |
619 | + } | |
620 | + end | |
621 | + end | |
622 | + | |
623 | + def set_order(payload) | |
624 | + order = options[:order].is_a?(Enumerable) ? options[:order] : {options[:order] => :asc} | |
625 | + # TODO id transformation for arrays | |
626 | + payload[:sort] = order.is_a?(Array) ? order : Hash[order.map { |k, v| [k.to_s == "id" ? :_id : k, v] }] | |
627 | + end | |
628 | + | |
601 | 629 | def where_filters(where) |
602 | 630 | filters = [] |
603 | 631 | (where || {}).each do |field, value| | ... | ... |