module Queryfy

Constants

VERSION

Public Class Methods

add_order(arel_table, arel_query, orderstring) click to toggle source
# File lib/queryfy.rb, line 121
def self.add_order(arel_table, arel_query, orderstring)
        # adds order conditions to the passed query

        # If we don't want to order, return the oridinal query
        return arel_query if orderstring.nil? || orderstring == ''

        # Split the fields we want to order on
        split = orderstring.split(',')
        
        # Determine how we want to order each field (asc or desc)
        split.each do |s|
                        order_char = s[-1, 1]
                        s.chop! if order_char == '+' || order_char == '-'
                        order_char = '+' unless order_char == '+' || order_char == '-'
                        field = Queryfy.get_arel_field(arel_table, s)
                        order = :asc
                        if order_char == '-'
                                        order = :desc
                        end

                        # Add the order as a string, since hash is unsupported with query
                        arel_query = arel_query.order("#{field} #{order}")
        end
        return arel_query
end
build_query(klass, querystring, orderstring, limit = 50, offset = 0) click to toggle source

Actually builds the query

# File lib/queryfy.rb, line 14
def self.build_query(klass, querystring, orderstring, limit = 50, offset = 0)
        limit = [max_limit, limit.to_i].min
        offset = offset.to_i
        # Handle empty and nil queries
        if (querystring.nil? || querystring == '')
                        data = self.add_order(klass.arel_table, klass.limit(limit).offset(offset), orderstring)
                return {
                        data: data,
                        count: klass.all.count,
                        limit: limit.to_i, offset: offset.to_i
                }
        end

        begin
                tree = FilterLexer::Parser.parse(querystring)
        rescue FilterLexer::ParseException => e
                raise FilterParseError, "Failed to parse querystring, #{ e.message }"
                return
        end

        # Build the query with pagination
        query = klass.arel_table.project(Arel.star).take(limit).skip(offset)

        cleaned_tree = self.clean_tree(tree)
        arel_tree = self.cleaned_to_arel(klass.arel_table, cleaned_tree)
        # If we want to actually query, add the conditions to query
        query = query.where(arel_tree) unless arel_tree.nil?
        query = self.add_order(klass.arel_table, query, orderstring)

        total = 0
        if arel_tree.nil?
                total = klass.all.count
        else
                countquery = klass.arel_table.project(klass.arel_table[klass.primary_key.to_sym].count.as('total')).where(arel_tree)
                results = klass.connection.execute(countquery.to_sql)
                if results.count == 0
                        raise QueryfyError, 'Failed to select count, this should not happen'
                else
                        total = results[0]['total']
                end
        end

        # Return the results
        return {data: klass.find_by_sql(query.to_sql), count: total.to_i, limit: limit.to_i, offset: offset.to_i}
end
clean_tree(tree, input = []) click to toggle source

Cleans the filterlexer tree Output is an array with either filterentries and operators, or an array of filterentries and operators The latter represents a group

# File lib/queryfy.rb, line 63
def self.clean_tree(tree, input = [])
        tree.elements.each do |el|
                if el.is_a?(FilterLexer::Expression)
                        input += clean_tree(el)
                elsif el.is_a?(FilterLexer::Group)
                        input += [clean_tree(el)]
                else
                        input.push(el)
                end
        end
        return input
end
cleaned_to_arel(arel_table, tree, ast = nil) click to toggle source

Converts a cleaned tree to something arel can understand

# File lib/queryfy.rb, line 77
def self.cleaned_to_arel(arel_table, tree, ast = nil)
        tree.each_with_index do |el, idx|
                next if el.is_a?(FilterLexer::LogicalOperator)
                operator = nil
                operator = tree[idx - 1] if idx > 0
                if el.is_a?(Array)
                        ast = join_ast(ast, arel_table.grouping(cleaned_to_arel(arel_table, el)), operator)
                else
                        ast = join_ast(ast, el.to_arel(arel_table), operator)
                end
        end

        return ast
end
from_queryparams(klass, queryparams) click to toggle source
# File lib/queryfy.rb, line 105
def self.from_queryparams(klass, queryparams)
        filter = ''
        offset = 0
        order = ''
        limit = Queryfy::default_limit
        if (queryparams.is_a?(Hash))
                filter = queryparams['filter'] unless queryparams['filter'].nil?
                offset = queryparams['offset'] unless queryparams['offset'].nil?
                limit = queryparams['limit'] unless queryparams['limit'].nil?
                order = queryparams['order'] unless queryparams['order'].nil?
        elsif(queryparams.is_a?(String))
                filter = queryparams
        end
        return Queryfy.build_query(klass, filter, order, limit, offset)
end
get_arel_field(arel_table, field) click to toggle source
# File lib/queryfy.rb, line 147
def self.get_arel_field(arel_table, field)
        # Check if the field we want to filter on exists
        field_index = arel_table.engine.column_names.index(field)
        arel_field = nil

        # Field does not exist, fail
        if field_index.nil?
                raise NoSuchFieldError.new("Unknown field #{ field }", field)
        else
                # Get the arel field name from our input, just to make sure
                # there is nothing weird is in the input
                arel_field = arel_table.engine.column_names[field_index]
        end
        return arel_field
end
join_ast(ast, nodes, operator) click to toggle source

Merges an existing ast with the passed nodes and uses the operator as a merge operator

# File lib/queryfy.rb, line 93
def self.join_ast(ast, nodes, operator)
        if ast.nil? && !operator.nil?
                raise InvalidFilterFormat, "Cannot join on nil tree with operator near #{operator.text_value}"
        end
        if operator.nil? || ast.nil?
                ast = nodes
        else
                ast = ast.send(operator.to_arel, nodes)
        end
        return ast
end