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