Skip to content

Instantly share code, notes, and snippets.

@localhostdotdev
Created March 29, 2019 16:19
Show Gist options
  • Save localhostdotdev/5f851a7ce0ae60a5ae05d3fc98995bcb to your computer and use it in GitHub Desktop.
Save localhostdotdev/5f851a7ce0ae60a5ae05d3fc98995bcb to your computer and use it in GitHub Desktop.
class Search
attr_reader :verbose
def initialize(search, verbose: false)
@parts = search.split.map(&:downcase)
@verbose = verbose
end
def self.matches?(search:, record:)
new(search).matches?(record)
end
def self.filter(search:, record:)
new(search).filter(records)
end
def filter(records)
records.select { |r| matches?(r) }
end
def matches?(record)
p record if verbose
outcome = {
result: true,
or: false,
and: false,
not: false,
}
@parts.each do |part|
if part.include?(':')
part_name, part_value = part.split(":", 2)
field = record.fetch(part_name.to_sym)
outcome = next_outcome(
matches_field?(record: record, field: field, value: part_value),
outcome: outcome
)
elsif and?(part)
outcome[:and] = true
elsif or?(part)
outcome[:or] = true
elsif not?(part)
outcome[:not] = !outcome[:not]
else
outcome = next_outcome(
any_field_matches?(record: record, value: part),
outcome: outcome
)
end
p outcome if verbose
end
outcome[:result]
end
def next_outcome(result, outcome:)
if outcome[:not]
outcome[:not] = false
result = !result
end
if outcome[:or]
outcome[:or] = false
outcome[:result] = result || outcome[:result]
else
outcome[:result] = result && outcome[:result]
end
outcome
end
def and?(string)
string.downcase == "and"
end
def or?(string)
string.downcase == "or"
end
def not?(string)
string.downcase == "not"
end
def any_field_matches?(record:, value:)
record.any? do |key, field|
matches_field?(record: record, field: field, value: value)
end
end
def matches_field?(record:, field:, value:)
if field.is_a?(Array)
field.any? do |element|
matches_field?(record: record, field: element, value: value)
end
elsif field.is_a?(Hash)
field.values.any? do |element|
matches_field?(record: record, field: element, value: value)
end
elsif field.is_a?(Date)
operation(field: field, value: value) do |value|
Date.parse(value)
end
elsif field.is_a?(DateTime)
operation(field: field, value: value) do |value|
Time.zone.parse(value)
end
elsif field.is_a?(Integer)
operation(field: field, value: value) do |value|
value.to_i
end
elsif field.is_a?(Float)
operation(field: field, value: value) do |value|
value.to_f
end
elsif field.is_a?(TrueClass) || field.is_a?(FalseClass)
field.to_s == value
else # Symbol, String
field.to_s.downcase.include?(value)
end
end
def operation(field:, value:)
if value.include?('..')
values = value.split('..').map { |v| yield(v) }
return false if values.include?(nil)
field >= values[0] && field <= values[1]
elsif value.starts_with?("<")
value = yield(value[1..-1])
return false unless value
field <= value
elsif value.starts_with?(">")
value = yield(value[1..-1])
return false unless value
field >= value
elsif value.starts_with?("=")
value = yield(value[1..-1])
return false unless value
field == value
else
value = yield(value)
return false unless value
field == value
end
end
end
@localhostdotdev
Copy link
Author

Query examples:

title:Revoir
score:>800
score:<100
score:100..200
score:0
score:=0
score:=0
title:Revoir or title:VPN
by:zapita title:OpenBazaar
by:zapita and title:OpenBazaar
by:zapita or by:okket or by:Jarred
by:zapita or by:okket or by:Jarred score:>800
by:zapita or by:okket or by:Jarred and score:>800
by:Zapita or by:oKket or by:jarred

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment