squeel 0.5.0 → 0.5.5
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/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])
|