squeel 0.5.0 → 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +115 -39
- data/lib/squeel/adapters/active_record.rb +22 -5
- data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
- data/lib/squeel/adapters/active_record/3.0/compat.rb +143 -0
- data/lib/squeel/adapters/active_record/3.0/context.rb +67 -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 +67 -0
- data/lib/squeel/adapters/active_record/join_association.rb +10 -56
- data/lib/squeel/adapters/active_record/join_dependency.rb +22 -7
- data/lib/squeel/adapters/active_record/preloader.rb +21 -0
- data/lib/squeel/adapters/active_record/relation.rb +84 -38
- data/lib/squeel/context.rb +38 -0
- data/lib/squeel/dsl.rb +1 -1
- data/lib/squeel/nodes/join.rb +18 -0
- data/lib/squeel/nodes/key_path.rb +2 -2
- data/lib/squeel/nodes/stub.rb +5 -1
- data/lib/squeel/version.rb +1 -1
- data/lib/squeel/visitors.rb +2 -2
- data/lib/squeel/visitors/{order_visitor.rb → attribute_visitor.rb} +1 -2
- data/lib/squeel/visitors/predicate_visitor.rb +13 -11
- data/lib/squeel/visitors/symbol_visitor.rb +48 -0
- data/spec/helpers/squeel_helper.rb +17 -1
- data/spec/spec_helper.rb +31 -0
- data/spec/squeel/adapters/active_record/context_spec.rb +50 -0
- data/spec/squeel/adapters/active_record/join_association_spec.rb +1 -1
- data/spec/squeel/adapters/active_record/join_depdendency_spec.rb +1 -1
- data/spec/squeel/adapters/active_record/relation_spec.rb +166 -25
- data/spec/squeel/dsl_spec.rb +6 -6
- data/spec/squeel/nodes/join_spec.rb +16 -3
- data/spec/squeel/nodes/stub_spec.rb +12 -0
- data/spec/squeel/visitors/{order_visitor_spec.rb → attribute_visitor_spec.rb} +4 -5
- data/spec/squeel/visitors/predicate_visitor_spec.rb +18 -6
- data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
- data/squeel.gemspec +2 -2
- metadata +21 -13
- data/lib/squeel/contexts/join_dependency_context.rb +0 -74
- data/lib/squeel/visitors/select_visitor.rb +0 -103
- data/spec/squeel/contexts/join_dependency_context_spec.rb +0 -43
- data/spec/squeel/visitors/select_visitor_spec.rb +0 -115
@@ -3,7 +3,6 @@ require 'active_record'
|
|
3
3
|
module Squeel
|
4
4
|
module Adapters
|
5
5
|
module ActiveRecord
|
6
|
-
|
7
6
|
class JoinAssociation < ::ActiveRecord::Associations::JoinDependency::JoinAssociation
|
8
7
|
|
9
8
|
def initialize(reflection, join_dependency, parent = nil, polymorphic_class = nil)
|
@@ -25,66 +24,21 @@ module Squeel
|
|
25
24
|
reflection.options[:polymorphic] = original_polymorphic
|
26
25
|
end
|
27
26
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# The chain starts with the target table, but we want to end with it here (makes
|
33
|
-
# more sense in this context), so we reverse
|
34
|
-
chain.reverse.each_with_index do |reflection, i|
|
35
|
-
table = tables.shift
|
36
|
-
|
37
|
-
case reflection.source_macro
|
38
|
-
when :belongs_to
|
39
|
-
key = reflection.association_primary_key
|
40
|
-
foreign_key = reflection.foreign_key
|
41
|
-
when :has_and_belongs_to_many
|
42
|
-
# Join the join table first...
|
43
|
-
relation.from(join(
|
44
|
-
table,
|
45
|
-
table[reflection.foreign_key].
|
46
|
-
eq(foreign_table[reflection.active_record_primary_key])
|
47
|
-
))
|
48
|
-
|
49
|
-
foreign_table, table = table, tables.shift
|
50
|
-
|
51
|
-
key = reflection.association_primary_key
|
52
|
-
foreign_key = reflection.association_foreign_key
|
53
|
-
else
|
54
|
-
key = reflection.foreign_key
|
55
|
-
foreign_key = reflection.active_record_primary_key
|
56
|
-
end
|
57
|
-
|
58
|
-
constraint = table[key].eq(foreign_table[foreign_key])
|
59
|
-
|
60
|
-
if reflection.options[:polymorphic]
|
61
|
-
constraint = constraint.and(
|
62
|
-
foreign_table[reflection.foreign_type].eq(reflection.klass.name)
|
63
|
-
)
|
64
|
-
end
|
65
|
-
|
66
|
-
if reflection.klass.finder_needs_type_condition?
|
67
|
-
constraint = table.create_and([
|
68
|
-
constraint,
|
69
|
-
reflection.klass.send(:type_condition, table)
|
70
|
-
])
|
71
|
-
end
|
72
|
-
|
73
|
-
relation.from(join(table, constraint))
|
74
|
-
|
75
|
-
unless conditions[i].empty?
|
76
|
-
relation.where(sanitize(conditions[i], table))
|
77
|
-
end
|
27
|
+
def ==(other)
|
28
|
+
super && active_record == other.active_record
|
29
|
+
end
|
78
30
|
|
79
|
-
|
80
|
-
|
31
|
+
def build_constraint(reflection, table, key, foreign_table, foreign_key)
|
32
|
+
if reflection.options[:polymorphic]
|
33
|
+
super.and(
|
34
|
+
foreign_table[reflection.foreign_type].eq(reflection.klass.name)
|
35
|
+
)
|
36
|
+
else
|
37
|
+
super
|
81
38
|
end
|
82
|
-
|
83
|
-
relation
|
84
39
|
end
|
85
40
|
|
86
41
|
end
|
87
|
-
|
88
42
|
end
|
89
43
|
end
|
90
44
|
end
|
@@ -12,9 +12,24 @@ module Squeel
|
|
12
12
|
def self.included(base)
|
13
13
|
base.class_eval do
|
14
14
|
alias_method_chain :build, :squeel
|
15
|
+
alias_method_chain :graft, :squeel
|
15
16
|
end
|
16
17
|
end
|
17
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
|
+
|
18
33
|
def build_with_squeel(associations, parent = nil, join_type = Arel::InnerJoin)
|
19
34
|
associations = associations.symbol if Nodes::Stub === associations
|
20
35
|
|
@@ -24,9 +39,9 @@ module Squeel
|
|
24
39
|
reflection = parent.reflections[associations.name] or
|
25
40
|
raise ::ActiveRecord::ConfigurationError, "Association named '#{ associations.name }' was not found; perhaps you misspelled it?"
|
26
41
|
|
27
|
-
unless join_association = find_join_association_respecting_polymorphism(reflection, parent, associations)
|
42
|
+
unless join_association = find_join_association_respecting_polymorphism(reflection, parent, associations.klass)
|
28
43
|
@reflections << reflection
|
29
|
-
join_association = build_join_association_respecting_polymorphism(reflection, parent, associations)
|
44
|
+
join_association = build_join_association_respecting_polymorphism(reflection, parent, associations.klass)
|
30
45
|
join_association.join_type = associations.type
|
31
46
|
@join_parts << join_association
|
32
47
|
cache_joined_association(join_association)
|
@@ -44,19 +59,19 @@ module Squeel
|
|
44
59
|
end
|
45
60
|
end
|
46
61
|
|
47
|
-
def find_join_association_respecting_polymorphism(reflection, parent,
|
62
|
+
def find_join_association_respecting_polymorphism(reflection, parent, klass)
|
48
63
|
if association = find_join_association(reflection, parent)
|
49
64
|
unless reflection.options[:polymorphic]
|
50
65
|
association
|
51
66
|
else
|
52
|
-
association if association.active_record ==
|
67
|
+
association if association.active_record == klass
|
53
68
|
end
|
54
69
|
end
|
55
70
|
end
|
56
71
|
|
57
|
-
def build_join_association_respecting_polymorphism(reflection, parent,
|
58
|
-
if reflection.options[:polymorphic] &&
|
59
|
-
JoinAssociation.new(reflection, self, parent,
|
72
|
+
def build_join_association_respecting_polymorphism(reflection, parent, klass)
|
73
|
+
if reflection.options[:polymorphic] && klass
|
74
|
+
JoinAssociation.new(reflection, self, parent, klass)
|
60
75
|
else
|
61
76
|
JoinAssociation.new(reflection, self, parent)
|
62
77
|
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
|
@@ -11,28 +11,35 @@ module Squeel
|
|
11
11
|
attr_writer :join_dependency
|
12
12
|
private :join_dependency=
|
13
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!
|
14
19
|
def join_dependency
|
15
20
|
@join_dependency ||= (build_join_dependency(table.from(table), @joins_values) && @join_dependency)
|
16
21
|
end
|
17
22
|
|
18
|
-
def select_visitor
|
19
|
-
Visitors::SelectVisitor.new(
|
20
|
-
Contexts::JoinDependencyContext.new(join_dependency)
|
21
|
-
)
|
22
|
-
end
|
23
|
-
|
24
23
|
def predicate_visitor
|
25
24
|
Visitors::PredicateVisitor.new(
|
26
|
-
|
25
|
+
Context.new(join_dependency)
|
27
26
|
)
|
28
27
|
end
|
29
28
|
|
30
|
-
def
|
31
|
-
Visitors::
|
32
|
-
|
29
|
+
def attribute_visitor
|
30
|
+
Visitors::AttributeVisitor.new(
|
31
|
+
Context.new(join_dependency)
|
33
32
|
)
|
34
33
|
end
|
35
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. :(
|
36
43
|
def merge(r, association_name = nil)
|
37
44
|
if association_name || relation_with_different_base?(r)
|
38
45
|
r = r.clone
|
@@ -66,6 +73,7 @@ module Squeel
|
|
66
73
|
build_join_dependency(arel, @joins_values) unless @joins_values.empty?
|
67
74
|
|
68
75
|
predicate_viz = predicate_visitor
|
76
|
+
attribute_viz = attribute_visitor
|
69
77
|
|
70
78
|
collapse_wheres(arel, predicate_viz.accept((@where_values - ['']).uniq))
|
71
79
|
|
@@ -74,14 +82,12 @@ module Squeel
|
|
74
82
|
arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
|
75
83
|
arel.skip(@offset_value) if @offset_value
|
76
84
|
|
77
|
-
arel.group(
|
85
|
+
arel.group(*attribute_viz.accept(@group_values.uniq.reject{|g| g.blank?})) unless @group_values.empty?
|
78
86
|
|
79
|
-
|
80
|
-
|
81
|
-
arel.order(*order_viz.accept(@order_values.uniq.reject{|o| o.blank?}))
|
82
|
-
end
|
87
|
+
order = @reorder_value ? @reorder_value : @order_values
|
88
|
+
arel.order(*attribute_viz.accept(order.uniq.reject{|o| o.blank?})) unless order.empty?
|
83
89
|
|
84
|
-
build_select(arel,
|
90
|
+
build_select(arel, attribute_viz.accept(@select_values.uniq))
|
85
91
|
|
86
92
|
arel.from(@from_value) if @from_value
|
87
93
|
arel.lock(@lock_value) if @lock_value
|
@@ -139,13 +145,37 @@ module Squeel
|
|
139
145
|
manager
|
140
146
|
end
|
141
147
|
|
148
|
+
def includes(*args)
|
149
|
+
if block_given? && args.empty?
|
150
|
+
super(DSL.eval &Proc.new)
|
151
|
+
else
|
152
|
+
super
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def preload(*args)
|
157
|
+
if block_given? && args.empty?
|
158
|
+
super(DSL.eval &Proc.new)
|
159
|
+
else
|
160
|
+
super
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def eager_load(*args)
|
165
|
+
if block_given? && args.empty?
|
166
|
+
super(DSL.eval &Proc.new)
|
167
|
+
else
|
168
|
+
super
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
142
172
|
def select(value = Proc.new)
|
143
173
|
if block_given? && Proc === value
|
144
174
|
if value.arity > 0
|
145
175
|
to_a.select {|*block_args| value.call(*block_args)}
|
146
176
|
else
|
147
177
|
relation = clone
|
148
|
-
relation.select_values += Array.wrap(DSL.
|
178
|
+
relation.select_values += Array.wrap(DSL.eval &value)
|
149
179
|
relation
|
150
180
|
end
|
151
181
|
else
|
@@ -153,35 +183,25 @@ module Squeel
|
|
153
183
|
end
|
154
184
|
end
|
155
185
|
|
156
|
-
def
|
157
|
-
if block_given? &&
|
158
|
-
super(DSL.
|
186
|
+
def group(*args)
|
187
|
+
if block_given? && args.empty?
|
188
|
+
super(DSL.eval &Proc.new)
|
159
189
|
else
|
160
190
|
super
|
161
191
|
end
|
162
192
|
end
|
163
193
|
|
164
|
-
def
|
165
|
-
|
166
|
-
|
194
|
+
def order(*args)
|
195
|
+
if block_given? && args.empty?
|
196
|
+
super(DSL.eval &Proc.new)
|
197
|
+
else
|
167
198
|
super
|
168
|
-
else # Let's prevent PredicateBuilder from doing its thing
|
169
|
-
[opts, *other].map do |arg|
|
170
|
-
case arg
|
171
|
-
when Array # Just in case there's an array in there somewhere
|
172
|
-
@klass.send(:sanitize_sql, arg)
|
173
|
-
when Hash
|
174
|
-
@klass.send(:expand_hash_conditions_for_aggregates, arg)
|
175
|
-
else
|
176
|
-
arg
|
177
|
-
end
|
178
|
-
end
|
179
199
|
end
|
180
200
|
end
|
181
201
|
|
182
|
-
def
|
202
|
+
def reorder(*args)
|
183
203
|
if block_given? && args.empty?
|
184
|
-
super(DSL.
|
204
|
+
super(DSL.eval &Proc.new)
|
185
205
|
else
|
186
206
|
super
|
187
207
|
end
|
@@ -189,7 +209,15 @@ module Squeel
|
|
189
209
|
|
190
210
|
def joins(*args)
|
191
211
|
if block_given? && args.empty?
|
192
|
-
super(DSL.
|
212
|
+
super(DSL.eval &Proc.new)
|
213
|
+
else
|
214
|
+
super
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def where(opts = Proc.new, *rest)
|
219
|
+
if block_given? && Proc === opts
|
220
|
+
super(DSL.eval &opts)
|
193
221
|
else
|
194
222
|
super
|
195
223
|
end
|
@@ -197,12 +225,30 @@ module Squeel
|
|
197
225
|
|
198
226
|
def having(*args)
|
199
227
|
if block_given? && args.empty?
|
200
|
-
super(DSL.
|
228
|
+
super(DSL.eval &Proc.new)
|
201
229
|
else
|
202
230
|
super
|
203
231
|
end
|
204
232
|
end
|
205
233
|
|
234
|
+
def build_where(opts, other = [])
|
235
|
+
case opts
|
236
|
+
when String, Array
|
237
|
+
super
|
238
|
+
else # Let's prevent PredicateBuilder from doing its thing
|
239
|
+
[opts, *other].map do |arg|
|
240
|
+
case arg
|
241
|
+
when Array # Just in case there's an array in there somewhere
|
242
|
+
@klass.send(:sanitize_sql, arg)
|
243
|
+
when Hash
|
244
|
+
@klass.send(:expand_hash_conditions_for_aggregates, arg)
|
245
|
+
else
|
246
|
+
arg
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
206
252
|
def collapse_wheres(arel, wheres)
|
207
253
|
wheres = [wheres] unless Array === wheres
|
208
254
|
binaries = wheres.grep(Arel::Nodes::Binary)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'arel'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
class Context
|
5
|
+
attr_reader :base, :engine, :arel_visitor
|
6
|
+
|
7
|
+
def initialize(object)
|
8
|
+
@object = object
|
9
|
+
@engine = @base.arel_engine
|
10
|
+
@arel_visitor = Arel::Visitors.visitor_for @engine
|
11
|
+
@default_table = Arel::Table.new(@base.table_name, :as => @base.aliased_table_name, :engine => @engine)
|
12
|
+
@tables = Hash.new {|hash, key| hash[key] = get_table(key)}
|
13
|
+
end
|
14
|
+
|
15
|
+
def find(object, parent = @base)
|
16
|
+
raise NotImplementedError, "Subclasses must implement public method find"
|
17
|
+
end
|
18
|
+
|
19
|
+
def traverse(keypath, parent = @base, include_endpoint = false)
|
20
|
+
raise NotImplementedError, "Subclasses must implement public method traverse"
|
21
|
+
end
|
22
|
+
|
23
|
+
def contextualize(object)
|
24
|
+
@tables[object]
|
25
|
+
end
|
26
|
+
|
27
|
+
def sanitize_sql(conditions, parent)
|
28
|
+
raise NotImplementedError, "Subclasses must implement public method sanitize_sql"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def get_table(object)
|
34
|
+
raise NotImplementedError, "Subclasses must implement private method get_table"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/squeel/dsl.rb
CHANGED
data/lib/squeel/nodes/join.rb
CHANGED
@@ -26,6 +26,24 @@ module Squeel
|
|
26
26
|
@klass
|
27
27
|
end
|
28
28
|
|
29
|
+
def eql?(other)
|
30
|
+
self.class == other.class &&
|
31
|
+
self.name == other.name &&
|
32
|
+
self.type == other.type &&
|
33
|
+
self.klass == other.klass
|
34
|
+
end
|
35
|
+
|
36
|
+
alias :== :eql?
|
37
|
+
|
38
|
+
def method_missing(method_id, *args)
|
39
|
+
super if method_id == :to_ary
|
40
|
+
if (args.size == 1) && (Class === args[0])
|
41
|
+
KeyPath.new(self, Join.new(method_id, Arel::InnerJoin, args[0]))
|
42
|
+
else
|
43
|
+
KeyPath.new(self, method_id)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
29
47
|
# expand_hash_conditions_for_aggregates assumes our hash keys can be
|
30
48
|
# converted to symbols, so this has to be implemented, but it doesn't
|
31
49
|
# really have to do anything useful.
|
@@ -101,8 +101,8 @@ module Squeel
|
|
101
101
|
if endpoint.respond_to? method_id
|
102
102
|
@endpoint = @endpoint.send(method_id, *args)
|
103
103
|
self
|
104
|
-
elsif Stub === endpoint
|
105
|
-
@path << endpoint
|
104
|
+
elsif Stub === endpoint || Join === endpoint
|
105
|
+
@path << endpoint
|
106
106
|
if args.empty?
|
107
107
|
@endpoint = Stub.new(method_id)
|
108
108
|
elsif (args.size == 1) && (Class === args[0])
|