lib/jsonb_accessor/query_builder.rb



# frozen_string_literal: true
module JsonbAccessor
  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

  IS_NUMBER_QUERY_ARGUMENTS = lambda do |arg|
    arg.is_a?(Hash) &&
      arg.keys.map(&:to_s).all? { |key| JsonbAccessor::NUMBER_OPERATORS.include?(key) }
  end

  IS_TIME_QUERY_ARGUMENTS = lambda do |arg|
    arg.is_a?(Hash) &&
      arg.keys.map(&:to_s).all? { |key| JsonbAccessor::TIME_OPERATORS.include?(key) }
  end

  module QueryBuilder
    extend ActiveSupport::Concern

    included do
      scope(:jsonb_contains,
        -> (column_name, attributes) { where("#{table_name}.#{column_name} @> (?)::jsonb", attributes.to_json) })

      scope(:jsonb_number_where, lambda do |column_name, field_name, given_operator, value|
        operator = JsonbAccessor::NUMBER_OPERATORS_MAP.fetch(given_operator.to_s)
        where("(#{table_name}.#{column_name} ->> ?)::float #{operator} ?", field_name, value)
      end)

      scope(:jsonb_time_where, lambda do |column_name, field_name, given_operator, value|
        operator = JsonbAccessor::TIME_OPERATORS_MAP.fetch(given_operator.to_s)
        where("(#{table_name}.#{column_name} ->> ?)::timestamp #{operator} ?", field_name, value)
      end)

      scope(:jsonb_where, lambda do |column_name, attributes|
        query = all
        contains_attributes = {}

        attributes.each do |name, value|
          case value
          when IS_NUMBER_QUERY_ARGUMENTS
            value.each { |operator, query_value| query = query.jsonb_number_where(column_name, name, operator, query_value) }
          when IS_TIME_QUERY_ARGUMENTS
            value.each { |operator, query_value| query = query.jsonb_time_where(column_name, name, operator, query_value) }
          else
            contains_attributes[name] = value
          end
        end

        query.jsonb_contains(column_name, contains_attributes)
      end)
    end
  end
end