Skip to content

Create a search method

Jess edited this page Jul 13, 2016 · 3 revisions

A search method class contains instructions for running a particular query on a given adapter, as well as formatting the input and output of that query so that the search method can act as middleware in a recipe (middleware stack).

First, generate a template:

rails generate whowas:search_method MySearchMethod

This will create a file app/whowas/search_methods/my_search_method.rb that looks like this:

module Whowas
  class MySearchMethod
    include Whowas::Middleware
    include Whowas::Searchable
      
    @@adapter = ADAPTER_CLASS_HERE
    
    private
    
    def required_inputs
      [
        # :ip,
        # :timestamp
      ]
    end

    def input_formats
      {
        # timestamp: lambda { |input| DateTime.parse(input) && true rescue false }
      }      
    end

    def format_input(input)
      input
    end
    
    def output_formats
      {
        # username: /User <\K\w*/
      }
    end    
  end
end

The public interface consists of one method, search, which is provided by Whowas::Searchable. The only required configuration is setting the @@adapter class variable. However, a search method is only as useful as its input and output, so configuring the optional methods is highly recommended. Like adapters, search methods can be validatable and formattable, and they can also be parsable.

Validatable

Validatable uses the following configuration methods to ensure input a) exists and b) is correct.

required_inputs [optional]

The input argument should always be a hash. required_inputs lists the required keys in an array. If there are no required inputs, this should return an empty array.

Example:

def required_inputs
  [:ip, :timestamp]
end

input_formats [optional]

Validates required input against specific requirements. You may specify none, some, or all of the keys enumerated in required_inputs. The value must be a lambda which takes one argument and returns a boolean. If there are no validations, this should return an empty hash.

Example:

def input_formats
  {
    ip: lambda { |input| IPAddr.new(input) && true rescue false }
    timestamp: lambda { |input| DateTime.parse(input) && true rescue false }
  }      
end

If any Validatable condition fails, the search will halt and return an error message with the results hash.

Formattable

format_input [optional]

Performs search method-wide transformations on the input. Adapter-wide transformations can be done in the adapter class. If there is no formatting to be done, this should return input unchanged.

In the example below transforms the input twice:

  1. The input[:query] pair is created from the input argument and a search method-specific string, and
  2. The input[:offset] pair is added to tell the adapter what range of time before the timestamp to search. (This particular adapter is configured to require an offset or use a default, but the value can vary by search method.)
def format_input(input)
  input[:query] = "#{input[:mac].tr(':','')} index=main | head 1"
  input[:offset] = -3600 * 24 * 7
  input
end

For the most part, Whowas is unconcerned with extra values in the input hash. Thus, input[:mac] is passed to the adapter class but is not used.

Parsable

Parsable transforms the output of the search.

output_formats [optional]

The adapter for each search returns its results as a string, which is useful for the final search method in a recipe but not so much for beginning or intermediate steps, which need to provide specific information to the next search. output_formats breaks information out of the results string into the input hash.

ouput_formats is a hash where the key is an input (required or otherwise) for the next search method, and the value is a regular expression matching part of the results hash.

For example, if the next search takes a mac address, which is part of the result string of this search:

def output_formats
  {
    mac: /([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})/
  }
end

If no inputs are required for the next search, this can return an empty hash. Even for the final search method in a recipe, however, it can be useful to break out the desired piece of information from the entire results string for the user to consume.