class OpenFdaApi::QueryBuilder

A helper to build queries against the openFDA API

The API supports five query parameters. The basic building block of queries is the search parameter. Use it to “filter” requests to the API by looking in specific fields for matches. Each endpoint has its own unique fields that can be searched.

search:

What to search for, in which fields. If you don't specify a field to search, the API will search in every field.

sort:

Sort the results of the search by the specified field in ascending or descending order by using the
:asc or :desc modifier.

count:

Count the number of unique values of a certain field, for all the records that matched the search parameter.
By default, the API returns the 1000 most frequent values.

limit:

Return up to this number of records that match the search parameter. Currently, the largest allowed value for the
limit parameter is 1000.

skip:

Skip this number of records that match the search parameter, then return the matching records that follow.
Use in combination with limit to paginate results. Currently, the largest allowed value for the skip parameter
is 25000. See Paging if you require paging through larger result sets.

Public Class Methods

new(query_input:, valid_search_fields:) click to toggle source

@param [Hash] valid_search_fields @param [QueryInputs] query_input

# File lib/open_fda_api/query_builder.rb, line 30
def initialize(query_input:, valid_search_fields:)
  # TODO: Turn validations back on once we get basic functionality working; need to flex on different field types
  # validate_arguments!(valid_search_fields, query_input: query_input)
  warn "You've passed in a valid_search_fields arg but it isn't being used right now..." if valid_search_fields
  @search  = build_query_string(query_fields: query_input.search)
  @sort    = build_query_string(query_fields: query_input.sort)
  @count   = build_query_string(query_fields: query_input.count)
  @skip    = build_skip_string(query_input.skip)
  @limit   = query_input.limit
  @api_key = query_input.api_key
end

Public Instance Methods

build_query() click to toggle source

@return [Hash] the query string portion of a request

# File lib/open_fda_api/query_builder.rb, line 43
def build_query
  {
    api_key: @api_key,
    search: @search,
    sort: @sort,
    count: @count,
    skip: @skip,
    limit: @limit
  }.compact.reject { |_k, v| v.to_s.empty? }
end

Private Instance Methods

build_groupings(fields) click to toggle source
# File lib/open_fda_api/query_builder.rb, line 85
def build_groupings(fields)
  fields.map do |and_args|
    "(#{and_args.map { |k, v| "#{k}:#{v.gsub(" ", "+")}" }.join("+AND+")})"
  end.join("+")
end
build_query_string(query_fields:) click to toggle source
# File lib/open_fda_api/query_builder.rb, line 75
def build_query_string(query_fields:)
  return "" if query_fields.empty?

  build_groupings(query_fields).to_s
end
build_skip_string(skip) click to toggle source
# File lib/open_fda_api/query_builder.rb, line 81
def build_skip_string(skip)
  skip.positive? ? skip.to_s : ""
end
count_and_skip_set?(count, skip) click to toggle source
# File lib/open_fda_api/query_builder.rb, line 103
def count_and_skip_set?(count, skip)
  skip.positive? && !count.empty?
end
get_invalid_fields(valid_search_fields:, fields:) click to toggle source
# File lib/open_fda_api/query_builder.rb, line 91
def get_invalid_fields(valid_search_fields:, fields:)
  # TODO: valid_search_fields define types and we need to check against those
  fields.map(&:keys).flatten.select do |field|
    if field.include?(".") # nested field (e.g. patient.patientagegroup)
      dig_values = field.split(".").join(",properties,").split(",")
      valid_search_fields["properties"].dig(*dig_values).nil?
    else
      !valid_search_fields["properties"].keys.include?(field.to_s)
    end
  end
end
validate_arguments!(valid_search_fields, query_input:) click to toggle source
# File lib/open_fda_api/query_builder.rb, line 56
def validate_arguments!(valid_search_fields, query_input:)
  # `search` keys must exist in adverse_events_fields.yml
  invalid_fields = get_invalid_fields(valid_search_fields: valid_search_fields, fields: query_input.search)
  raise InvalidQueryArgument, "'search' has invalid fields: #{invalid_fields}" if invalid_fields.any?

  # `sort` keys must exist in adverse_events_fields.yml
  invalid_fields = get_invalid_fields(valid_search_fields: valid_search_fields, fields: query_input.sort)
  raise InvalidQueryArgument, "'sort' has invalid fields: #{invalid_fields}" if invalid_fields.any?

  # `count` keys must exist in adverse_events_fields.yml
  invalid_fields = get_invalid_fields(valid_search_fields: valid_search_fields, fields: query_input.count)
  raise InvalidQueryArgument, "'count' has invalid fields: #{invalid_fields}" if invalid_fields.any?

  # `count` and `skip` cannot be set at the same time
  return unless count_and_skip_set?(query_input.count, query_input.skip)

  raise InvalidQueryArgument, "'count' and 'skip' cannot both be set at the same time!"
end