diff --git a/lib/jquery_query_builder/evaluator.rb b/lib/jquery_query_builder/evaluator.rb index 8cb6489..d1d4750 100644 --- a/lib/jquery_query_builder/evaluator.rb +++ b/lib/jquery_query_builder/evaluator.rb @@ -9,13 +9,18 @@ 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) @@ -23,7 +28,11 @@ def get_matching_objects(objects) 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 diff --git a/lib/jquery_query_builder/operators/begins_with.rb b/lib/jquery_query_builder/operators/begins_with.rb index 4dc7b72..241d1a3 100644 --- a/lib/jquery_query_builder/operators/begins_with.rb +++ b/lib/jquery_query_builder/operators/begins_with.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/between.rb b/lib/jquery_query_builder/operators/between.rb index 8e83645..053c091 100644 --- a/lib/jquery_query_builder/operators/between.rb +++ b/lib/jquery_query_builder/operators/between.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/contains.rb b/lib/jquery_query_builder/operators/contains.rb index da079d5..6e3c35b 100644 --- a/lib/jquery_query_builder/operators/contains.rb +++ b/lib/jquery_query_builder/operators/contains.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/ends_with.rb b/lib/jquery_query_builder/operators/ends_with.rb index b990ac8..3baccf7 100644 --- a/lib/jquery_query_builder/operators/ends_with.rb +++ b/lib/jquery_query_builder/operators/ends_with.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/equal.rb b/lib/jquery_query_builder/operators/equal.rb index 294a5e6..dd4846b 100644 --- a/lib/jquery_query_builder/operators/equal.rb +++ b/lib/jquery_query_builder/operators/equal.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/greater.rb b/lib/jquery_query_builder/operators/greater.rb index d5134c7..0c480da 100644 --- a/lib/jquery_query_builder/operators/greater.rb +++ b/lib/jquery_query_builder/operators/greater.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/greater_or_equal.rb b/lib/jquery_query_builder/operators/greater_or_equal.rb index 1c7133b..d4bb5da 100644 --- a/lib/jquery_query_builder/operators/greater_or_equal.rb +++ b/lib/jquery_query_builder/operators/greater_or_equal.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/in.rb b/lib/jquery_query_builder/operators/in.rb index b971316..a3a278d 100644 --- a/lib/jquery_query_builder/operators/in.rb +++ b/lib/jquery_query_builder/operators/in.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/is_empty.rb b/lib/jquery_query_builder/operators/is_empty.rb index 7d1b955..78c25d9 100644 --- a/lib/jquery_query_builder/operators/is_empty.rb +++ b/lib/jquery_query_builder/operators/is_empty.rb @@ -4,6 +4,10 @@ class IsEmpty def evaluate(input, value) input.blank? end + + def sql_query(id, value) + "(#{id} = '')" + end end end end diff --git a/lib/jquery_query_builder/operators/is_not_empty.rb b/lib/jquery_query_builder/operators/is_not_empty.rb index ae6d37d..6e65b6b 100644 --- a/lib/jquery_query_builder/operators/is_not_empty.rb +++ b/lib/jquery_query_builder/operators/is_not_empty.rb @@ -4,6 +4,10 @@ class IsNotEmpty def evaluate(input, value) input.present? end + + def sql_query(id, value) + "(#{id} != '')" + end end end end diff --git a/lib/jquery_query_builder/operators/is_not_null.rb b/lib/jquery_query_builder/operators/is_not_null.rb index fa55d71..7276190 100644 --- a/lib/jquery_query_builder/operators/is_not_null.rb +++ b/lib/jquery_query_builder/operators/is_not_null.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/is_null.rb b/lib/jquery_query_builder/operators/is_null.rb index 9835438..57ff09d 100644 --- a/lib/jquery_query_builder/operators/is_null.rb +++ b/lib/jquery_query_builder/operators/is_null.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/less.rb b/lib/jquery_query_builder/operators/less.rb index 5a0811c..423c59f 100644 --- a/lib/jquery_query_builder/operators/less.rb +++ b/lib/jquery_query_builder/operators/less.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/less_or_equal.rb b/lib/jquery_query_builder/operators/less_or_equal.rb index e6eafff..8c34c7a 100644 --- a/lib/jquery_query_builder/operators/less_or_equal.rb +++ b/lib/jquery_query_builder/operators/less_or_equal.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/not_begins_with.rb b/lib/jquery_query_builder/operators/not_begins_with.rb index e1d22a4..675eda6 100644 --- a/lib/jquery_query_builder/operators/not_begins_with.rb +++ b/lib/jquery_query_builder/operators/not_begins_with.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/not_between.rb b/lib/jquery_query_builder/operators/not_between.rb index 76f4dfa..e0b7539 100644 --- a/lib/jquery_query_builder/operators/not_between.rb +++ b/lib/jquery_query_builder/operators/not_between.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/not_contains.rb b/lib/jquery_query_builder/operators/not_contains.rb index 778d8cf..748a1da 100644 --- a/lib/jquery_query_builder/operators/not_contains.rb +++ b/lib/jquery_query_builder/operators/not_contains.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/not_ends_with.rb b/lib/jquery_query_builder/operators/not_ends_with.rb index 8c9b6ad..ad985d8 100644 --- a/lib/jquery_query_builder/operators/not_ends_with.rb +++ b/lib/jquery_query_builder/operators/not_ends_with.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/not_equal.rb b/lib/jquery_query_builder/operators/not_equal.rb index bb3a325..0725462 100644 --- a/lib/jquery_query_builder/operators/not_equal.rb +++ b/lib/jquery_query_builder/operators/not_equal.rb @@ -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 diff --git a/lib/jquery_query_builder/operators/not_in.rb b/lib/jquery_query_builder/operators/not_in.rb index 28c8d40..8ab8d1a 100644 --- a/lib/jquery_query_builder/operators/not_in.rb +++ b/lib/jquery_query_builder/operators/not_in.rb @@ -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 diff --git a/lib/jquery_query_builder/rule.rb b/lib/jquery_query_builder/rule.rb index 93f3eca..91e6b21 100644 --- a/lib/jquery_query_builder/rule.rb +++ b/lib/jquery_query_builder/rule.rb @@ -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 diff --git a/lib/jquery_query_builder/rule_group.rb b/lib/jquery_query_builder/rule_group.rb index b98ae87..9ee83d3 100644 --- a/lib/jquery_query_builder/rule_group.rb +++ b/lib/jquery_query_builder/rule_group.rb @@ -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