squeel 1.0.18 → 1.1.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/.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)
|