squeel 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.rdoc +41 -0
- data/Rakefile +19 -0
- data/lib/core_ext/hash.rb +13 -0
- data/lib/core_ext/symbol.rb +36 -0
- data/lib/squeel.rb +26 -0
- data/lib/squeel/adapters/active_record.rb +6 -0
- data/lib/squeel/adapters/active_record/join_association.rb +90 -0
- data/lib/squeel/adapters/active_record/join_dependency.rb +68 -0
- data/lib/squeel/adapters/active_record/relation.rb +292 -0
- data/lib/squeel/configuration.rb +25 -0
- data/lib/squeel/constants.rb +23 -0
- data/lib/squeel/contexts/join_dependency_context.rb +74 -0
- data/lib/squeel/dsl.rb +31 -0
- data/lib/squeel/nodes.rb +10 -0
- data/lib/squeel/nodes/and.rb +8 -0
- data/lib/squeel/nodes/binary.rb +23 -0
- data/lib/squeel/nodes/function.rb +84 -0
- data/lib/squeel/nodes/join.rb +51 -0
- data/lib/squeel/nodes/key_path.rb +127 -0
- data/lib/squeel/nodes/nary.rb +35 -0
- data/lib/squeel/nodes/not.rb +8 -0
- data/lib/squeel/nodes/operation.rb +23 -0
- data/lib/squeel/nodes/operators.rb +27 -0
- data/lib/squeel/nodes/or.rb +8 -0
- data/lib/squeel/nodes/order.rb +35 -0
- data/lib/squeel/nodes/predicate.rb +49 -0
- data/lib/squeel/nodes/predicate_operators.rb +17 -0
- data/lib/squeel/nodes/stub.rb +113 -0
- data/lib/squeel/nodes/unary.rb +22 -0
- data/lib/squeel/predicate_methods.rb +22 -0
- data/lib/squeel/predicate_methods/function.rb +9 -0
- data/lib/squeel/predicate_methods/predicate.rb +11 -0
- data/lib/squeel/predicate_methods/stub.rb +9 -0
- data/lib/squeel/predicate_methods/symbol.rb +9 -0
- data/lib/squeel/version.rb +3 -0
- data/lib/squeel/visitors.rb +3 -0
- data/lib/squeel/visitors/base.rb +46 -0
- data/lib/squeel/visitors/order_visitor.rb +107 -0
- data/lib/squeel/visitors/predicate_visitor.rb +179 -0
- data/lib/squeel/visitors/select_visitor.rb +103 -0
- data/spec/blueprints/articles.rb +5 -0
- data/spec/blueprints/comments.rb +5 -0
- data/spec/blueprints/notes.rb +3 -0
- data/spec/blueprints/people.rb +4 -0
- data/spec/blueprints/tags.rb +3 -0
- data/spec/console.rb +22 -0
- data/spec/core_ext/symbol_spec.rb +68 -0
- data/spec/helpers/squeel_helper.rb +5 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
- data/spec/squeel/adapters/active_record/join_depdendency_spec.rb +60 -0
- data/spec/squeel/adapters/active_record/relation_spec.rb +437 -0
- data/spec/squeel/contexts/join_dependency_context_spec.rb +43 -0
- data/spec/squeel/dsl_spec.rb +73 -0
- data/spec/squeel/nodes/function_spec.rb +149 -0
- data/spec/squeel/nodes/join_spec.rb +27 -0
- data/spec/squeel/nodes/key_path_spec.rb +92 -0
- data/spec/squeel/nodes/operation_spec.rb +149 -0
- data/spec/squeel/nodes/operators_spec.rb +87 -0
- data/spec/squeel/nodes/order_spec.rb +30 -0
- data/spec/squeel/nodes/predicate_operators_spec.rb +88 -0
- data/spec/squeel/nodes/predicate_spec.rb +92 -0
- data/spec/squeel/nodes/stub_spec.rb +178 -0
- data/spec/squeel/visitors/order_visitor_spec.rb +128 -0
- data/spec/squeel/visitors/predicate_visitor_spec.rb +267 -0
- data/spec/squeel/visitors/select_visitor_spec.rb +115 -0
- data/spec/support/schema.rb +101 -0
- data/squeel.gemspec +44 -0
- metadata +221 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'squeel/constants'
|
2
|
+
require 'squeel/predicate_methods'
|
3
|
+
|
4
|
+
module Squeel
|
5
|
+
module Configuration
|
6
|
+
|
7
|
+
def configure
|
8
|
+
yield self
|
9
|
+
end
|
10
|
+
|
11
|
+
def load_core_extensions(*exts)
|
12
|
+
exts.each do |ext|
|
13
|
+
require "core_ext/#{ext}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def alias_predicate(new_name, existing_name)
|
18
|
+
raise ArgumentError, 'the existing name should be the base name, not an _any/_all variation' if existing_name.to_s =~ /(_any|_all)$/
|
19
|
+
['', '_any', '_all'].each do |suffix|
|
20
|
+
PredicateMethods.class_eval "alias :#{new_name}#{suffix} :#{existing_name}#{suffix} unless defined?(#{new_name}#{suffix})"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Squeel
|
2
|
+
module Constants
|
3
|
+
PREDICATES = [
|
4
|
+
:eq, :eq_any, :eq_all,
|
5
|
+
:not_eq, :not_eq_any, :not_eq_all,
|
6
|
+
:matches, :matches_any, :matches_all,
|
7
|
+
:does_not_match, :does_not_match_any, :does_not_match_all,
|
8
|
+
:lt, :lt_any, :lt_all,
|
9
|
+
:lteq, :lteq_any, :lteq_all,
|
10
|
+
:gt, :gt_any, :gt_all,
|
11
|
+
:gteq, :gteq_any, :gteq_all,
|
12
|
+
:in, :in_any, :in_all,
|
13
|
+
:not_in, :not_in_any, :not_in_all
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
PREDICATE_ALIASES = {
|
17
|
+
:matches => [:like],
|
18
|
+
:does_not_match => [:not_like],
|
19
|
+
:lteq => [:lte],
|
20
|
+
:gteq => [:gte]
|
21
|
+
}.freeze
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
# Because the AR::Associations namespace is insane
|
5
|
+
JoinPart = ActiveRecord::Associations::JoinDependency::JoinPart
|
6
|
+
|
7
|
+
module Contexts
|
8
|
+
class JoinDependencyContext
|
9
|
+
attr_reader :base, :engine, :arel_visitor
|
10
|
+
|
11
|
+
def initialize(join_dependency)
|
12
|
+
@join_dependency = join_dependency
|
13
|
+
@base = join_dependency.join_base
|
14
|
+
@engine = @base.arel_engine
|
15
|
+
@arel_visitor = Arel::Visitors.visitor_for @engine
|
16
|
+
@default_table = Arel::Table.new(@base.table_name, :as => @base.aliased_table_name, :engine => @engine)
|
17
|
+
@tables = Hash.new {|hash, key| hash[key] = get_table(key)}
|
18
|
+
end
|
19
|
+
|
20
|
+
def find(object, parent = @base)
|
21
|
+
if JoinPart === parent
|
22
|
+
object = object.to_sym if String === object
|
23
|
+
case object
|
24
|
+
when Symbol, Nodes::Stub
|
25
|
+
@join_dependency.join_associations.detect { |j|
|
26
|
+
j.reflection.name == object.to_sym && j.parent == parent
|
27
|
+
}
|
28
|
+
when Nodes::Join
|
29
|
+
@join_dependency.join_associations.detect { |j|
|
30
|
+
j.reflection.name == object.name && j.parent == parent &&
|
31
|
+
(object.polymorphic? ? j.reflection.klass == object.klass : true)
|
32
|
+
}
|
33
|
+
else
|
34
|
+
@join_dependency.join_associations.detect { |j|
|
35
|
+
j.reflection == object && j.parent == parent
|
36
|
+
}
|
37
|
+
end
|
38
|
+
else
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def traverse(keypath, parent = @base, include_endpoint = false)
|
44
|
+
parent = @base if keypath.absolute?
|
45
|
+
keypath.path.each do |key|
|
46
|
+
parent = find(key, parent)
|
47
|
+
end
|
48
|
+
parent = find(keypath.endpoint, parent) if include_endpoint
|
49
|
+
|
50
|
+
parent
|
51
|
+
end
|
52
|
+
|
53
|
+
def contextualize(object)
|
54
|
+
@tables[object]
|
55
|
+
end
|
56
|
+
|
57
|
+
def sanitize_sql(conditions, parent)
|
58
|
+
parent.active_record.send(:sanitize_sql, conditions, parent.aliased_table_name)
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def get_table(object)
|
64
|
+
if [Symbol, Nodes::Stub].include?(object.class)
|
65
|
+
Arel::Table.new(object.to_sym, :engine => @engine)
|
66
|
+
elsif object.respond_to?(:aliased_table_name)
|
67
|
+
Arel::Table.new(object.table_name, :as => object.aliased_table_name, :engine => @engine)
|
68
|
+
else
|
69
|
+
@default_table
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/squeel/dsl.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Squeel
|
2
|
+
class DSL
|
3
|
+
|
4
|
+
Squeel.evil_things do
|
5
|
+
(instance_methods + private_instance_methods).each do |method|
|
6
|
+
unless method.to_s =~ /^(__|instance_eval)/
|
7
|
+
undef_method method
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.evaluate(&block)
|
13
|
+
if block.arity > 0
|
14
|
+
yield self.new
|
15
|
+
else
|
16
|
+
self.new.instance_eval(&block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(method_id, *args)
|
21
|
+
if args.empty?
|
22
|
+
Nodes::Stub.new method_id
|
23
|
+
elsif (args.size == 1) && (Class === args[0])
|
24
|
+
Nodes::Join.new(method_id, Arel::InnerJoin, args[0])
|
25
|
+
else
|
26
|
+
Nodes::Function.new method_id, args
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
data/lib/squeel/nodes.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'squeel/nodes/stub'
|
2
|
+
require 'squeel/nodes/key_path'
|
3
|
+
require 'squeel/nodes/predicate'
|
4
|
+
require 'squeel/nodes/function'
|
5
|
+
require 'squeel/nodes/operation'
|
6
|
+
require 'squeel/nodes/order'
|
7
|
+
require 'squeel/nodes/and'
|
8
|
+
require 'squeel/nodes/or'
|
9
|
+
require 'squeel/nodes/not'
|
10
|
+
require 'squeel/nodes/join'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'squeel/nodes/predicate_operators'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Nodes
|
5
|
+
class Binary
|
6
|
+
include PredicateOperators
|
7
|
+
|
8
|
+
attr_reader :left, :right
|
9
|
+
|
10
|
+
def initialize(left, right)
|
11
|
+
@left, @right = left, right
|
12
|
+
end
|
13
|
+
|
14
|
+
def eql?(other)
|
15
|
+
self.class == other.class &&
|
16
|
+
self.left == other.left &&
|
17
|
+
self.right == other.right
|
18
|
+
end
|
19
|
+
|
20
|
+
alias :== :eql?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'squeel/predicate_methods'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Nodes
|
5
|
+
class Function
|
6
|
+
|
7
|
+
include PredicateMethods
|
8
|
+
include Operators
|
9
|
+
|
10
|
+
attr_reader :name, :args, :alias
|
11
|
+
|
12
|
+
def initialize(name, args)
|
13
|
+
@name, @args = name, args
|
14
|
+
end
|
15
|
+
|
16
|
+
def as(alias_name)
|
17
|
+
@alias = alias_name.to_s
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def ==(value)
|
22
|
+
Predicate.new self, :eq, value
|
23
|
+
end
|
24
|
+
|
25
|
+
def asc
|
26
|
+
Order.new self, 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def desc
|
30
|
+
Order.new self, -1
|
31
|
+
end
|
32
|
+
|
33
|
+
# Won't work on Ruby 1.8.x so need to do this conditionally
|
34
|
+
define_method('!=') do |value|
|
35
|
+
Predicate.new(self, :not_eq, value)
|
36
|
+
end if respond_to?('!=')
|
37
|
+
|
38
|
+
def ^(value)
|
39
|
+
Predicate.new self, :not_eq, value
|
40
|
+
end
|
41
|
+
|
42
|
+
def >>(value)
|
43
|
+
Predicate.new self, :in, value
|
44
|
+
end
|
45
|
+
|
46
|
+
def <<(value)
|
47
|
+
Predicate.new self, :not_in, value
|
48
|
+
end
|
49
|
+
|
50
|
+
def =~(value)
|
51
|
+
Predicate.new self, :matches, value
|
52
|
+
end
|
53
|
+
|
54
|
+
# Won't work on Ruby 1.8.x so need to do this conditionally
|
55
|
+
define_method('!~') do |value|
|
56
|
+
Predicate.new(self, :does_not_match, value)
|
57
|
+
end if respond_to?('!~')
|
58
|
+
|
59
|
+
def >(value)
|
60
|
+
Predicate.new self, :gt, value
|
61
|
+
end
|
62
|
+
|
63
|
+
def >=(value)
|
64
|
+
Predicate.new self, :gteq, value
|
65
|
+
end
|
66
|
+
|
67
|
+
def <(value)
|
68
|
+
Predicate.new self, :lt, value
|
69
|
+
end
|
70
|
+
|
71
|
+
def <=(value)
|
72
|
+
Predicate.new self, :lteq, value
|
73
|
+
end
|
74
|
+
|
75
|
+
# expand_hash_conditions_for_aggregates assumes our hash keys can be
|
76
|
+
# converted to symbols, so this has to be implemented, but it doesn't
|
77
|
+
# really have to do anything useful.
|
78
|
+
def to_sym
|
79
|
+
nil
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Squeel
|
2
|
+
module Nodes
|
3
|
+
class Join
|
4
|
+
attr_reader :name, :type, :klass
|
5
|
+
|
6
|
+
def initialize(name, type = Arel::InnerJoin, klass = nil)
|
7
|
+
@name, @type = name, type
|
8
|
+
@klass = convert_to_class(klass) if klass
|
9
|
+
end
|
10
|
+
|
11
|
+
def inner
|
12
|
+
@type = Arel::InnerJoin
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def outer
|
17
|
+
@type = Arel::OuterJoin
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def klass=(class_or_class_name)
|
22
|
+
@klass = convert_to_class(class_or_class_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
def polymorphic?
|
26
|
+
@klass
|
27
|
+
end
|
28
|
+
|
29
|
+
# expand_hash_conditions_for_aggregates assumes our hash keys can be
|
30
|
+
# converted to symbols, so this has to be implemented, but it doesn't
|
31
|
+
# really have to do anything useful.
|
32
|
+
def to_sym
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def convert_to_class(value)
|
39
|
+
case value
|
40
|
+
when String, Symbol
|
41
|
+
Kernel.const_get(value)
|
42
|
+
when Class
|
43
|
+
value
|
44
|
+
else
|
45
|
+
raise ArgumentError, "#{value} cannot be converted to a Class"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'squeel/nodes/operators'
|
2
|
+
require 'squeel/nodes/predicate_operators'
|
3
|
+
|
4
|
+
module Squeel
|
5
|
+
module Nodes
|
6
|
+
class KeyPath
|
7
|
+
include PredicateOperators
|
8
|
+
include Operators
|
9
|
+
|
10
|
+
attr_reader :path, :endpoint
|
11
|
+
|
12
|
+
def initialize(path, endpoint, absolute = false)
|
13
|
+
@path, @endpoint = path, endpoint
|
14
|
+
@path = [@path] unless Array === @path
|
15
|
+
@endpoint = Stub.new(@endpoint) if Symbol === @endpoint
|
16
|
+
@absolute = absolute
|
17
|
+
end
|
18
|
+
|
19
|
+
def absolute?
|
20
|
+
@absolute
|
21
|
+
end
|
22
|
+
|
23
|
+
def eql?(other)
|
24
|
+
self.class == other.class &&
|
25
|
+
self.path == other.path &&
|
26
|
+
self.endpoint.eql?(other.endpoint) &&
|
27
|
+
self.absolute? == other.absolute?
|
28
|
+
end
|
29
|
+
|
30
|
+
def |(other)
|
31
|
+
endpoint.respond_to?(:|) ? super : no_method_error(:|)
|
32
|
+
end
|
33
|
+
|
34
|
+
def &(other)
|
35
|
+
endpoint.respond_to?(:&) ? super : no_method_error(:&)
|
36
|
+
end
|
37
|
+
|
38
|
+
def -@
|
39
|
+
endpoint.respond_to?(:-@) ? super : no_method_error(:-@)
|
40
|
+
end
|
41
|
+
|
42
|
+
def +(other)
|
43
|
+
endpoint.respond_to?(:+) ? super : no_method_error(:+)
|
44
|
+
end
|
45
|
+
|
46
|
+
def -(other)
|
47
|
+
endpoint.respond_to?(:-) ? super : no_method_error(:-)
|
48
|
+
end
|
49
|
+
|
50
|
+
def *(other)
|
51
|
+
endpoint.respond_to?(:*) ? super : no_method_error(:*)
|
52
|
+
end
|
53
|
+
|
54
|
+
def /(other)
|
55
|
+
endpoint.respond_to?(:/) ? super : no_method_error(:/)
|
56
|
+
end
|
57
|
+
|
58
|
+
def op(operator, other)
|
59
|
+
endpoint.respond_to?(:op) ? super : no_method_error(:/)
|
60
|
+
end
|
61
|
+
|
62
|
+
def ~
|
63
|
+
@absolute = true
|
64
|
+
self
|
65
|
+
end
|
66
|
+
|
67
|
+
# To let these fall through to the endpoint via method_missing
|
68
|
+
instance_methods.grep(/^(==|=~|!~)$/) do |operator|
|
69
|
+
undef_method operator
|
70
|
+
end
|
71
|
+
|
72
|
+
def hash
|
73
|
+
[self.class, endpoint, *path].hash
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_sym
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
def %(val)
|
81
|
+
case endpoint
|
82
|
+
when Stub, Function
|
83
|
+
eq(val)
|
84
|
+
self
|
85
|
+
else
|
86
|
+
endpoint % val
|
87
|
+
self
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def path_with_endpoint
|
92
|
+
path + [endpoint]
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_s
|
96
|
+
path.map(&:to_s).join('.') << ".#{endpoint}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def method_missing(method_id, *args)
|
100
|
+
super if method_id == :to_ary
|
101
|
+
if endpoint.respond_to? method_id
|
102
|
+
@endpoint = @endpoint.send(method_id, *args)
|
103
|
+
self
|
104
|
+
elsif Stub === endpoint
|
105
|
+
@path << endpoint.symbol
|
106
|
+
if args.empty?
|
107
|
+
@endpoint = Stub.new(method_id)
|
108
|
+
elsif (args.size == 1) && (Class === args[0])
|
109
|
+
@endpoint = Join.new(method_id, Arel::InnerJoin, args[0])
|
110
|
+
else
|
111
|
+
@endpoint = Nodes::Function.new method_id, args
|
112
|
+
end
|
113
|
+
self
|
114
|
+
else
|
115
|
+
super
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def no_method_error(method_id)
|
122
|
+
raise NoMethodError, "undefined method `#{method_id}' for #{self}:#{self.class}"
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|