1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
|
# frozen_string_literal: true
module JsonbAccessor
module QueryHelper
# Errors
InvalidColumnName = Class.new(StandardError)
InvalidFieldName = Class.new(StandardError)
InvalidDirection = Class.new(StandardError)
NotSupported = Class.new(StandardError)
# Constants
GREATER_THAN = ">"
GREATER_THAN_OR_EQUAL_TO = ">="
LESS_THAN = "<"
LESS_THAN_OR_EQUAL_TO = "<="
NUMBER_OPERATORS_MAP = {
GREATER_THAN => GREATER_THAN,
"greater_than" => GREATER_THAN,
"gt" => GREATER_THAN,
GREATER_THAN_OR_EQUAL_TO => GREATER_THAN_OR_EQUAL_TO,
"greater_than_or_equal_to" => GREATER_THAN_OR_EQUAL_TO,
"gte" => GREATER_THAN_OR_EQUAL_TO,
LESS_THAN => LESS_THAN,
"less_than" => LESS_THAN,
"lt" => LESS_THAN,
LESS_THAN_OR_EQUAL_TO => LESS_THAN_OR_EQUAL_TO,
"less_than_or_equal_to" => LESS_THAN_OR_EQUAL_TO,
"lte" => LESS_THAN_OR_EQUAL_TO
}.freeze
NUMBER_OPERATORS = NUMBER_OPERATORS_MAP.keys.freeze
TIME_OPERATORS_MAP = {
"after" => GREATER_THAN,
"before" => LESS_THAN
}.freeze
TIME_OPERATORS = TIME_OPERATORS_MAP.keys.freeze
ORDER_DIRECTIONS = [:asc, :desc, "asc", "desc"].freeze
class << self
def validate_column_name!(query, column_name)
raise InvalidColumnName, "a column named `#{column_name}` does not exist on the `#{query.model.table_name}` table" if query.model.columns.none? { |column| column.name == column_name.to_s }
end
def validate_field_name!(query, column_name, field_name)
store_keys = query.model.public_send("jsonb_store_key_mapping_for_#{column_name}").values
if store_keys.exclude?(field_name.to_s)
valid_field_names = store_keys.map { |key| "`#{key}`" }.join(", ")
raise InvalidFieldName, "`#{field_name}` is not a valid field name, valid field names include: #{valid_field_names}"
end
end
def validate_direction!(option)
raise InvalidDirection, "`#{option}` is not a valid direction for ordering, only `asc` and `desc` are accepted" if ORDER_DIRECTIONS.exclude?(option)
end
def number_query_arguments?(arg)
arg.is_a?(Hash) && arg.keys.map(&:to_s).all? { |key| NUMBER_OPERATORS.include?(key) }
end
def time_query_arguments?(arg)
arg.is_a?(Hash) && arg.keys.map(&:to_s).all? { |key| TIME_OPERATORS.include?(key) }
end
def convert_number_ranges(attributes)
attributes.each_with_object({}) do |(name, value), new_attributes|
is_range = value.is_a?(Range)
new_attributes[name] = if is_range && value.first.is_a?(Numeric) && value.exclude_end?
{ greater_than_or_equal_to: value.first, less_than: value.end }
elsif is_range && value.first.is_a?(Numeric)
{ greater_than_or_equal_to: value.first, less_than_or_equal_to: value.end }
else
value
end
end
end
def convert_time_ranges(attributes)
attributes.each_with_object({}) do |(name, value), new_attributes|
is_range = value.is_a?(Range)
if is_range && (value.first.is_a?(Time) || value.first.is_a?(Date))
start_time = value.first
end_time = value.end
new_attributes[name] = { before: end_time, after: start_time }
else
new_attributes[name] = value
end
end
end
def convert_ranges(attributes)
%i[convert_number_ranges convert_time_ranges].reduce(attributes) do |new_attributes, converter_method|
public_send(converter_method, new_attributes)
end
end
end
end
end
|