squeel 1.1.1 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +36 -4
  5. data/CHANGELOG.md +15 -0
  6. data/Gemfile +1 -1
  7. data/README.md +47 -6
  8. data/Rakefile +14 -2
  9. data/lib/squeel.rb +9 -1
  10. data/lib/squeel/adapters/active_record.rb +0 -1
  11. data/lib/squeel/adapters/active_record/3.0/join_dependency_extensions.rb +1 -0
  12. data/lib/squeel/adapters/active_record/3.0/relation_extensions.rb +12 -1
  13. data/lib/squeel/adapters/active_record/3.1/join_dependency_extensions.rb +1 -0
  14. data/lib/squeel/adapters/active_record/3.2/join_dependency_extensions.rb +1 -0
  15. data/lib/squeel/adapters/active_record/4.0/join_dependency_extensions.rb +1 -0
  16. data/lib/squeel/adapters/active_record/4.0/relation_extensions.rb +92 -0
  17. data/lib/squeel/adapters/active_record/4.1/compat.rb +15 -0
  18. data/lib/squeel/adapters/active_record/4.1/context.rb +88 -0
  19. data/lib/squeel/adapters/active_record/4.1/preloader_extensions.rb +31 -0
  20. data/lib/squeel/adapters/active_record/4.1/reflection_extensions.rb +37 -0
  21. data/lib/squeel/adapters/active_record/4.1/relation_extensions.rb +307 -0
  22. data/lib/squeel/adapters/active_record/4.2/compat.rb +1 -0
  23. data/lib/squeel/adapters/active_record/4.2/context.rb +1 -0
  24. data/lib/squeel/adapters/active_record/4.2/preloader_extensions.rb +1 -0
  25. data/lib/squeel/adapters/active_record/4.2/relation_extensions.rb +108 -0
  26. data/lib/squeel/adapters/active_record/context.rb +7 -7
  27. data/lib/squeel/adapters/active_record/join_dependency_extensions.rb +9 -13
  28. data/lib/squeel/adapters/active_record/relation_extensions.rb +38 -8
  29. data/lib/squeel/core_ext/symbol.rb +3 -3
  30. data/lib/squeel/dsl.rb +1 -1
  31. data/lib/squeel/nodes.rb +1 -0
  32. data/lib/squeel/nodes/as.rb +12 -0
  33. data/lib/squeel/nodes/join.rb +8 -4
  34. data/lib/squeel/nodes/key_path.rb +10 -1
  35. data/lib/squeel/nodes/node.rb +21 -0
  36. data/lib/squeel/nodes/stub.rb +8 -4
  37. data/lib/squeel/nodes/subquery_join.rb +44 -0
  38. data/lib/squeel/version.rb +1 -1
  39. data/lib/squeel/visitors.rb +2 -0
  40. data/lib/squeel/visitors/enumeration_visitor.rb +101 -0
  41. data/lib/squeel/visitors/order_visitor.rb +9 -2
  42. data/lib/squeel/visitors/predicate_visitor.rb +11 -0
  43. data/lib/squeel/visitors/preload_visitor.rb +12 -0
  44. data/lib/squeel/visitors/visitor.rb +89 -13
  45. data/spec/config.travis.yml +13 -0
  46. data/spec/config.yml +12 -0
  47. data/spec/console.rb +3 -12
  48. data/spec/core_ext/symbol_spec.rb +3 -3
  49. data/spec/helpers/squeel_helper.rb +8 -5
  50. data/spec/spec_helper.rb +4 -16
  51. data/spec/squeel/adapters/active_record/context_spec.rb +8 -4
  52. data/spec/squeel/adapters/active_record/join_dependency_extensions_spec.rb +123 -38
  53. data/spec/squeel/adapters/active_record/relation_extensions_spec.rb +350 -124
  54. data/spec/squeel/core_ext/symbol_spec.rb +3 -3
  55. data/spec/squeel/nodes/join_spec.rb +4 -4
  56. data/spec/squeel/nodes/stub_spec.rb +3 -3
  57. data/spec/squeel/nodes/subquery_join_spec.rb +46 -0
  58. data/spec/squeel/visitors/order_visitor_spec.rb +3 -3
  59. data/spec/squeel/visitors/predicate_visitor_spec.rb +69 -36
  60. data/spec/squeel/visitors/select_visitor_spec.rb +1 -1
  61. data/spec/squeel/visitors/visitor_spec.rb +7 -6
  62. data/spec/support/models.rb +99 -15
  63. data/spec/support/schema.rb +109 -4
  64. data/squeel.gemspec +8 -6
  65. metadata +89 -107
  66. data/.ruby-gemset +0 -1
  67. data/.ruby-version +0 -1
  68. data/spec/blueprints/articles.rb +0 -5
  69. data/spec/blueprints/comments.rb +0 -5
  70. data/spec/blueprints/notes.rb +0 -3
  71. data/spec/blueprints/people.rb +0 -4
  72. data/spec/blueprints/tags.rb +0 -3
@@ -11,9 +11,16 @@ module Squeel
11
11
  # @param parent The node's parent within the context
12
12
  # @return [Arel::Nodes::Ordering] An ascending or desending ordering
13
13
  def visit_Squeel_Nodes_Order(o, parent)
14
- visit(o.expr, parent).send(o.descending? ? :desc : :asc)
14
+ if defined?(Arel::Nodes::Descending)
15
+ if o.descending?
16
+ Arel::Nodes::Descending.new(visit(o.expr, parent))
17
+ else
18
+ Arel::Nodes::Ascending.new(visit(o.expr, parent))
19
+ end
20
+ else
21
+ visit(o.expr, parent).send(o.descending? ? :desc : :asc)
22
+ end
15
23
  end
16
-
17
24
  end
18
25
  end
19
26
  end
@@ -49,6 +49,16 @@ module Squeel
49
49
  end
50
50
  end
51
51
 
52
+ def visit_Hash!(o, parent)
53
+ predicates = super
54
+
55
+ if predicates.size > 1
56
+ Arel::Nodes::Grouping.new(Arel::Nodes::And.new predicates)
57
+ else
58
+ predicates.first
59
+ end
60
+ end
61
+
52
62
 
53
63
  # @return [Boolean] Whether the given value implies a context change
54
64
  # @param v The value to consider
@@ -74,6 +84,7 @@ module Squeel
74
84
  def visit_without_hash_context_shift(k, v, parent)
75
85
  # Short-circuit for stuff like `where(:author => User.first)`
76
86
  # This filthy hack emulates similar behavior in AR PredicateBuilder
87
+
77
88
  if ActiveRecord::Base === v &&
78
89
  association = classify(parent).reflect_on_association(k.to_sym)
79
90
  return expand_belongs_to(Nodes::Predicate.new(k, :eq, v), parent, association)
@@ -11,6 +11,10 @@ module Squeel
11
11
  visit(object, parent)
12
12
  end
13
13
 
14
+ def accept!(object, parent = nil)
15
+ visit!(object, parent)
16
+ end
17
+
14
18
  private
15
19
 
16
20
  def visit_Hash(o, parent)
@@ -21,6 +25,14 @@ module Squeel
21
25
  end
22
26
  end
23
27
 
28
+ def visit_Hash!(o, parent)
29
+ {}.tap do |hash|
30
+ o.each do |key, value|
31
+ hash[visit!(key, parent)] = visit!(value, parent)
32
+ end
33
+ end
34
+ end
35
+
24
36
  def visit_Symbol(o, parent)
25
37
  o
26
38
  end
@@ -6,7 +6,7 @@ module Squeel
6
6
  # The Base visitor class, containing the default behavior common to subclasses.
7
7
  class Visitor
8
8
  attr_accessor :context
9
- delegate :contextualize, :classify, :find, :traverse, :engine, :arel_visitor, :to => :context
9
+ delegate :contextualize, :classify, :find, :traverse, :engine, :arel_visitor, :find!, :traverse!, :to => :context
10
10
 
11
11
  # Create a new Visitor that uses the supplied context object to contextualize
12
12
  # visited nodes.
@@ -27,6 +27,10 @@ module Squeel
27
27
  visit(object, parent)
28
28
  end
29
29
 
30
+ def accept!(object, parent = context.base)
31
+ visit!(object, parent)
32
+ end
33
+
30
34
  # @param object The object to check
31
35
  # @return [Boolean] Whether or not the visitor can visit the given object
32
36
  def can_visit?(object)
@@ -98,6 +102,21 @@ module Squeel
98
102
  @hash_context_depth -= 1
99
103
  end
100
104
 
105
+ def visit_with_hash_context_shift!(k, v, parent)
106
+ @hash_context_depth += 1
107
+
108
+ parent = case k
109
+ when Nodes::KeyPath
110
+ traverse!(k, parent, true)
111
+ else
112
+ find!(k, parent)
113
+ end
114
+
115
+ can_visit?(v) ? visit!(v, parent || k) : v
116
+ ensure
117
+ @hash_context_depth -= 1
118
+ end
119
+
101
120
  # If there is no context change, the default behavior is to return the
102
121
  # value unchanged. Subclasses will alter this behavior as needed.
103
122
  #
@@ -119,9 +138,11 @@ module Squeel
119
138
  # object if not passed as an SqlLiteral.
120
139
  def quoted?(object)
121
140
  case object
122
- when NilClass, Arel::Nodes::SqlLiteral, Bignum, Fixnum,
141
+ when Arel::Nodes::SqlLiteral, Bignum, Fixnum,
123
142
  Arel::SelectManager
124
143
  false
144
+ when NilClass
145
+ defined?(Arel::Nodes::Quoted) ? true : false
125
146
  else
126
147
  true
127
148
  end
@@ -142,7 +163,11 @@ module Squeel
142
163
  when Range
143
164
  Range.new(quote(value.begin), quote(value.end), value.exclude_end?)
144
165
  else
145
- Arel.sql(arel_visitor.accept value)
166
+ if defined?(Arel::Collectors::SQLString)
167
+ Arel.sql(arel_visitor.compile(Arel::Nodes.build_quoted(value)))
168
+ else
169
+ Arel.sql(arel_visitor.accept value)
170
+ end
146
171
  end
147
172
  else
148
173
  value
@@ -166,6 +191,12 @@ module Squeel
166
191
  retry
167
192
  end
168
193
 
194
+ def visit!(object, parent)
195
+ send("#{DISPATCH[object.class]}!", object, parent)
196
+ rescue NoMethodError => e
197
+ visit(object, parent)
198
+ end
199
+
169
200
  # Pass an object through the visitor unmodified. This is
170
201
  # in order to allow objects that don't require modification
171
202
  # to be handled by Arel directly.
@@ -176,6 +207,7 @@ module Squeel
176
207
  def visit_passthrough(object, parent)
177
208
  object
178
209
  end
210
+
179
211
  alias :visit_Fixnum :visit_passthrough
180
212
  alias :visit_Bignum :visit_passthrough
181
213
 
@@ -189,6 +221,10 @@ module Squeel
189
221
  o.map { |v| can_visit?(v) ? visit(v, parent) : v }.flatten
190
222
  end
191
223
 
224
+ def visit_Array!(o, parent)
225
+ o.map { |v| can_visit?(v) ? visit!(v, parent) : v }.flatten
226
+ end
227
+
192
228
  # Visit a Hash. This entails iterating through each key and value and
193
229
  # visiting each value in turn.
194
230
  #
@@ -205,6 +241,16 @@ module Squeel
205
241
  end.flatten
206
242
  end
207
243
 
244
+ def visit_Hash!(o, parent)
245
+ o.map do |k, v|
246
+ if implies_hash_context_shift?(v)
247
+ visit_with_hash_context_shift!(k, v, parent)
248
+ else
249
+ visit_without_hash_context_shift(k, v, parent)
250
+ end
251
+ end.flatten
252
+ end
253
+
208
254
  # Visit a symbol. This will return an attribute named after the symbol
209
255
  # against the current parent's contextualized table.
210
256
  #
@@ -240,6 +286,12 @@ module Squeel
240
286
  visit(o.endpoint, parent)
241
287
  end
242
288
 
289
+ def visit_Squeel_Nodes_KeyPath!(o, parent)
290
+ parent = traverse!(o, parent)
291
+
292
+ visit!(o.endpoint, parent)
293
+ end
294
+
243
295
  # Visit a Literal by converting it to an Arel SqlLiteral
244
296
  #
245
297
  # @param [Nodes::Literal] o The Literal to visit
@@ -255,13 +307,25 @@ module Squeel
255
307
  # @param parent The parent object in the context
256
308
  # @return [Arel::Nodes::As] The resulting as node.
257
309
  def visit_Squeel_Nodes_As(o, parent)
258
- left = visit(o.left, parent)
259
- # Some nodes, like Arel::SelectManager, have their own #as methods,
260
- # with behavior that we don't want to clobber.
261
- if left.respond_to?(:as)
262
- left.as(o.right)
310
+ # patch for 4+, binds params using native to_sql before transforms to sql string
311
+ if ::ActiveRecord::VERSION::MAJOR >= 4 && o.left.is_a?(::ActiveRecord::Relation)
312
+ Arel::Nodes::TableAlias.new(
313
+ Arel::Nodes::Grouping.new(
314
+ Arel::Nodes::SqlLiteral.new(
315
+ o.left.respond_to?(:to_sql_with_binding_params) ? o.left.to_sql_with_binding_params : o.left.to_sql
316
+ )
317
+ ),
318
+ o.right
319
+ )
263
320
  else
264
- Arel::Nodes::As.new(left, o.right)
321
+ left = visit(o.left, parent)
322
+ # Some nodes, like Arel::SelectManager, have their own #as methods,
323
+ # with behavior that we don't want to clobber.
324
+ if left.respond_to?(:as)
325
+ left.as(o.right)
326
+ else
327
+ Arel::Nodes::As.new(left, o.right)
328
+ end
265
329
  end
266
330
  end
267
331
 
@@ -313,18 +377,22 @@ module Squeel
313
377
  def visit_Squeel_Nodes_Function(o, parent)
314
378
  args = o.args.map do |arg|
315
379
  case arg
316
- when Nodes::Function, Nodes::As, Nodes::Literal, Nodes::Grouping, Nodes::KeyPath, Nodes::KeyPath
380
+ when Nodes::Function, Nodes::As, Nodes::Literal, Nodes::Grouping, Nodes::KeyPath
317
381
  visit(arg, parent)
318
382
  when ActiveRecord::Relation
319
383
  arg.arel.ast
320
384
  when Symbol, Nodes::Stub
321
- Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_s])
385
+ if defined?(Arel::Collectors::SQLString)
386
+ Arel.sql(arel_visitor.compile(contextualize(parent)[arg.to_s]))
387
+ else
388
+ Arel.sql(arel_visitor.accept(contextualize(parent)[arg.to_s]))
389
+ end
322
390
  else
323
391
  quote arg
324
392
  end
325
393
  end
326
394
 
327
- Arel::Nodes::NamedFunction.new(o.function_name, args)
395
+ Arel::Nodes::NamedFunction.new(o.function_name.to_s, args)
328
396
  end
329
397
 
330
398
  # Visit a Squeel operation node, convering it to an Arel InfixOperation
@@ -340,7 +408,11 @@ module Squeel
340
408
  when Nodes::Function, Nodes::As, Nodes::Literal, Nodes::Grouping, Nodes::KeyPath
341
409
  visit(arg, parent)
342
410
  when Symbol, Nodes::Stub
343
- Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_s])
411
+ if defined?(Arel::Collectors::SQLString)
412
+ Arel.sql(arel_visitor.compile(contextualize(parent)[arg.to_s]))
413
+ else
414
+ Arel.sql(arel_visitor.accept(contextualize(parent)[arg.to_s]))
415
+ end
344
416
  else
345
417
  quote arg
346
418
  end
@@ -381,6 +453,10 @@ module Squeel
381
453
  o.id
382
454
  end
383
455
 
456
+ def visit_Arel_Nodes_Node(o, parent)
457
+ o
458
+ end
459
+
384
460
  end
385
461
  end
386
462
  end
@@ -0,0 +1,13 @@
1
+ databases:
2
+ mysql:
3
+ database: squeel_test_examples
4
+ username: travis
5
+ encoding: utf8
6
+ mysql2:
7
+ database: squeel_test_examples
8
+ username: travis
9
+ encoding: utf8
10
+ postgresql:
11
+ database: squeel_test_examples
12
+ min_messages: warning
13
+ username: postgres
@@ -0,0 +1,12 @@
1
+ databases:
2
+ mysql:
3
+ database: squeel_test_examples
4
+ username: squeel
5
+ encoding: utf8
6
+ mysql2:
7
+ database: squeel_test_examples
8
+ username: squeel
9
+ encoding: utf8
10
+ postgresql:
11
+ database: squeel_test_examples
12
+ min_messages: warning
@@ -1,6 +1,7 @@
1
1
  Bundler.setup
2
- require 'machinist/active_record'
3
- require 'sham'
2
+ require 'pp'
3
+ require 'active_record'
4
+ require 'active_support'
4
5
  require 'faker'
5
6
 
6
7
  Dir[File.expand_path('../helpers/*.rb', __FILE__)].each do |f|
@@ -9,16 +10,6 @@ end
9
10
  require File.expand_path('../support/schema.rb', __FILE__)
10
11
  require File.expand_path('../support/models.rb', __FILE__)
11
12
 
12
- Sham.define do
13
- name { Faker::Name.name }
14
- title { Faker::Lorem.sentence }
15
- body { Faker::Lorem.paragraph }
16
- salary {|index| 30000 + (index * 1000)}
17
- tag_name { Faker::Lorem.words(3).join(' ') }
18
- note { Faker::Lorem.words(7).join(' ') }
19
- object_name { Faker::Lorem.words(1).first }
20
- end
21
-
22
13
  Models.make
23
14
 
24
15
  require 'squeel'
@@ -56,13 +56,13 @@ describe Symbol do
56
56
  it 'creates inner joins' do
57
57
  join = :join.inner
58
58
  join.should be_a Squeel::Nodes::Join
59
- join._type.should eq Arel::InnerJoin
59
+ join._type.should eq Squeel::InnerJoin
60
60
  end
61
61
 
62
62
  it 'creates outer joins' do
63
63
  join = :join.outer
64
64
  join.should be_a Squeel::Nodes::Join
65
- join._type.should eq Arel::OuterJoin
65
+ join._type.should eq Squeel::OuterJoin
66
66
  end
67
67
 
68
68
  it 'creates as nodes' do
@@ -72,4 +72,4 @@ describe Symbol do
72
72
  as.right.should eq 'other_name'
73
73
  end
74
74
 
75
- end
75
+ end
@@ -1,4 +1,11 @@
1
1
  module SqueelHelper
2
+ JoinDependency =
3
+ if defined?(::ActiveRecord::Associations::JoinDependency)
4
+ ::ActiveRecord::Associations::JoinDependency
5
+ else
6
+ ::ActiveRecord::Associations::ClassMethods::JoinDependency
7
+ end
8
+
2
9
  def dsl(&block)
3
10
  Squeel::DSL.eval(&block)
4
11
  end
@@ -16,11 +23,7 @@ module SqueelHelper
16
23
  end
17
24
 
18
25
  def new_join_dependency(*args)
19
- if defined?(ActiveRecord::Associations::JoinDependency)
20
- ActiveRecord::Associations::JoinDependency.new(*args)
21
- else
22
- ActiveRecord::Associations::ClassMethods::JoinDependency.new(*args)
23
- end
26
+ JoinDependency.new(*args)
24
27
  end
25
28
 
26
29
  def activerecord_version_at_least(version_string)
@@ -1,6 +1,6 @@
1
- require 'machinist/active_record'
2
- require 'sham'
3
1
  require 'faker'
2
+ require 'active_record'
3
+ require 'active_support'
4
4
 
5
5
  module ActiveRecord
6
6
  # Shamelessly swiped from the AR test code
@@ -21,11 +21,11 @@ module ActiveRecord
21
21
  end
22
22
  end
23
23
  end
24
- ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
24
+ ::ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
25
25
  end
26
26
 
27
27
  unless ENV['DEPRECATIONS']
28
- ActiveSupport::Deprecation.silenced = true
28
+ ::ActiveSupport::Deprecation.silenced = true
29
29
  end
30
30
 
31
31
  Dir[File.expand_path('../helpers/*.rb', __FILE__)].each do |f|
@@ -34,16 +34,6 @@ end
34
34
  require File.expand_path('../support/schema.rb', __FILE__)
35
35
  require File.expand_path('../support/models.rb', __FILE__)
36
36
 
37
- Sham.define do
38
- name { Faker::Name.name }
39
- title { Faker::Lorem.sentence }
40
- body { Faker::Lorem.paragraph }
41
- salary {|index| 30000 + (index * 1000)}
42
- tag_name { Faker::Lorem.words(3).join(' ') }
43
- note { Faker::Lorem.words(7).join(' ') }
44
- object_name { Faker::Lorem.words(1).first }
45
- end
46
-
47
37
  RSpec.configure do |config|
48
38
  config.before(:suite) do
49
39
  puts '=' * 80
@@ -51,8 +41,6 @@ RSpec.configure do |config|
51
41
  puts '=' * 80
52
42
  Models.make
53
43
  end
54
- config.before(:all) { Sham.reset(:before_all) }
55
- config.before(:each) { Sham.reset(:before_each) }
56
44
 
57
45
  config.include SqueelHelper
58
46
  end
@@ -16,7 +16,11 @@ module Squeel
16
16
  end
17
17
 
18
18
  it 'contextualizes join parts with the proper alias' do
19
- table = @c.contextualize @jd._join_parts.last
19
+ table = if activerecord_version_at_least('4.1.0')
20
+ @c.contextualize @jd.join_root.children.last.children.last.children.last.children.last
21
+ else
22
+ @c.contextualize @jd.join_associations.last
23
+ end
20
24
  table.table_alias.should eq 'parents_people_2'
21
25
  end
22
26
 
@@ -27,13 +31,13 @@ module Squeel
27
31
  end
28
32
 
29
33
  it 'contextualizes polymorphic Join nodes to the arel_table of their klass' do
30
- table = @c.contextualize Nodes::Join.new(:notable, Arel::InnerJoin, Article)
34
+ table = @c.contextualize Nodes::Join.new(:notable, Squeel::InnerJoin, Article)
31
35
  table.name.should eq 'articles'
32
36
  table.table_alias.should be_nil
33
37
  end
34
38
 
35
39
  it 'contextualizes non-polymorphic Join nodes to the table for their name' do
36
- table = @c.contextualize Nodes::Join.new(:notes, Arel::InnerJoin)
40
+ table = @c.contextualize Nodes::Join.new(:notes, Squeel::InnerJoin)
37
41
  table.name.should eq 'notes'
38
42
  table.table_alias.should be_nil
39
43
  end
@@ -41,4 +45,4 @@ module Squeel
41
45
  end
42
46
  end
43
47
  end
44
- end
48
+ end