squeel 0.5.0 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/README.rdoc +115 -39
  2. data/lib/squeel/adapters/active_record.rb +22 -5
  3. data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
  4. data/lib/squeel/adapters/active_record/3.0/compat.rb +143 -0
  5. data/lib/squeel/adapters/active_record/3.0/context.rb +67 -0
  6. data/lib/squeel/adapters/active_record/3.0/join_association.rb +54 -0
  7. data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +84 -0
  8. data/lib/squeel/adapters/active_record/3.0/relation.rb +327 -0
  9. data/lib/squeel/adapters/active_record/context.rb +67 -0
  10. data/lib/squeel/adapters/active_record/join_association.rb +10 -56
  11. data/lib/squeel/adapters/active_record/join_dependency.rb +22 -7
  12. data/lib/squeel/adapters/active_record/preloader.rb +21 -0
  13. data/lib/squeel/adapters/active_record/relation.rb +84 -38
  14. data/lib/squeel/context.rb +38 -0
  15. data/lib/squeel/dsl.rb +1 -1
  16. data/lib/squeel/nodes/join.rb +18 -0
  17. data/lib/squeel/nodes/key_path.rb +2 -2
  18. data/lib/squeel/nodes/stub.rb +5 -1
  19. data/lib/squeel/version.rb +1 -1
  20. data/lib/squeel/visitors.rb +2 -2
  21. data/lib/squeel/visitors/{order_visitor.rb → attribute_visitor.rb} +1 -2
  22. data/lib/squeel/visitors/predicate_visitor.rb +13 -11
  23. data/lib/squeel/visitors/symbol_visitor.rb +48 -0
  24. data/spec/helpers/squeel_helper.rb +17 -1
  25. data/spec/spec_helper.rb +31 -0
  26. data/spec/squeel/adapters/active_record/context_spec.rb +50 -0
  27. data/spec/squeel/adapters/active_record/join_association_spec.rb +1 -1
  28. data/spec/squeel/adapters/active_record/join_depdendency_spec.rb +1 -1
  29. data/spec/squeel/adapters/active_record/relation_spec.rb +166 -25
  30. data/spec/squeel/dsl_spec.rb +6 -6
  31. data/spec/squeel/nodes/join_spec.rb +16 -3
  32. data/spec/squeel/nodes/stub_spec.rb +12 -0
  33. data/spec/squeel/visitors/{order_visitor_spec.rb → attribute_visitor_spec.rb} +4 -5
  34. data/spec/squeel/visitors/predicate_visitor_spec.rb +18 -6
  35. data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
  36. data/squeel.gemspec +2 -2
  37. metadata +21 -13
  38. data/lib/squeel/contexts/join_dependency_context.rb +0 -74
  39. data/lib/squeel/visitors/select_visitor.rb +0 -103
  40. data/spec/squeel/contexts/join_dependency_context_spec.rb +0 -43
  41. 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 join_to(relation)
29
- tables = @tables.dup
30
- foreign_table = parent_table
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
- # The current table in this iteration becomes the foreign table in the next
80
- foreign_table = table
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, join)
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 == join.klass
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, join)
58
- if reflection.options[:polymorphic] && join.polymorphic?
59
- JoinAssociation.new(reflection, self, parent, join.klass)
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
- Contexts::JoinDependencyContext.new(join_dependency)
25
+ Context.new(join_dependency)
27
26
  )
28
27
  end
29
28
 
30
- def order_visitor
31
- Visitors::OrderVisitor.new(
32
- Contexts::JoinDependencyContext.new(join_dependency)
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(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
85
+ arel.group(*attribute_viz.accept(@group_values.uniq.reject{|g| g.blank?})) unless @group_values.empty?
78
86
 
79
- unless @order_values.empty?
80
- order_viz = order_visitor
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, select_visitor.accept(@select_values.uniq))
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.evaluate &value)
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 where(opts = Proc.new, *rest)
157
- if block_given? && Proc === opts
158
- super(DSL.evaluate &opts)
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 build_where(opts, other = [])
165
- case opts
166
- when String, Array
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 order(*args)
202
+ def reorder(*args)
183
203
  if block_given? && args.empty?
184
- super(DSL.evaluate &Proc.new)
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.evaluate &Proc.new)
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.evaluate &Proc.new)
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
@@ -9,7 +9,7 @@ module Squeel
9
9
  end
10
10
  end
11
11
 
12
- def self.evaluate(&block)
12
+ def self.eval(&block)
13
13
  if block.arity > 0
14
14
  yield self.new
15
15
  else
@@ -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.symbol
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])