Skip to content

Add SQL query support in Ruby #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions lib/jquery_query_builder/evaluator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,30 @@
module JqueryQueryBuilder
class Evaluator
attr_accessor :parsed_rule_set
def initialize(rule_set)
attr_accessor :id_whitelist

def initialize(rule_set, id_whitelist: nil)
if rule_set.is_a? String
#assuming the json was passed in
self.parsed_rule_set = JSON.parse(rule_set)
else
self.parsed_rule_set = rule_set
end

# whitelist of ids to restrict the rules to just those within the whitelist
self.id_whitelist = id_whitelist
end

def get_matching_objects(objects)
objects.select{|o| object_matches_rules?(o)}
end

def object_matches_rules?(object)
RuleGroup.new(parsed_rule_set).evaluate(object)
RuleGroup.new(parsed_rule_set, id_whitelist: id_whitelist).evaluate(object)
end

def sql_query
RuleGroup.new(parsed_rule_set, id_whitelist: id_whitelist).sql_query
end
end
end
Expand Down
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/begins_with.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class BeginsWith
def evaluate(left, right)
left.start_with?(right)
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(LOWER(#{id}) LIKE LOWER(?))", "#{value}%"] )
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/between.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class Between
def evaluate(input, bounds)
input > bounds[0] && input < bounds[1]
end

def sql_query(id, bounds)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(#{id} BETWEEN ? AND ?)", bounds[0], bounds[1]] )
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/contains.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ def evaluate(left, right)
return false if left.nil?
left.include?(right)
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(LOWER(#{id}) LIKE LOWER(?))", "%#{value}%"] )
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/ends_with.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class EndsWith
def evaluate(left, right)
left.end_with?(right)
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(LOWER(#{id}) LIKE LOWER(?))", "%#{value}"] )
end
end
end
end
7 changes: 7 additions & 0 deletions lib/jquery_query_builder/operators/equal.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
include ActiveRecord::Sanitization::ClassMethods

module JqueryQueryBuilder
module Operators
class Equal
def evaluate(left, right)
left == right
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(#{id} = ?)", value] )
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/greater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class Greater
def evaluate(left, right)
left > right
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(#{id} > ?)", value] )
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/greater_or_equal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class GreaterOrEqual
def evaluate(left, right)
left >= right
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(#{id} >= ?)", value] )
end
end
end
end
6 changes: 6 additions & 0 deletions lib/jquery_query_builder/operators/in.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ class In
def evaluate(left, right)
right.include?(left)
end

def sql_query(id, value)
value = value.join(', ') if value.is_a?(Array)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(#{id} IN(?))", value] )
end
end
end
end
4 changes: 4 additions & 0 deletions lib/jquery_query_builder/operators/is_empty.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ class IsEmpty
def evaluate(input, value)
input.blank?
end

def sql_query(id, value)
"(#{id} = '')"
end
end
end
end
4 changes: 4 additions & 0 deletions lib/jquery_query_builder/operators/is_not_empty.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ class IsNotEmpty
def evaluate(input, value)
input.present?
end

def sql_query(id, value)
"(#{id} != '')"
end
end
end
end
4 changes: 4 additions & 0 deletions lib/jquery_query_builder/operators/is_not_null.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ class IsNotNull
def evaluate(input, value)
!input.nil?
end

def sql_query(id, value)
"(#{id} IS NOT NULL)"
end
end
end
end
4 changes: 4 additions & 0 deletions lib/jquery_query_builder/operators/is_null.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ class IsNull
def evaluate(input, value)
input.nil?
end

def sql_query(id, value)
"(#{id} IS NULL)"
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/less.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class Less
def evaluate(left, right)
left < right
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(#{id} < ?)", value] )
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/less_or_equal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class LessOrEqual
def evaluate(left, right)
left <= right
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(#{id} <= ?)", value] )
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/not_begins_with.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class NotBeginsWith
def evaluate(left, right)
!left.start_with?(right)
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(#{id} NOT LIKE ?)", "%#{value}%"] )
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/not_between.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class NotBetween
def evaluate(input, bounds)
input <= bounds[0] || input >= bounds[1]
end

def sql_query(id, bounds)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(#{id} NOT BETWEEN ? AND ?)", bounds[0], bounds[1]] )
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/not_contains.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class NotContains
def evaluate(left, right)
!left.include?(right)
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(LOWER(#{id}) NOT LIKE LOWER(?))", "%#{value}%"] )
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/not_ends_with.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class NotEndsWith
def evaluate(left, right)
!left.end_with?(right)
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(LOWER(#{id}) NOT LIKE LOWER(?))", "%#{value}"] )
end
end
end
end
5 changes: 5 additions & 0 deletions lib/jquery_query_builder/operators/not_equal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ class NotEqual
def evaluate(left, right)
left != right
end

def sql_query(id, value)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(#{id} != ?)", value] )
end
end
end
end
6 changes: 6 additions & 0 deletions lib/jquery_query_builder/operators/not_in.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ class NotIn
def evaluate(left, right)
!right.include?(left)
end

def sql_query(id, value)
value = value.join(', ') if value.is_a?(Array)
# sanitize_sql_for_conditions is made public in Rails 5.2
ApplicationRecord.send(:sanitize_sql_for_conditions, ["(#{id} NOT IN(?))", value] )
end
end
end
end
15 changes: 13 additions & 2 deletions lib/jquery_query_builder/rule.rb
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
module JqueryQueryBuilder
class Rule
attr_accessor :id, :field, :type, :input, :operator, :value
def initialize(rule_hash)
attr_accessor :id, :field, :type, :input, :operator, :value, :id_whitelist
def initialize(rule_hash, id_whitelist: nil)
self.id = rule_hash['id']
self.field = rule_hash['field']
self.type = rule_hash['type']
self.input = rule_hash['input']
self.operator = rule_hash['operator']
self.value = rule_hash['value']
self.id_whitelist = id_whitelist
end

def whitelisted?
return true unless id_whitelist.present?

id_whitelist.include?(self.id)
end

def evaluate(object)
get_operator.evaluate(get_input(object), get_value)
end

def sql_query
get_operator.sql_query(self.id, get_value)
end

def get_operator
JqueryQueryBuilder::Operator.get_operator_class(operator).new
end
Expand Down
58 changes: 52 additions & 6 deletions lib/jquery_query_builder/rule_group.rb
Original file line number Diff line number Diff line change
@@ -1,25 +1,71 @@
module JqueryQueryBuilder
class RuleGroup
attr_accessor :condition, :rules
def initialize(rule_group_hash)
attr_accessor :condition, :rules, :id_whitelist
def initialize(rule_group_hash, id_whitelist: nil)
self.condition = rule_group_hash['condition']
self.rules = rule_group_hash['rules']
self.id_whitelist = id_whitelist
end

def evaluate(object)
case condition
when "AND"
rules.all?{|rule| get_rule_object(rule).evaluate(object) }
rules.all? do |rule|
rule_obj = get_rule_object(rule)
if rule_obj.whitelisted?
rule_obj.evaluate(object)
else
true # ignore non whitelisted rule
end
end
when "OR"
rules.any?{|rule| get_rule_object(rule).evaluate(object) }
rules.any? do |rule|
rule_obj = get_rule_object(rule)
if rule_obj.whitelisted?
rule_obj.evaluate(object)
else
false # ignore non whitelisted rule
end
end
end
end

def sql_query
queries = []
rules.each do |rule|
rule_object = get_rule_object(rule)
# skip the conditional if the rule is not whitelisted
sub_query = rule_object.sql_query if rule_object.whitelisted?
queries << sub_query if sub_query # query can be nil if all rules are rejected
end

if queries.empty?
query_string = nil
elsif queries.length == 1
query_string = queries[0]
elsif queries.length > 1
case condition
when "AND"
query_string = queries.join(' AND ')
when "OR"
query_string = queries.join(' OR ')
end
query_string = "(#{query_string})" # add parens for groups
end

return query_string
end

def whitelisted?
# rule groups are always whitelisted, only rules can fail
true
end

def get_rule_object(rule)
if rule['rules'].present?
RuleGroup.new(rule)
RuleGroup.new(rule, id_whitelist: id_whitelist)
else
Rule.new(rule)
Rule.new(rule, id_whitelist: id_whitelist)
end
end
end
Expand Down