class Praxis::Extensions::SequelFilterQueryBuilder

Attributes

filters_map[R]
query[R]
root[R]

Public Class Methods

for(definition) click to toggle source
# File lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb, line 10
def for(definition)
  Class.new(self) do
    @filters_map = case definition
                      when Hash
                        definition
                      when Array
                        definition.each_with_object({}) { |item, hash| hash[item.to_sym] = item }
                      else
                        raise "Cannot use FilterQueryBuilder.of without passing an array or a hash (Got: #{definition.class.name})"
                      end
    class << self
      attr_reader :filters_map
    end
  end
end
new(query:, model: ) click to toggle source

Base query to build upon table is necessary when use the strin queries, when the query has multiple tables involved (to disambiguate)

# File lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb, line 30
def initialize(query:, model: )
  @query = query
  @root = model.table_name
end

Public Instance Methods

_mapped_filter(name) click to toggle source
# File lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb, line 72
def _mapped_filter(name)
  target = self.class.filters_map[name]
  unless target
    if @model.attribute_names.include?(name.to_s)
      # Cache it in the filters mapping (to avoid later lookups), and return it.
      self.class.filters_map[name] = name
      target = name
    end
  end
  return target
end
expand_binding(column_name:,op:,value:) click to toggle source
# File lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb, line 59
def expand_binding(column_name:,op:,value:)
  assoc_or_field, *rest = column_name.to_s.split('.')
  if rest.empty?
    column_name = Sequel.qualify(root,column_name)
  else
    puts "Adding eager graph for #{assoc_or_field} due to being used in filter"
    # Ensure the joined table is aliased properly (to the association name) so we can add the condition appropriately
    @query = query.eager_graph(Sequel.as(assoc_or_field.to_sym, assoc_or_field.to_sym) )
    column_name = Sequel.qualify(assoc_or_field, rest.first)
  end
  add_clause(attr: column_name, op: op, value: value)
end
generate(filters) click to toggle source

By default we’ll simply use the incoming op and value, and will map the attribute based on what’s on the ‘filters_map` definition

# File lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb, line 37
def generate(filters)
  raise "Not refactored yet!"
  seen_associations = Set.new
  filters.each do |(attr, spec)|
    column_name = _mapped_filter(attr)
    unless column_name
      msg = "Filtering by #{attr} is not allowed. No implementation mapping defined for it has been found \
        and there is not a model attribute with this name either.\n" \
        "Please add a mapping for #{attr} in the `filters_mapping` method of the appropriate Resource class"
      raise msg
    end          
    if column_name.is_a?(Proc)
      bindings = column_name.call(spec)
      # A hash of bindings, consisting of a key with column name and a value to the query value
      bindings.each{|col,val| expand_binding(column_name: col, op: spec[:op], value: val )}
    else
      expand_binding(column_name: column_name, **spec)
    end
  end
  query
end
get_like_value(value) click to toggle source

Returns nil if the value was not a fuzzzy pattern

# File lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb, line 127
def get_like_value(value)
  if value.is_a?(String) && (value[-1] == '*' || value[0] == '*')
    likeval = value.dup
    likeval[-1] = '%' if value[-1] == '*'
    likeval[0] = '%' if value[0] == '*'
    likeval
  end
end

Private Instance Methods

add_clause(attr:, op:, value:) click to toggle source

Private to try to funnel all column names through ‘generate` that restricts the attribute names better (to allow more difficult SQL injections )

# File lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb, line 86
            def add_clause(attr:, op:, value:)
      # TODO: partial matching
      #components = attr.to_s.split('.')
      #attr_selector = Sequel.qualify(*components)
      attr_selector = attr
#      HERE!! if we have "association.name" we should properly join it ...!

#> ds.eager_graph(:device).where{{device[:name] => 'A%'}}.select(:accountID)
#=> #<Sequel::Mysql2::Dataset: "SELECT `accountID` FROM `EventData`
#     LEFT OUTER JOIN `Device` AS `device` ON
#           ((`device`.`accountID` = `EventData`.`accountID`) AND (`device`.`deviceID` = `EventData`.`deviceID`))
#      WHERE (`device`.`name` = 'A%')">
      likeval = get_like_value(value)
      @query =  case op
                when '='
                  if likeval
                    query.where(Sequel.like(attr_selector, likeval))
                  else
                    query.where(attr_selector => value)
                  end
                when '!='
                  if likeval
                    query.exclude(Sequel.like(attr_selector, likeval))
                  else
                    query.exclude(attr_selector => value)
                  end
                when '>'
                  #query.where(Sequel.lit("#{attr_selector} > ?", value))
                  query.where{attr_selector > value}
                when '<'
                  query.where{attr_selector < value}
                when '>='
                  query.where{attr_selector >= value}
                when '<='
                  query.where{attr_selector <= value}
                else
                  raise "Unsupported Operator!!! #{op}"
                end
    end