Skip to content

Instantly share code, notes, and snippets.

@robhurring
Created December 14, 2011 18:05
Show Gist options
  • Select an option

  • Save robhurring/1477730 to your computer and use it in GitHub Desktop.

Select an option

Save robhurring/1477730 to your computer and use it in GitHub Desktop.

Revisions

  1. robhurring revised this gist Dec 6, 2012. 1 changed file with 60 additions and 13 deletions.
    73 changes: 60 additions & 13 deletions search_terms.rb
    Original file line number Diff line number Diff line change
    @@ -1,32 +1,51 @@
    # Search term parser from https://gist.github.com/1477730
    # Modified to allow periods (and other non-letter chars) in unquoted field values
    # and field names.
    #
    # Helper class to help parse out more advanced saerch terms
    # from a form query
    #
    #
    # Note: all hash keys are downcased, so ID:10 == {'id' => 10}
    # you can also access all keys with methods e.g.: terms.id = terms['id'] = 10
    # this doesn't work with query as thats reserved for the left-over pieces
    #
    #
    # Usage:
    # terms = SearchTerms.new('id:10 search terms here')
    # => @query="search terms here", @parts={"id"=>"10"}
    # => terms.query = 'search terms here'
    # => terms['id'] = 10
    #
    #
    # terms = SearchTerms.new('name:"support for spaces" state:pa')
    # => @query="", @parts={"name"=>"support for spaces", "state"=>"pa"}
    # => terms.query = ''
    # => terms['name'] = 'support for spaces'
    # => terms.name = 'support for spaces'
    #
    #
    # terms = SearchTerms.new('state:pa,nj,ca')
    # => @query="", @parts={"state"=>["pa","nj","ca"]}
    #
    #
    # terms = SearchTerms.new('state:pa,nj,ca', false)
    # => @query="", @parts={"state"=>"pa,nj,c"}
    #
    #
    # Useful to drive custom logic in controllers
    class SearchTerms
    attr_reader :query, :parts


    # regex scanner for the parser
    SCANNER = %r{
    (?:
    ([\w\.]+) # look for any word
    )
    (?: # check if it has a value attached
    : # find the value delimiter
    (
    [\w,\-]+ # match any word-like values
    | # -or-
    (?:"(?:.+|[^\"])*") # match any quoted values
    )
    )?
    }x

    # query:: this is what you want tokenized
    # split:: if you'd like to split values on "," then pass true
    def initialize(query, split = true)
    @@ -35,7 +54,7 @@ def initialize(query, split = true)
    @split = split
    parse_query!
    end

    def [](key)
    @parts[key]
    end
    @@ -44,7 +63,8 @@ def [](key)

    def parse_query!
    tmp = []
    @query.scan(/(?:(\w+))(?::([\w,\-]+|(?:"(?:.+|[^\"])*")))?/).map do |key,value|

    @query.scan(SCANNER).map do |key,value|
    if value.nil?
    tmp << key
    else
    @@ -53,19 +73,46 @@ def parse_query!
    define_metaclass_method(key){ @parts[key] } unless key == 'query'
    end
    end

    @query = tmp.join(' ')
    end

    def clean_value(value)
    return value.tr('"', '') if value.include?('"')
    return value.split(',') if @split && value.include?(',')
    return true if value == 'true'
    return false if value == 'false'
    return value.to_i if value =~ /^[^0]\d+$/
    return value.to_i if value =~ /^[1-9][0-9]*$/
    value
    end

    def define_metaclass_method(method, &block)
    (class << self; self; end).send :define_method, method, &block
    end
    end
    end

    if $0 == __FILE__
    require 'test/unit'

    class SearchTermsTest < Test::Unit::TestCase
    TEST_CASES = {
    "simple" => ["foo","foo",{}],
    "simple_field" => ["one:two","",{"one" => "two"}],
    "quotes" => [%{foo:"quoted value"}, "", {"foo" => "quoted value"}],
    "term_with_period" => ["1.5","1.5",{}],
    "multiple_fields" => ["one:two three:four","",{"one" => "two", "three" => "four"}],
    "int_parse" => ["id:123","",{"id" => 123}],
    "int_parse_leading_letter" => ["id:a01","","id" => "a01"],
    "int_parse_leading_zero" => ["id:001","","id" => "001"],
    "mixed_fields_terms" => ["one two:three four five:six","one four",{"two" => "three", "five" => "six"}]
    }

    TEST_CASES.each do |name, (input, query, parts)|
    define_method("test_#{name}") do
    terms = SearchTerms.new(input)
    assert_equal query, terms.query
    assert_equal parts, terms.parts
    end
    end
    end
    end
  2. robhurring revised this gist Jan 3, 2012. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions search_terms.rb
    Original file line number Diff line number Diff line change
    @@ -30,10 +30,10 @@ class SearchTerms
    # query:: this is what you want tokenized
    # split:: if you'd like to split values on "," then pass true
    def initialize(query, split = true)
    @query = ''
    @query = query
    @parts = {}
    @split = split
    parse_query!(query)
    parse_query!
    end

    def [](key)
    @@ -42,9 +42,9 @@ def [](key)

    private

    def parse_query!(query)
    def parse_query!
    tmp = []
    query.scan(/(?:(\w+))(?::([\w,\-]+|(?:"(?:.+|[^\"])*")))?/).map do |key,value|
    @query.scan(/(?:(\w+))(?::([\w,\-]+|(?:"(?:.+|[^\"])*")))?/).map do |key,value|
    if value.nil?
    tmp << key
    else
  3. robhurring revised this gist Dec 23, 2011. 1 changed file with 13 additions and 1 deletion.
    14 changes: 13 additions & 1 deletion search_terms.rb
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,8 @@
    # from a form query
    #
    # Note: all hash keys are downcased, so ID:10 == {'id' => 10}
    # you can also access all keys with methods e.g.: terms.id = terms['id'] = 10
    # this doesn't work with query as thats reserved for the left-over pieces
    #
    # Usage:
    # terms = SearchTerms.new('id:10 search terms here')
    @@ -13,6 +15,7 @@
    # => @query="", @parts={"name"=>"support for spaces", "state"=>"pa"}
    # => terms.query = ''
    # => terms['name'] = 'support for spaces'
    # => terms.name = 'support for spaces'
    #
    # terms = SearchTerms.new('state:pa,nj,ca')
    # => @query="", @parts={"state"=>["pa","nj","ca"]}
    @@ -45,7 +48,9 @@ def parse_query!(query)
    if value.nil?
    tmp << key
    else
    @parts[key.downcase] = clean_value(value)
    key.downcase!
    @parts[key] = clean_value(value)
    define_metaclass_method(key){ @parts[key] } unless key == 'query'
    end
    end
    @query = tmp.join(' ')
    @@ -54,6 +59,13 @@ def parse_query!(query)
    def clean_value(value)
    return value.tr('"', '') if value.include?('"')
    return value.split(',') if @split && value.include?(',')
    return true if value == 'true'
    return false if value == 'false'
    return value.to_i if value =~ /^[^0]\d+$/
    value
    end

    def define_metaclass_method(method, &block)
    (class << self; self; end).send :define_method, method, &block
    end
    end
  4. robhurring revised this gist Dec 23, 2011. 1 changed file with 16 additions and 0 deletions.
    16 changes: 16 additions & 0 deletions your_controller.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    # basic usage to search users from your #index action
    class UsersController < ApplicationController
    def index
    if params[:q]
    terms = SearchTerms.new(params[:q])

    if terms['id']
    return redirect_to user_path(terms['id'])
    else
    @users = @users.search_by_name(terms.query) unless terms.query.blank?
    @users = @users.with_role(terms['role']) if terms['role']
    @users = @users.registered(false) if terms['guest']
    end
    end
    end
    end
  5. robhurring created this gist Dec 14, 2011.
    59 changes: 59 additions & 0 deletions search_terms.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,59 @@
    # Helper class to help parse out more advanced saerch terms
    # from a form query
    #
    # Note: all hash keys are downcased, so ID:10 == {'id' => 10}
    #
    # Usage:
    # terms = SearchTerms.new('id:10 search terms here')
    # => @query="search terms here", @parts={"id"=>"10"}
    # => terms.query = 'search terms here'
    # => terms['id'] = 10
    #
    # terms = SearchTerms.new('name:"support for spaces" state:pa')
    # => @query="", @parts={"name"=>"support for spaces", "state"=>"pa"}
    # => terms.query = ''
    # => terms['name'] = 'support for spaces'
    #
    # terms = SearchTerms.new('state:pa,nj,ca')
    # => @query="", @parts={"state"=>["pa","nj","ca"]}
    #
    # terms = SearchTerms.new('state:pa,nj,ca', false)
    # => @query="", @parts={"state"=>"pa,nj,c"}
    #
    # Useful to drive custom logic in controllers
    class SearchTerms
    attr_reader :query, :parts

    # query:: this is what you want tokenized
    # split:: if you'd like to split values on "," then pass true
    def initialize(query, split = true)
    @query = ''
    @parts = {}
    @split = split
    parse_query!(query)
    end

    def [](key)
    @parts[key]
    end

    private

    def parse_query!(query)
    tmp = []
    query.scan(/(?:(\w+))(?::([\w,\-]+|(?:"(?:.+|[^\"])*")))?/).map do |key,value|
    if value.nil?
    tmp << key
    else
    @parts[key.downcase] = clean_value(value)
    end
    end
    @query = tmp.join(' ')
    end

    def clean_value(value)
    return value.tr('"', '') if value.include?('"')
    return value.split(',') if @split && value.include?(',')
    value
    end
    end