Skip to content

Instantly share code, notes, and snippets.

@oleganza
Forked from anonymous/gist:28712
Created November 28, 2008 00:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oleganza/29871 to your computer and use it in GitHub Desktop.
Save oleganza/29871 to your computer and use it in GitHub Desktop.
# Sometimes :symbol.operator looks nice.
# I generally hate "global variables programming",
# but this extension seems to be safe and handy.
def experimental_by_oleganza
# arrays for OR, hashes for AND with in-Symbol operators
all([{:name => ["john", "jane"]}, {:age.gr => 3, :age.lt => 65}])
# same as above, but with optional :any, :all keys (:any for OR, :all for AND)
all([{:name => ["john", "jane"]}, {:any => [{:age.gt => 3}, {:parental_control.not => :nil}], :age.lt => 65}])
# minimalistic hashes with :all, :any quantors
all(:any => {
:name => ["john", "jane"],
:all => {
:any => {
:age.gt => 3,
:parental_control.not => :nil
},
:age.lt => 65
}
}
)
# ParseTree/RubyToRuby version
all { |p|
["john", "jane"].include?(p.name) or
(
(p.age > 3 or p.parental_control != nil) and
p.age < 65
)
}
# Maybe we should adopt every flavor to be detected automatically.
# Also, it is a good idea to construct proc's from the AST for in-memory operations
end
module Sql
module Syntax
def self.serialize(sexp)
send(*sexp)
end
private
def self.and(*args)
"(" + args.map do |expr|
send(*expr)
end.join(" AND ") + ")"
end
def self.or(*args)
"(" + args.map do |expr|
send(*expr)
end.join(" OR ") + ")"
end
def self.gt(field, value)
"#{field} > #{value}"
end
def self.lt(field, value)
"#{field} < #{value}"
end
def self.eq(field, value)
"#{field} = #{value}"
end
end
end
class UnboundCondition
def self.gt(field, value)
new(:gt, field, value)
end
def self.lt(field, value)
new(:lt, field, value)
end
def self.eq(field, value)
new(:eq, field, value)
end
def initialize(operator, field, value)
@operator = operator
@field = field
@value = value
end
def and(*others)
AndExpression.new(self, *others)
end
def or(*others)
OrExpression.new(self, *others)
end
def to_a
[@operator, @field, @value]
end
end
class Expression
def initialize(*values)
@values = values
end
def or(*values)
OrExpression.new(self, *values)
end
def and(*values)
AndExpression.new(self, *values)
end
def to_a
[self.class.operator, *@values.map { |value| value.to_a }]
end
private
def self.operator
raise NotImplementedError.new("Expression is an abstract class")
end
end
class AndExpression < Expression
private
def self.operator
:and
end
end
class OrExpression < Expression
private
def self.operator
:or
end
end
class Query
class Conditions
def initialize(expression = nil)
@expression = expression
end
def and(expression)
append(:and, expression)
self
end
def or(expression)
append(:or, expression)
self
end
def set(expression)
@expression = expression
self
end
def to_a
@expression.to_a
end
private
def append(method, expression)
if @expression
@expression = case method
when :and then @expression.and(expression)
when :or then @expression.or(expression)
else raise NotImplementedError.new("Method #{method} unsupported")
end
else
@expression = expression
end
end
end
def initialize(root)
@root = root
@conditions = Conditions.new
end
def conditions
@conditions
end
def conditions=(expression)
@conditions = Conditions.new(expression)
end
end
a = [:or,
[:or,
[:eq, :name, "John"],
[:eq, :name, "Jane"]
],
[:and,
[:gt, :age, 3],
[:lt, :age, 65]
]
]
p Sql::Syntax.serialize(a)
q = Query.new(Object)
john = UnboundCondition.eq(:name, "John")
jane = UnboundCondition.eq(:name, "Jane")
toddler = UnboundCondition.gt(:age, 3)
not_senior = UnboundCondition.lt(:age, 65)
q.conditions.set(john.or(jane)).or(toddler.and(not_senior))
p Sql::Syntax.serialize(q.conditions.to_a)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment