squeel 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|