squeel_rbg 0.8.2
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/.yardopts +3 -0
- data/Gemfile +13 -0
- data/LICENSE +20 -0
- data/README.md +398 -0
- data/Rakefile +19 -0
- data/lib/core_ext/hash.rb +13 -0
- data/lib/core_ext/symbol.rb +39 -0
- data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
- data/lib/squeel/adapters/active_record/3.0/compat.rb +142 -0
- data/lib/squeel/adapters/active_record/3.0/context.rb +66 -0
- data/lib/squeel/adapters/active_record/3.0/join_association.rb +54 -0
- data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +84 -0
- data/lib/squeel/adapters/active_record/3.0/relation.rb +327 -0
- data/lib/squeel/adapters/active_record/context.rb +66 -0
- data/lib/squeel/adapters/active_record/join_association.rb +44 -0
- data/lib/squeel/adapters/active_record/join_dependency.rb +83 -0
- data/lib/squeel/adapters/active_record/preloader.rb +21 -0
- data/lib/squeel/adapters/active_record/relation.rb +351 -0
- data/lib/squeel/adapters/active_record.rb +28 -0
- data/lib/squeel/configuration.rb +54 -0
- data/lib/squeel/constants.rb +24 -0
- data/lib/squeel/context.rb +67 -0
- data/lib/squeel/dsl.rb +86 -0
- data/lib/squeel/nodes/aliasing.rb +13 -0
- data/lib/squeel/nodes/and.rb +9 -0
- data/lib/squeel/nodes/as.rb +14 -0
- data/lib/squeel/nodes/binary.rb +32 -0
- data/lib/squeel/nodes/function.rb +66 -0
- data/lib/squeel/nodes/join.rb +113 -0
- data/lib/squeel/nodes/key_path.rb +192 -0
- data/lib/squeel/nodes/nary.rb +45 -0
- data/lib/squeel/nodes/not.rb +9 -0
- data/lib/squeel/nodes/operation.rb +32 -0
- data/lib/squeel/nodes/operators.rb +43 -0
- data/lib/squeel/nodes/or.rb +9 -0
- data/lib/squeel/nodes/order.rb +53 -0
- data/lib/squeel/nodes/predicate.rb +71 -0
- data/lib/squeel/nodes/predicate_operators.rb +29 -0
- data/lib/squeel/nodes/stub.rb +125 -0
- data/lib/squeel/nodes/unary.rb +28 -0
- data/lib/squeel/nodes.rb +17 -0
- data/lib/squeel/predicate_methods.rb +14 -0
- data/lib/squeel/version.rb +3 -0
- data/lib/squeel/visitors/attribute_visitor.rb +191 -0
- data/lib/squeel/visitors/base.rb +112 -0
- data/lib/squeel/visitors/predicate_visitor.rb +319 -0
- data/lib/squeel/visitors/symbol_visitor.rb +48 -0
- data/lib/squeel/visitors.rb +3 -0
- data/lib/squeel.rb +28 -0
- data/lib/squeel_rbg.rb +5 -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 +75 -0
- data/spec/helpers/squeel_helper.rb +21 -0
- data/spec/spec_helper.rb +66 -0
- data/spec/squeel/adapters/active_record/context_spec.rb +44 -0
- data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
- data/spec/squeel/adapters/active_record/join_dependency_spec.rb +66 -0
- data/spec/squeel/adapters/active_record/relation_spec.rb +627 -0
- data/spec/squeel/dsl_spec.rb +92 -0
- data/spec/squeel/nodes/function_spec.rb +149 -0
- data/spec/squeel/nodes/join_spec.rb +47 -0
- data/spec/squeel/nodes/key_path_spec.rb +100 -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 +50 -0
- data/spec/squeel/nodes/stub_spec.rb +198 -0
- data/spec/squeel/visitors/attribute_visitor_spec.rb +142 -0
- data/spec/squeel/visitors/predicate_visitor_spec.rb +342 -0
- data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
- data/spec/support/schema.rb +104 -0
- data/squeel.gemspec +43 -0
- metadata +246 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
module JoinDependency
|
7
|
+
|
8
|
+
# Yes, I'm using alias_method_chain here. No, I don't feel too
|
9
|
+
# bad about it. JoinDependency, or, to call it by its full proper
|
10
|
+
# name, ::ActiveRecord::Associations::JoinDependency, is one of the
|
11
|
+
# most "for internal use only" chunks of ActiveRecord.
|
12
|
+
def self.included(base)
|
13
|
+
base.class_eval do
|
14
|
+
alias_method_chain :build, :squeel
|
15
|
+
alias_method_chain :graft, :squeel
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def graft_with_squeel(*associations)
|
20
|
+
associations.each do |association|
|
21
|
+
unless join_associations.detect {|a| association == a}
|
22
|
+
if association.reflection.options[:polymorphic]
|
23
|
+
build(Nodes::Join.new(association.reflection.name, association.join_type, association.reflection.klass),
|
24
|
+
association.find_parent_in(self) || join_base, association.join_type)
|
25
|
+
else
|
26
|
+
build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_with_squeel(associations, parent = nil, join_type = Arel::InnerJoin)
|
34
|
+
associations = associations.symbol if Nodes::Stub === associations
|
35
|
+
|
36
|
+
case associations
|
37
|
+
when Nodes::Join
|
38
|
+
parent ||= join_parts.last
|
39
|
+
reflection = parent.reflections[associations._name] or
|
40
|
+
raise ::ActiveRecord::ConfigurationError, "Association named '#{ associations._name }' was not found; perhaps you misspelled it?"
|
41
|
+
|
42
|
+
unless join_association = find_join_association_respecting_polymorphism(reflection, parent, associations._klass)
|
43
|
+
@reflections << reflection
|
44
|
+
join_association = build_join_association_respecting_polymorphism(reflection, parent, associations._klass)
|
45
|
+
join_association.join_type = associations._type
|
46
|
+
@join_parts << join_association
|
47
|
+
cache_joined_association(join_association)
|
48
|
+
end
|
49
|
+
|
50
|
+
join_association
|
51
|
+
when Nodes::KeyPath
|
52
|
+
parent ||= join_parts.last
|
53
|
+
associations.path_with_endpoint.each do |key|
|
54
|
+
parent = build(key, parent, join_type)
|
55
|
+
end
|
56
|
+
parent
|
57
|
+
else
|
58
|
+
build_without_squeel(associations, parent, join_type)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_join_association_respecting_polymorphism(reflection, parent, klass)
|
63
|
+
if association = find_join_association(reflection, parent)
|
64
|
+
unless reflection.options[:polymorphic]
|
65
|
+
association
|
66
|
+
else
|
67
|
+
association if association.active_record == klass
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_join_association_respecting_polymorphism(reflection, parent, klass)
|
73
|
+
if reflection.options[:polymorphic] && klass
|
74
|
+
JoinAssociation.new(reflection, self, parent, klass)
|
75
|
+
else
|
76
|
+
JoinAssociation.new(reflection, self, parent)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Squeel
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
module Preloader
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
alias_method_chain :run, :squeel
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def run_with_squeel
|
13
|
+
unless records.empty?
|
14
|
+
Visitors::SymbolVisitor.new.accept(associations).each { |association| preload(association) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,351 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
module Relation
|
7
|
+
|
8
|
+
JoinAssociation = ::ActiveRecord::Associations::JoinDependency::JoinAssociation
|
9
|
+
JoinDependency = ::ActiveRecord::Associations::JoinDependency
|
10
|
+
|
11
|
+
attr_writer :join_dependency
|
12
|
+
private :join_dependency=
|
13
|
+
|
14
|
+
# Returns a JoinDependency for the current relation.
|
15
|
+
#
|
16
|
+
# We don't need to clear out @join_dependency by overriding #reset, because
|
17
|
+
# the default #reset already does this, despite never setting it anywhere that
|
18
|
+
# I can find. Serendipity, I say!
|
19
|
+
def join_dependency
|
20
|
+
@join_dependency ||= (build_join_dependency(table.from(table), @joins_values) && @join_dependency)
|
21
|
+
end
|
22
|
+
|
23
|
+
def predicate_visitor
|
24
|
+
Visitors::PredicateVisitor.new(
|
25
|
+
Context.new(join_dependency)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def attribute_visitor
|
30
|
+
Visitors::AttributeVisitor.new(
|
31
|
+
Context.new(join_dependency)
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
# We need to be able to support merging two relations that have a different
|
36
|
+
# base class. Stock ActiveRecord doesn't have to do anything too special, because
|
37
|
+
# it's already created predicates out of the where_values by now, and they're
|
38
|
+
# already bound to the proper table.
|
39
|
+
#
|
40
|
+
# Squeel, on the other hand, needs to do its best to ensure the predicates are still
|
41
|
+
# winding up against the proper table. Merging relations is a really nifty shortcut
|
42
|
+
# but another little corner of ActiveRecord where the magic quickly fades. :(
|
43
|
+
def merge(r, association_name = nil)
|
44
|
+
if association_name || relation_with_different_base?(r)
|
45
|
+
r = r.clone
|
46
|
+
association_name ||= infer_association_for_relation_merge(r)
|
47
|
+
prepare_relation_for_association_merge!(r, association_name)
|
48
|
+
end
|
49
|
+
|
50
|
+
super(r)
|
51
|
+
end
|
52
|
+
|
53
|
+
def relation_with_different_base?(r)
|
54
|
+
::ActiveRecord::Relation === r &&
|
55
|
+
base_class.name != r.klass.base_class.name
|
56
|
+
end
|
57
|
+
|
58
|
+
def infer_association_for_relation_merge(r)
|
59
|
+
default_association = reflect_on_all_associations.detect {|a| a.class_name == r.klass.name}
|
60
|
+
default_association ? default_association.name : r.table_name.to_sym
|
61
|
+
end
|
62
|
+
|
63
|
+
def prepare_relation_for_association_merge!(r, association_name)
|
64
|
+
r.where_values.map! {|w| Squeel::Visitors::PredicateVisitor.can_accept?(w) ? {association_name => w} : w}
|
65
|
+
r.having_values.map! {|h| Squeel::Visitors::PredicateVisitor.can_accept?(h) ? {association_name => h} : h}
|
66
|
+
r.group_values.map! {|g| Squeel::Visitors::AttributeVisitor.can_accept?(g) ? {association_name => g} : g}
|
67
|
+
r.order_values.map! {|o| Squeel::Visitors::AttributeVisitor.can_accept?(o) ? {association_name => o} : o}
|
68
|
+
r.select_values.map! {|s| Squeel::Visitors::AttributeVisitor.can_accept?(s) ? {association_name => s} : s}
|
69
|
+
r.joins_values.map! {|j| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(j.class) ? {association_name => j} : j}
|
70
|
+
r.includes_values.map! {|i| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(i.class) ? {association_name => i} : i}
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_arel
|
74
|
+
arel = table.from table
|
75
|
+
|
76
|
+
build_join_dependency(arel, @joins_values) unless @joins_values.empty?
|
77
|
+
|
78
|
+
predicate_viz = predicate_visitor
|
79
|
+
attribute_viz = attribute_visitor
|
80
|
+
|
81
|
+
collapse_wheres(arel, predicate_viz.accept((@where_values - ['']).uniq))
|
82
|
+
|
83
|
+
arel.having(*predicate_viz.accept(@having_values.uniq.reject{|h| h.blank?})) unless @having_values.empty?
|
84
|
+
|
85
|
+
arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
|
86
|
+
arel.skip(@offset_value) if @offset_value
|
87
|
+
|
88
|
+
arel.group(*attribute_viz.accept(@group_values.uniq.reject{|g| g.blank?})) unless @group_values.empty?
|
89
|
+
|
90
|
+
order = @reorder_value ? @reorder_value : @order_values
|
91
|
+
order = attribute_viz.accept(order.uniq.reject{|o| o.blank?})
|
92
|
+
order = reverse_sql_order(sqlify_order(order)) if @reverse_order_value
|
93
|
+
arel.order(*order) unless order.empty?
|
94
|
+
|
95
|
+
build_select(arel, attribute_viz.accept(@select_values.uniq))
|
96
|
+
|
97
|
+
arel.from(@from_value) if @from_value
|
98
|
+
arel.lock(@lock_value) if @lock_value
|
99
|
+
|
100
|
+
arel
|
101
|
+
end
|
102
|
+
|
103
|
+
# reverse_sql_order doesn't understand ARel ordering nodes, so we
|
104
|
+
# need to convert them to their corresponding SQL
|
105
|
+
def sqlify_order(order)
|
106
|
+
order.map do |o|
|
107
|
+
o.respond_to?(:to_sql) ? o.to_sql : o
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_join_dependency(manager, joins)
|
112
|
+
buckets = joins.group_by do |join|
|
113
|
+
case join
|
114
|
+
when String
|
115
|
+
'string_join'
|
116
|
+
when Hash, Symbol, Array, Nodes::Stub, Nodes::Join, Nodes::KeyPath
|
117
|
+
'association_join'
|
118
|
+
when JoinAssociation
|
119
|
+
'stashed_join'
|
120
|
+
when Arel::Nodes::Join
|
121
|
+
'join_node'
|
122
|
+
else
|
123
|
+
raise 'unknown class: %s' % join.class.name
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
association_joins = buckets['association_join'] || []
|
128
|
+
stashed_association_joins = buckets['stashed_join'] || []
|
129
|
+
join_nodes = buckets['join_node'] || []
|
130
|
+
string_joins = (buckets['string_join'] || []).map { |x|
|
131
|
+
x.strip
|
132
|
+
}.uniq
|
133
|
+
|
134
|
+
join_list = custom_join_ast(manager, string_joins)
|
135
|
+
|
136
|
+
# All of this duplication just to add
|
137
|
+
self.join_dependency = JoinDependency.new(
|
138
|
+
@klass,
|
139
|
+
association_joins,
|
140
|
+
join_list
|
141
|
+
)
|
142
|
+
|
143
|
+
join_nodes.each do |join|
|
144
|
+
join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase)
|
145
|
+
end
|
146
|
+
|
147
|
+
join_dependency.graft(*stashed_association_joins)
|
148
|
+
|
149
|
+
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
|
150
|
+
|
151
|
+
join_dependency.join_associations.each do |association|
|
152
|
+
association.join_to(manager)
|
153
|
+
end
|
154
|
+
|
155
|
+
manager.join_sources.concat join_nodes.uniq
|
156
|
+
manager.join_sources.concat join_list
|
157
|
+
|
158
|
+
manager
|
159
|
+
end
|
160
|
+
|
161
|
+
def includes(*args)
|
162
|
+
if block_given? && args.empty?
|
163
|
+
super(DSL.eval &Proc.new)
|
164
|
+
else
|
165
|
+
super
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def preload(*args)
|
170
|
+
if block_given? && args.empty?
|
171
|
+
super(DSL.eval &Proc.new)
|
172
|
+
else
|
173
|
+
super
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def eager_load(*args)
|
178
|
+
if block_given? && args.empty?
|
179
|
+
super(DSL.eval &Proc.new)
|
180
|
+
else
|
181
|
+
super
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def select(value = Proc.new)
|
186
|
+
if block_given? && Proc === value
|
187
|
+
if value.arity > 0
|
188
|
+
to_a.select {|*block_args| value.call(*block_args)}
|
189
|
+
else
|
190
|
+
relation = clone
|
191
|
+
relation.select_values += Array.wrap(DSL.eval &value)
|
192
|
+
relation
|
193
|
+
end
|
194
|
+
else
|
195
|
+
super
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def group(*args)
|
200
|
+
if block_given? && args.empty?
|
201
|
+
super(DSL.eval &Proc.new)
|
202
|
+
else
|
203
|
+
super
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def order(*args)
|
208
|
+
if block_given? && args.empty?
|
209
|
+
super(DSL.eval &Proc.new)
|
210
|
+
else
|
211
|
+
super
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def reorder(*args)
|
216
|
+
if block_given? && args.empty?
|
217
|
+
super(DSL.eval &Proc.new)
|
218
|
+
else
|
219
|
+
super
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def joins(*args)
|
224
|
+
if block_given? && args.empty?
|
225
|
+
super(DSL.eval &Proc.new)
|
226
|
+
else
|
227
|
+
super
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def where(opts = Proc.new, *rest)
|
232
|
+
if block_given? && Proc === opts
|
233
|
+
super(DSL.eval &opts)
|
234
|
+
else
|
235
|
+
super
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def having(*args)
|
240
|
+
if block_given? && args.empty?
|
241
|
+
super(DSL.eval &Proc.new)
|
242
|
+
else
|
243
|
+
super
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def build_where(opts, other = [])
|
248
|
+
case opts
|
249
|
+
when String, Array
|
250
|
+
super
|
251
|
+
else # Let's prevent PredicateBuilder from doing its thing
|
252
|
+
[opts, *other].map do |arg|
|
253
|
+
case arg
|
254
|
+
when Array # Just in case there's an array in there somewhere
|
255
|
+
@klass.send(:sanitize_sql, arg)
|
256
|
+
when Hash
|
257
|
+
@klass.send(:expand_hash_conditions_for_aggregates, arg)
|
258
|
+
else
|
259
|
+
arg
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def collapse_wheres(arel, wheres)
|
266
|
+
wheres = [wheres] unless Array === wheres
|
267
|
+
binaries = wheres.grep(Arel::Nodes::Binary)
|
268
|
+
|
269
|
+
groups = binaries.group_by {|b| [b.class, b.left]}
|
270
|
+
|
271
|
+
groups.each do |_, bins|
|
272
|
+
arel.where(Arel::Nodes::And.new(bins))
|
273
|
+
end
|
274
|
+
|
275
|
+
(wheres - binaries).each do |where|
|
276
|
+
where = Arel.sql(where) if String === where
|
277
|
+
arel.where(Arel::Nodes::Grouping.new(where))
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def find_equality_predicates(nodes)
|
282
|
+
nodes.map { |node|
|
283
|
+
case node
|
284
|
+
when Arel::Nodes::Equality
|
285
|
+
node if node.left.relation.name == table_name
|
286
|
+
when Arel::Nodes::Grouping
|
287
|
+
find_equality_predicates([node.expr])
|
288
|
+
when Arel::Nodes::And
|
289
|
+
find_equality_predicates(node.children)
|
290
|
+
else
|
291
|
+
nil
|
292
|
+
end
|
293
|
+
}.compact.flatten
|
294
|
+
end
|
295
|
+
|
296
|
+
# Simulate the logic that occurs in #to_a
|
297
|
+
#
|
298
|
+
# This will let us get a dump of the SQL that will be run against the
|
299
|
+
# DB for debug purposes without actually running the query.
|
300
|
+
def debug_sql
|
301
|
+
if eager_loading?
|
302
|
+
including = (@eager_load_values + @includes_values).uniq
|
303
|
+
join_dependency = JoinDependency.new(@klass, including, [])
|
304
|
+
construct_relation_for_association_find(join_dependency).to_sql
|
305
|
+
else
|
306
|
+
arel.to_sql
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
### ZOMG ALIAS_METHOD_CHAIN IS BELOW. HIDE YOUR EYES!
|
311
|
+
# ...
|
312
|
+
# ...
|
313
|
+
# ...
|
314
|
+
# Since you're still looking, let me explain this horrible
|
315
|
+
# transgression you see before you.
|
316
|
+
# You see, Relation#where_values_hash is defined on the
|
317
|
+
# ActiveRecord::Relation class. Since it's defined there, but
|
318
|
+
# I would very much like to modify its behavior, I have three
|
319
|
+
# choices.
|
320
|
+
#
|
321
|
+
# 1. Inherit from ActiveRecord::Relation in a Squeel::Relation
|
322
|
+
# class, and make an attempt to usurp all of the various calls
|
323
|
+
# to methods on ActiveRecord::Relation by doing some really
|
324
|
+
# evil stuff with constant reassignment, all for the sake of
|
325
|
+
# being able to use super().
|
326
|
+
#
|
327
|
+
# 2. Submit a patch to Rails core, breaking this method off into
|
328
|
+
# another module, all for my own selfish desire to use super()
|
329
|
+
# while mucking about in Rails internals.
|
330
|
+
#
|
331
|
+
# 3. Use alias_method_chain, and say 10 hail Hanssons as penance.
|
332
|
+
#
|
333
|
+
# I opted to go with #3. Except for the hail Hansson thing.
|
334
|
+
# Unless you're DHH, in which case, I totally said them.
|
335
|
+
|
336
|
+
def self.included(base)
|
337
|
+
base.class_eval do
|
338
|
+
alias_method_chain :where_values_hash, :squeel
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def where_values_hash_with_squeel
|
343
|
+
equalities = find_equality_predicates(predicate_visitor.accept(@where_values))
|
344
|
+
|
345
|
+
Hash[equalities.map { |where| [where.left.name, where.right] }]
|
346
|
+
end
|
347
|
+
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
case ActiveRecord::VERSION::MAJOR
|
2
|
+
when 3
|
3
|
+
case ActiveRecord::VERSION::MINOR
|
4
|
+
when 0
|
5
|
+
require 'squeel/adapters/active_record/3.0/compat'
|
6
|
+
require 'squeel/adapters/active_record/3.0/relation'
|
7
|
+
require 'squeel/adapters/active_record/3.0/join_dependency'
|
8
|
+
require 'squeel/adapters/active_record/3.0/join_association'
|
9
|
+
require 'squeel/adapters/active_record/3.0/association_preload'
|
10
|
+
require 'squeel/adapters/active_record/3.0/context'
|
11
|
+
|
12
|
+
ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::Relation
|
13
|
+
ActiveRecord::Associations::ClassMethods::JoinDependency.send :include, Squeel::Adapters::ActiveRecord::JoinDependency
|
14
|
+
ActiveRecord::Base.extend Squeel::Adapters::ActiveRecord::AssociationPreload
|
15
|
+
else
|
16
|
+
require 'squeel/adapters/active_record/relation'
|
17
|
+
require 'squeel/adapters/active_record/join_dependency'
|
18
|
+
require 'squeel/adapters/active_record/join_association'
|
19
|
+
require 'squeel/adapters/active_record/preloader'
|
20
|
+
require 'squeel/adapters/active_record/context'
|
21
|
+
|
22
|
+
ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::Relation
|
23
|
+
ActiveRecord::Associations::JoinDependency.send :include, Squeel::Adapters::ActiveRecord::JoinDependency
|
24
|
+
ActiveRecord::Associations::Preloader.send :include, Squeel::Adapters::ActiveRecord::Preloader
|
25
|
+
end
|
26
|
+
else
|
27
|
+
raise NotImplementedError, "Squeel does not support ActiveRecord version #{ActiveRecord::VERSION::STRING}"
|
28
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'squeel/constants'
|
2
|
+
require 'squeel/predicate_methods'
|
3
|
+
|
4
|
+
module Squeel
|
5
|
+
# The Squeel configuration module. The Squeel module extends this to provide its
|
6
|
+
# configuration capability.
|
7
|
+
module Configuration
|
8
|
+
|
9
|
+
# Start a Squeel configuration block in an initializer.
|
10
|
+
#
|
11
|
+
# @yield [config] A configuration block
|
12
|
+
#
|
13
|
+
# @example Load hash and symbol extensions
|
14
|
+
# Squeel.configure do |config|
|
15
|
+
# config.load_core_extensions :hash, :symbol
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# @example Alias a predicate
|
19
|
+
# Squeel.configure do |config|
|
20
|
+
# config.alias_ptedicate :is_less_than, :lt
|
21
|
+
# end
|
22
|
+
def configure
|
23
|
+
yield self
|
24
|
+
end
|
25
|
+
|
26
|
+
# Load core extensions for Hash, Symbol, or both
|
27
|
+
#
|
28
|
+
# @overload load_core_extensions(sym)
|
29
|
+
# Load a single extension
|
30
|
+
# @param [Symbol] sym :hash or :symbol
|
31
|
+
# @overload load_core_extensions(sym1, sym2)
|
32
|
+
# Load both extensions
|
33
|
+
# @param [Symbol] sym1 :hash or :symbol
|
34
|
+
# @param [Symbol] sym2 :hash or :symbol
|
35
|
+
def load_core_extensions(*exts)
|
36
|
+
exts.each do |ext|
|
37
|
+
require "core_ext/#{ext}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create an alias to an existing predication method. The _any/_all variations will
|
42
|
+
# be created automatically.
|
43
|
+
# @param [Symbol] new_name The alias name
|
44
|
+
# @param [Symbol] existing_name The existing predicate name
|
45
|
+
# @raise [ArgumentError] The existing name is an _any/_all variation, and not the original predicate name
|
46
|
+
def alias_predicate(new_name, existing_name)
|
47
|
+
raise ArgumentError, 'the existing name should be the base name, not an _any/_all variation' if existing_name.to_s =~ /(_any|_all)$/
|
48
|
+
['', '_any', '_all'].each do |suffix|
|
49
|
+
PredicateMethods.class_eval "alias :#{new_name}#{suffix} :#{existing_name}#{suffix} unless defined?(#{new_name}#{suffix})"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Squeel
|
2
|
+
# Defines the default list of ARel predicates and predicate aliases
|
3
|
+
module Constants
|
4
|
+
PREDICATES = [
|
5
|
+
:eq, :eq_any, :eq_all,
|
6
|
+
:not_eq, :not_eq_any, :not_eq_all,
|
7
|
+
:matches, :matches_any, :matches_all,
|
8
|
+
:does_not_match, :does_not_match_any, :does_not_match_all,
|
9
|
+
:lt, :lt_any, :lt_all,
|
10
|
+
:lteq, :lteq_any, :lteq_all,
|
11
|
+
:gt, :gt_any, :gt_all,
|
12
|
+
:gteq, :gteq_any, :gteq_all,
|
13
|
+
:in, :in_any, :in_all,
|
14
|
+
:not_in, :not_in_any, :not_in_all
|
15
|
+
].freeze
|
16
|
+
|
17
|
+
PREDICATE_ALIASES = {
|
18
|
+
:matches => [:like],
|
19
|
+
:does_not_match => [:not_like],
|
20
|
+
:lteq => [:lte],
|
21
|
+
:gteq => [:gte]
|
22
|
+
}.freeze
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'arel'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
# @abstract Subclass and implement {#traverse}, #{find} and {#get_table}
|
5
|
+
# to create a Context that supports a given ORM.
|
6
|
+
class Context
|
7
|
+
attr_reader :base, :engine, :arel_visitor
|
8
|
+
|
9
|
+
# The Squeel context expects some kind of context object that is
|
10
|
+
# representative of the current joins in a query in order to return
|
11
|
+
# appropriate tables. Again, in the case of an ActiveRecord context,
|
12
|
+
# this will be a JoinDependency. Subclasses are expected to set the
|
13
|
+
# <tt>@base</tt>, <tt>@engine</tt>, and <tt>@arel_visitor</tt>
|
14
|
+
# instance variables to appropriate values for use in their implementations
|
15
|
+
# of other required methods.
|
16
|
+
#
|
17
|
+
# @param object The object the context will use for contextualization
|
18
|
+
def initialize(object)
|
19
|
+
@object = object
|
20
|
+
@tables = Hash.new {|hash, key| hash[key] = get_table(key)}
|
21
|
+
end
|
22
|
+
|
23
|
+
# This method should find a given object inside the context.
|
24
|
+
#
|
25
|
+
# @param object The object to find
|
26
|
+
# @param parent The parent object, if applicable
|
27
|
+
# @return a valid "parent" or contextualizable object
|
28
|
+
def find(object, parent = @base)
|
29
|
+
raise NotImplementedError, "Subclasses must implement public method find"
|
30
|
+
end
|
31
|
+
|
32
|
+
# This method should traverse a keypath and return an object for use
|
33
|
+
# in future calls to #traverse, #find, or #contextualize.
|
34
|
+
#
|
35
|
+
# @param [Nodes::KeyPath] keypath The keypath to traverse
|
36
|
+
# @param parent The parent object from which traversal should start.
|
37
|
+
# @param [Boolean] include_endpoint Whether or not the KeyPath's
|
38
|
+
# endpoint should be treated as a traversable key
|
39
|
+
# @return a valid "parent" or contextualizable object
|
40
|
+
def traverse(keypath, parent = @base, include_endpoint = false)
|
41
|
+
raise NotImplementedError, "Subclasses must implement public method traverse"
|
42
|
+
end
|
43
|
+
|
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 ARel Table occurs.
|
46
|
+
#
|
47
|
+
# @param object A contextualizable object (this will depend on the subclass's implementation)
|
48
|
+
# @return [Arel::Table] A table corresponding to the object param
|
49
|
+
def contextualize(object)
|
50
|
+
@tables[object]
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Returns an Arel::Table that's appropriate for the object it's been sent.
|
56
|
+
# What's "appropriate"? Well, that's up to the implementation to decide, but
|
57
|
+
# it should probably generate a table that is least likely to result in invalid
|
58
|
+
# SQL.
|
59
|
+
#
|
60
|
+
# @param object A contextualizable object (this will depend on the subclass's implementation)
|
61
|
+
# @return [Arel::Table] A table corresponding to the object param.
|
62
|
+
def get_table(object)
|
63
|
+
raise NotImplementedError, "Subclasses must implement private method get_table"
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|