squeel 1.0.18 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +2 -7
- data/CHANGELOG.md +9 -0
- data/Gemfile +2 -2
- data/README.md +223 -147
- data/lib/generators/templates/squeel.rb +1 -1
- data/lib/squeel/adapters/active_record.rb +1 -1
- data/lib/squeel/adapters/active_record/4.0/compat.rb +17 -0
- data/lib/squeel/adapters/active_record/4.0/context.rb +1 -0
- data/lib/squeel/adapters/active_record/4.0/preloader_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/4.0/relation_extensions.rb +126 -0
- data/lib/squeel/adapters/active_record/base_extensions.rb +1 -1
- data/lib/squeel/adapters/active_record/context.rb +10 -10
- data/lib/squeel/adapters/active_record/relation_extensions.rb +24 -16
- data/lib/squeel/configuration.rb +1 -0
- data/lib/squeel/constants.rb +2 -2
- data/lib/squeel/context.rb +2 -2
- data/lib/squeel/dsl.rb +1 -1
- data/lib/squeel/nodes.rb +2 -0
- data/lib/squeel/nodes/binary.rb +1 -1
- data/lib/squeel/nodes/function.rb +5 -5
- data/lib/squeel/nodes/join.rb +2 -2
- data/lib/squeel/nodes/key_path.rb +10 -5
- data/lib/squeel/nodes/literal.rb +1 -1
- data/lib/squeel/nodes/nary.rb +5 -7
- data/lib/squeel/nodes/node.rb +6 -0
- data/lib/squeel/nodes/operation.rb +1 -1
- data/lib/squeel/nodes/order.rb +1 -1
- data/lib/squeel/nodes/predicate.rb +5 -5
- data/lib/squeel/nodes/predicate_methods.rb +11 -2
- data/lib/squeel/nodes/sifter.rb +2 -2
- data/lib/squeel/nodes/stub.rb +2 -2
- data/lib/squeel/nodes/unary.rb +1 -1
- data/lib/squeel/version.rb +1 -1
- data/lib/squeel/visitors/predicate_visitation.rb +6 -6
- data/lib/squeel/visitors/predicate_visitor.rb +1 -1
- data/lib/squeel/visitors/visitor.rb +20 -20
- data/spec/spec_helper.rb +6 -4
- data/spec/squeel/adapters/active_record/base_extensions_spec.rb +6 -6
- data/spec/squeel/adapters/active_record/relation_extensions_spec.rb +55 -24
- data/spec/squeel/core_ext/symbol_spec.rb +2 -2
- data/spec/squeel/nodes/key_path_spec.rb +3 -3
- data/spec/squeel/nodes/predicate_operators_spec.rb +4 -4
- data/spec/squeel/visitors/predicate_visitor_spec.rb +11 -11
- data/spec/squeel/visitors/visitor_spec.rb +9 -9
- data/spec/support/models.rb +25 -7
- data/spec/support/schema.rb +1 -1
- data/squeel.gemspec +4 -4
- metadata +19 -12
@@ -5,7 +5,7 @@ Squeel.configure do |config|
|
|
5
5
|
# config.load_core_extensions :hash
|
6
6
|
|
7
7
|
# To load symbol extensions (for a subset of the old MetaWhere functionality,
|
8
|
-
# via
|
8
|
+
# via Arel predicate methods on Symbols: :name.matches, etc):
|
9
9
|
#
|
10
10
|
# NOTE: Not recommended. Learn the new DSL. Use it. Love it.
|
11
11
|
#
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'squeel/adapters/active_record/compat'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Associations
|
5
|
+
class AssociationScope
|
6
|
+
# 4.0's eval_scope doesn't play nicely with different bases. Need to do
|
7
|
+
# a similar workaround to the one for AR::Relation#merge, visiting it
|
8
|
+
def eval_scope(klass, scope)
|
9
|
+
if scope.is_a?(Relation)
|
10
|
+
scope
|
11
|
+
else
|
12
|
+
klass.unscoped.instance_exec(owner, &scope).visited
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'squeel/adapters/active_record/context'
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'squeel/adapters/active_record/preloader_extensions'
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'squeel/adapters/active_record/relation_extensions'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
module RelationExtensions
|
7
|
+
|
8
|
+
# where.not is a pain. It calls the private `build_where` method on its
|
9
|
+
# scope, and since ActiveRecord::Relation already includes the original
|
10
|
+
# ActiveRecord::QueryMethods module, we have to find a way to trick the
|
11
|
+
# scope passed to the WhereChain initializer into having the original
|
12
|
+
# behavior. This is a way to do it that avoids using alias_method_chain.
|
13
|
+
module WhereChainCompatibility
|
14
|
+
include ::ActiveRecord::QueryMethods
|
15
|
+
define_method :build_where,
|
16
|
+
::ActiveRecord::QueryMethods.instance_method(:build_where)
|
17
|
+
end
|
18
|
+
|
19
|
+
def where(opts = :chain, *rest)
|
20
|
+
if block_given?
|
21
|
+
super(DSL.eval &Proc.new)
|
22
|
+
else
|
23
|
+
if opts == :chain
|
24
|
+
scope = spawn
|
25
|
+
scope.extend(WhereChainCompatibility)
|
26
|
+
::ActiveRecord::QueryMethods::WhereChain.new(scope)
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_arel
|
34
|
+
arel = Arel::SelectManager.new(table.engine, table)
|
35
|
+
|
36
|
+
build_joins(arel, joins_values) unless joins_values.empty?
|
37
|
+
|
38
|
+
collapse_wheres(arel, where_visit((where_values - ['']).uniq))
|
39
|
+
|
40
|
+
arel.having(*having_visit(having_values.uniq.reject{|h| h.blank?})) unless having_values.empty?
|
41
|
+
|
42
|
+
arel.take(connection.sanitize_limit(limit_value)) if limit_value
|
43
|
+
arel.skip(offset_value.to_i) if offset_value
|
44
|
+
|
45
|
+
arel.group(*group_visit(group_values.uniq.reject{|g| g.blank?})) unless group_values.empty?
|
46
|
+
|
47
|
+
build_order(arel)
|
48
|
+
|
49
|
+
build_select(arel, select_visit(select_values.uniq))
|
50
|
+
|
51
|
+
arel.distinct(distinct_value)
|
52
|
+
arel.from(from_visit(from_value)) if from_value
|
53
|
+
arel.lock(lock_value) if lock_value
|
54
|
+
|
55
|
+
arel
|
56
|
+
end
|
57
|
+
|
58
|
+
def build_order(arel)
|
59
|
+
orders = order_visit(dehashified_order_values)
|
60
|
+
orders = reverse_sql_order(attrs_to_orderings(orders)) if reverse_order_value
|
61
|
+
|
62
|
+
orders = orders.uniq.reject(&:blank?).flat_map do |order|
|
63
|
+
case order
|
64
|
+
when Symbol
|
65
|
+
table[order].asc
|
66
|
+
when Hash
|
67
|
+
order.map { |field, dir| table[field].send(dir) }
|
68
|
+
else
|
69
|
+
order
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
arel.order(*orders) unless orders.empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
def build_from
|
77
|
+
opts, name = from_value
|
78
|
+
case opts
|
79
|
+
when Relation
|
80
|
+
name ||= 'subquery'
|
81
|
+
opts.arel.as(name.to_s)
|
82
|
+
else
|
83
|
+
opts
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# This is copied directly from 4.0.0's implementation, but adds an extra
|
88
|
+
# exclusion for Squeel::Nodes::Node to fix #248. Can be removed if/when
|
89
|
+
# rails/rails#11439 is merged.
|
90
|
+
def order!(*args)
|
91
|
+
args.flatten!
|
92
|
+
validate_order_args args
|
93
|
+
|
94
|
+
references = args.reject { |arg|
|
95
|
+
Arel::Node === arg || Squeel::Nodes::Node === arg
|
96
|
+
}
|
97
|
+
references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact!
|
98
|
+
references!(references) if references.any?
|
99
|
+
|
100
|
+
# if a symbol is given we prepend the quoted table name
|
101
|
+
args = args.map { |arg|
|
102
|
+
arg.is_a?(Symbol) ? "#{quoted_table_name}.#{arg} ASC" : arg
|
103
|
+
}
|
104
|
+
|
105
|
+
self.order_values = args + self.order_values
|
106
|
+
self
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def dehashified_order_values
|
112
|
+
order_values.map { |o|
|
113
|
+
if Hash === o && o.values.all? { |v| [:asc, :desc].include?(v) }
|
114
|
+
o.map { |field, dir| table[field].send(dir) }
|
115
|
+
else
|
116
|
+
o
|
117
|
+
end
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::RelationExtensions
|
@@ -9,7 +9,7 @@ module Squeel
|
|
9
9
|
|
10
10
|
def sifter(name = nil)
|
11
11
|
if Symbol === name && block_given?
|
12
|
-
singleton_class.send :define_method, name,
|
12
|
+
singleton_class.send :define_method, "sifter_#{name}",
|
13
13
|
lambda {|*args| DSL.exec(*args, &Proc.new)}
|
14
14
|
else
|
15
15
|
raise ArgumentError, "A name and block are required"
|
@@ -44,6 +44,16 @@ module Squeel
|
|
44
44
|
parent
|
45
45
|
end
|
46
46
|
|
47
|
+
def classify(object)
|
48
|
+
if Class === object
|
49
|
+
object
|
50
|
+
elsif object.respond_to? :base_klass
|
51
|
+
object.base_klass
|
52
|
+
else
|
53
|
+
raise ArgumentError, "#{object} can't be converted to a class"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
47
57
|
private
|
48
58
|
|
49
59
|
def get_table(object)
|
@@ -58,16 +68,6 @@ module Squeel
|
|
58
68
|
end
|
59
69
|
end
|
60
70
|
|
61
|
-
def classify(object)
|
62
|
-
if Class === object
|
63
|
-
object
|
64
|
-
elsif object.respond_to? :active_record
|
65
|
-
object.active_record
|
66
|
-
else
|
67
|
-
raise ArgumentError, "#{object} can't be converted to a class"
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
71
|
def get_arel_visitor
|
72
72
|
@engine.connection.visitor
|
73
73
|
end
|
@@ -14,7 +14,12 @@ module Squeel
|
|
14
14
|
# because the default #reset already does this, despite never setting
|
15
15
|
# it anywhere that I can find. Serendipity, I say!
|
16
16
|
def join_dependency
|
17
|
-
@join_dependency ||= (
|
17
|
+
@join_dependency ||= (
|
18
|
+
build_join_dependency(
|
19
|
+
Arel::SelectManager.new(table.engine, table),
|
20
|
+
joins_values
|
21
|
+
) && @join_dependency
|
22
|
+
)
|
18
23
|
end
|
19
24
|
|
20
25
|
%w(where having group order select from).each do |visitor|
|
@@ -40,6 +45,7 @@ module Squeel
|
|
40
45
|
merge_resolving_duplicate_squeel_equalities(r)
|
41
46
|
end
|
42
47
|
else
|
48
|
+
puts r.inspect if r.is_a?(Proc)
|
43
49
|
super(r)
|
44
50
|
end
|
45
51
|
end
|
@@ -49,12 +55,12 @@ module Squeel
|
|
49
55
|
end
|
50
56
|
|
51
57
|
def visit!
|
52
|
-
|
53
|
-
|
54
|
-
# FIXME: AR barfs on
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
+
self.where_values = where_visit((where_values - ['']).uniq)
|
59
|
+
self.having_values = having_visit(having_values.uniq.reject{|h| h.blank?})
|
60
|
+
# FIXME: AR barfs on Arel attributes in group_values. Workaround?
|
61
|
+
# self.group_values = group_visit(group_values.uniq.reject{|g| g.blank?})
|
62
|
+
self.order_values = order_visit(order_values.uniq.reject{|o| o.blank?})
|
63
|
+
self.select_values = select_visit(select_values.uniq)
|
58
64
|
|
59
65
|
self
|
60
66
|
end
|
@@ -100,22 +106,22 @@ module Squeel
|
|
100
106
|
buckets = joins.group_by do |join|
|
101
107
|
case join
|
102
108
|
when String
|
103
|
-
|
109
|
+
:string_join
|
104
110
|
when Hash, Symbol, Array, Nodes::Stub, Nodes::Join, Nodes::KeyPath
|
105
|
-
|
111
|
+
:association_join
|
106
112
|
when JoinAssociation
|
107
|
-
|
113
|
+
:stashed_join
|
108
114
|
when Arel::Nodes::Join
|
109
|
-
|
115
|
+
:join_node
|
110
116
|
else
|
111
117
|
raise 'unknown class: %s' % join.class.name
|
112
118
|
end
|
113
119
|
end
|
114
120
|
|
115
|
-
association_joins = buckets[
|
116
|
-
stashed_association_joins = buckets[
|
117
|
-
join_nodes = (buckets[
|
118
|
-
string_joins = (buckets[
|
121
|
+
association_joins = buckets[:association_join] || []
|
122
|
+
stashed_association_joins = buckets[:stashed_join] || []
|
123
|
+
join_nodes = (buckets[:join_node] || []).uniq
|
124
|
+
string_joins = (buckets[:string_join] || []).map { |x|
|
119
125
|
x.strip
|
120
126
|
}.uniq
|
121
127
|
|
@@ -140,6 +146,8 @@ module Squeel
|
|
140
146
|
|
141
147
|
manager
|
142
148
|
end
|
149
|
+
# For 4.x adapters
|
150
|
+
alias :build_joins :build_join_dependency
|
143
151
|
|
144
152
|
def includes(*args)
|
145
153
|
if block_given? && args.empty?
|
@@ -335,7 +343,7 @@ module Squeel
|
|
335
343
|
# DB for debug purposes without actually running the query.
|
336
344
|
def debug_sql
|
337
345
|
if eager_loading?
|
338
|
-
including = (
|
346
|
+
including = (eager_load_values + includes_values).uniq
|
339
347
|
join_dependency = JoinDependency.new(@klass, including, [])
|
340
348
|
construct_relation_for_association_find(join_dependency).to_sql
|
341
349
|
else
|
data/lib/squeel/configuration.rb
CHANGED
@@ -33,6 +33,7 @@ module Squeel
|
|
33
33
|
# @param [Symbol] sym1 :hash or :symbol
|
34
34
|
# @param [Symbol] sym2 :hash or :symbol
|
35
35
|
def load_core_extensions(*exts)
|
36
|
+
deprecate 'Core extensions are deprecated and will be removed in Squeel 2.0.'
|
36
37
|
exts.each do |ext|
|
37
38
|
require "squeel/core_ext/#{ext}"
|
38
39
|
end
|
data/lib/squeel/constants.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Squeel
|
2
|
-
# Defines the default list of
|
2
|
+
# Defines the default list of Arel predicates and predicate aliases
|
3
3
|
module Constants
|
4
4
|
PREDICATES = [
|
5
5
|
:eq, :eq_any, :eq_all,
|
@@ -21,4 +21,4 @@ module Squeel
|
|
21
21
|
:gteq => [:gte]
|
22
22
|
}.freeze
|
23
23
|
end
|
24
|
-
end
|
24
|
+
end
|
data/lib/squeel/context.rb
CHANGED
@@ -42,7 +42,7 @@ module Squeel
|
|
42
42
|
end
|
43
43
|
|
44
44
|
# This method, as implemented, just makes use of the table cache, which will
|
45
|
-
# call get_table, where the real work of getting the
|
45
|
+
# call get_table, where the real work of getting the Arel Table occurs.
|
46
46
|
#
|
47
47
|
# @param object A contextualizable object (this will depend on the subclass's implementation)
|
48
48
|
# @return [Arel::Table] A table corresponding to the object param
|
@@ -72,4 +72,4 @@ module Squeel
|
|
72
72
|
end
|
73
73
|
|
74
74
|
end
|
75
|
-
end
|
75
|
+
end
|
data/lib/squeel/dsl.rb
CHANGED
@@ -63,7 +63,7 @@ module Squeel
|
|
63
63
|
@caller.instance_eval &block
|
64
64
|
end
|
65
65
|
|
66
|
-
# Shorthand for creating
|
66
|
+
# Shorthand for creating Arel SqlLiteral nodes.
|
67
67
|
#
|
68
68
|
# @param [String] string The string to convert to an SQL literal.
|
69
69
|
# @return [Arel::Nodes::SqlLiteral] The SQL literal.
|
data/lib/squeel/nodes.rb
CHANGED
data/lib/squeel/nodes/binary.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Squeel
|
2
2
|
module Nodes
|
3
3
|
# A node that represents an SQL function call
|
4
|
-
class Function
|
4
|
+
class Function < Node
|
5
5
|
|
6
6
|
include PredicateMethods
|
7
7
|
include PredicateOperators
|
@@ -22,7 +22,7 @@ module Squeel
|
|
22
22
|
alias :<= :lteq
|
23
23
|
|
24
24
|
# @return [Symbol] The name of the SQL function to be called
|
25
|
-
attr_reader :
|
25
|
+
attr_reader :function_name
|
26
26
|
|
27
27
|
# @return [Array] The arguments to be passed to the SQL function
|
28
28
|
attr_reader :args
|
@@ -30,8 +30,8 @@ module Squeel
|
|
30
30
|
# Create a node representing an SQL Function with the given name and arguments
|
31
31
|
# @param [Symbol] name The function name
|
32
32
|
# @param [Array] args The array of arguments to pass to the function.
|
33
|
-
def initialize(
|
34
|
-
@
|
33
|
+
def initialize(function_name, args)
|
34
|
+
@function_name, @args = function_name, args
|
35
35
|
end
|
36
36
|
|
37
37
|
# expand_hash_conditions_for_aggregates assumes our hash keys can be
|
@@ -48,7 +48,7 @@ module Squeel
|
|
48
48
|
|
49
49
|
def eql?(other)
|
50
50
|
self.class == other.class &&
|
51
|
-
self.
|
51
|
+
self.function_name.eql?(other.function_name) &&
|
52
52
|
self.args.eql?(other.args)
|
53
53
|
end
|
54
54
|
|
data/lib/squeel/nodes/join.rb
CHANGED
@@ -3,7 +3,7 @@ require 'active_support/core_ext/module'
|
|
3
3
|
module Squeel
|
4
4
|
module Nodes
|
5
5
|
# A node representing a joined association
|
6
|
-
class Join
|
6
|
+
class Join < Node
|
7
7
|
undef_method :id if method_defined?(:id)
|
8
8
|
|
9
9
|
attr_reader :_join
|
@@ -12,7 +12,7 @@ module Squeel
|
|
12
12
|
|
13
13
|
# Create a new Join node
|
14
14
|
# @param [Symbol] name The association name
|
15
|
-
# @param [Arel::InnerJoin, Arel::OuterJoin] type The
|
15
|
+
# @param [Arel::InnerJoin, Arel::OuterJoin] type The Arel join class
|
16
16
|
# @param [Class, String, Symbol] klass The polymorphic belongs_to class or class name
|
17
17
|
def initialize(name, type = Arel::InnerJoin, klass = nil)
|
18
18
|
@_join = Polyamorous::Join.new(name, type, klass)
|
@@ -4,7 +4,7 @@ module Squeel
|
|
4
4
|
module Nodes
|
5
5
|
# A node that stores a path of keys (of Symbol, Stub, or Join values) and
|
6
6
|
# an endpoint. Used similarly to a nested hash.
|
7
|
-
class KeyPath
|
7
|
+
class KeyPath < Node
|
8
8
|
include PredicateOperators
|
9
9
|
include Operators
|
10
10
|
|
@@ -179,12 +179,17 @@ module Squeel
|
|
179
179
|
|
180
180
|
# Appends to the KeyPath or delegates to the endpoint, as appropriate
|
181
181
|
# @return [KeyPath] The updated KeyPath
|
182
|
-
def method_missing(method_id, *args)
|
182
|
+
def method_missing(method_id, *args, &block)
|
183
183
|
super if method_id == :to_ary
|
184
184
|
|
185
|
-
if endpoint.respond_to?
|
186
|
-
|
187
|
-
|
185
|
+
if endpoint.respond_to?(method_id)
|
186
|
+
if Predicate === endpoint && method_id == :==
|
187
|
+
false
|
188
|
+
else
|
189
|
+
# TODO: We really should not mutate here.
|
190
|
+
self.endpoint = endpoint.send(method_id, *args)
|
191
|
+
self
|
192
|
+
end
|
188
193
|
elsif Stub === endpoint || Join === endpoint
|
189
194
|
if args.empty?
|
190
195
|
@path << Stub.new(method_id)
|