Class: ScopedSearch::QueryBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/scoped_search/query_builder.rb

Defined Under Namespace

Modules: AST, Field

Constant Summary collapse

SQL_OPERATORS =
{ :eq =>'=', :ne => '<>',
:like => 'LIKE', :unlike => 'NOT LIKE',
:gt => '>', :lt =>'<', :lte => '<=', :gte => '>=' }

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(definition, ast) ⇒ QueryBuilder

Initializes the instance by setting the relevant parameters



20
21
22
# File 'lib/scoped_search/query_builder.rb', line 20

def initialize(definition, ast)
  @definition, @ast = definition, ast
end

Instance Attribute Details

#astObject (readonly)

Returns the value of attribute ast.



5
6
7
# File 'lib/scoped_search/query_builder.rb', line 5

def ast
  @ast
end

#definitionObject (readonly)

Returns the value of attribute definition.



5
6
7
# File 'lib/scoped_search/query_builder.rb', line 5

def definition
  @definition
end

Class Method Details

.build_query(definition, query) ⇒ Object

Creates a find parameter hash given a class, and query string.



8
9
10
11
12
13
14
15
16
17
# File 'lib/scoped_search/query_builder.rb', line 8

def self.build_query(definition, query) 
  # Return all record when an empty search string is given
  if !query.kind_of?(String) || query.strip.blank?
    return { :conditions => nil }
  elsif query.kind_of?(ScopedSearch::QueryLanguage::AST::Node)
    return self.new(definition, query).build_find_params
  else
    return self.new(definition, ScopedSearch::QueryLanguage::Compiler.parse(query)).build_find_params
  end
end

.datetime_test(field, operator, value) {|:parameter, timestamp| ... } ⇒ Object

Perform a comparison between a field and a Date(Time) value. Makes sure the date is valid and adjust the comparison in some cases to return more logical results

Yields:

  • (:parameter, timestamp)


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
# File 'lib/scoped_search/query_builder.rb', line 62

def self.datetime_test(field, operator, value, &block)
  
  # Parse the value as a date/time and ignore invalid timestamps
  timestamp = parse_temporal(value)
  return nil unless timestamp 
  timestamp = Date.parse(timestamp.strftime('%Y-%m-%d')) if field.date?      
  
  # Check for the case that a date-only value is given as search keyword,
  # but the field is of datetime type. Change the comparison to return
  # more logical results.
  if timestamp.day_fraction == 0 && field.datetime?
    
    if [:eq, :ne].include?(operator)
      # Instead of looking for an exact (non-)match, look for dates that
      # fall inside/outside the range of timestamps of that day.
      yield(:parameter, timestamp)
      yield(:parameter, timestamp + 1)
      negate    = (operator == :ne) ? 'NOT' : ''
      field_sql = field.to_sql(operator, &block)
      return "#{negate}(#{field_sql} >= ? AND #{field_sql} < ?)"
      
    elsif operator == :gt
      # Make sure timestamps on the given date are not included in the results
      # by moving the date to the next day.
      timestamp += 1
      operator = :gte
      
    elsif operator == :lte
      # Make sure the timestamps of the given date are included by moving the 
      # date to the next date.
      timestamp += 1
      operator = :lt
    end
  end

  # Yield the timestamp and return the SQL test
  yield(:parameter, timestamp)
  "#{field.to_sql(operator, &block)} #{self.sql_operator(operator, field)} ?"
end

.parse_temporal(value) ⇒ Object

Try to parse a string as a datetime.



116
117
118
# File 'lib/scoped_search/query_builder.rb', line 116

def self.parse_temporal(value)
  DateTime.parse(value, true) rescue nil
end

.sql_operator(operator, field) ⇒ Object

Return the SQL operator to use



55
56
57
# File 'lib/scoped_search/query_builder.rb', line 55

def self.sql_operator(operator, field)
  SQL_OPERATORS[operator]
end

.sql_test(field, operator, value, &block) ⇒ Object

Generates a simple SQL test expression, for a field and value using an operator.



103
104
105
106
107
108
109
110
111
112
113
# File 'lib/scoped_search/query_builder.rb', line 103

def self.sql_test(field, operator, value, &block)
  if [:like, :unlike].include?(operator) && value !~ /^\%/ && value !~ /\%$/
    yield(:parameter, "%#{value}%")
    return "#{field.to_sql(operator, &block)} #{self.sql_operator(operator, field)} ?"
  elsif field.temporal?
    return datetime_test(field, operator, value, &block)
  else
    yield(:parameter, value)
    return "#{field.to_sql(operator, &block)} #{self.sql_operator(operator, field)} ?"
  end
end

Instance Method Details

#build_find_paramsObject

Actually builds the find parameters



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/scoped_search/query_builder.rb', line 25

def build_find_params
  parameters = []
  includes   = []
  
  # Build SQL WHERE clause using the AST
  sql = @ast.to_sql(definition) do |notification, value|
    
    # Handle the notifications encountered during the SQL generation:
    # Store the parameters, includes, etc so that they can be added to
    # the find-hash later on.
    case notification
    when :parameter then parameters << value
    when :include   then includes   << value
    else raise ScopedSearch::QueryNotSupported, "Cannot handle #{notification.inspect}: #{value.inspect}"
    end
  end
  
  # Build hash for ActiveRecord::Base#find for the named scope
  find_attributes = {}
  find_attributes[:conditions] = [sql] + parameters unless sql.nil?
  find_attributes[:include]    = includes.uniq      unless includes.empty?
  # p find_attributes # Uncomment for debugging
  return find_attributes
end