squeel 1.1.1 → 1.2.1

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.
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