squeel_rbg 0.8.2

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 (80) hide show
  1. data/.gitignore +4 -0
  2. data/.yardopts +3 -0
  3. data/Gemfile +13 -0
  4. data/LICENSE +20 -0
  5. data/README.md +398 -0
  6. data/Rakefile +19 -0
  7. data/lib/core_ext/hash.rb +13 -0
  8. data/lib/core_ext/symbol.rb +39 -0
  9. data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
  10. data/lib/squeel/adapters/active_record/3.0/compat.rb +142 -0
  11. data/lib/squeel/adapters/active_record/3.0/context.rb +66 -0
  12. data/lib/squeel/adapters/active_record/3.0/join_association.rb +54 -0
  13. data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +84 -0
  14. data/lib/squeel/adapters/active_record/3.0/relation.rb +327 -0
  15. data/lib/squeel/adapters/active_record/context.rb +66 -0
  16. data/lib/squeel/adapters/active_record/join_association.rb +44 -0
  17. data/lib/squeel/adapters/active_record/join_dependency.rb +83 -0
  18. data/lib/squeel/adapters/active_record/preloader.rb +21 -0
  19. data/lib/squeel/adapters/active_record/relation.rb +351 -0
  20. data/lib/squeel/adapters/active_record.rb +28 -0
  21. data/lib/squeel/configuration.rb +54 -0
  22. data/lib/squeel/constants.rb +24 -0
  23. data/lib/squeel/context.rb +67 -0
  24. data/lib/squeel/dsl.rb +86 -0
  25. data/lib/squeel/nodes/aliasing.rb +13 -0
  26. data/lib/squeel/nodes/and.rb +9 -0
  27. data/lib/squeel/nodes/as.rb +14 -0
  28. data/lib/squeel/nodes/binary.rb +32 -0
  29. data/lib/squeel/nodes/function.rb +66 -0
  30. data/lib/squeel/nodes/join.rb +113 -0
  31. data/lib/squeel/nodes/key_path.rb +192 -0
  32. data/lib/squeel/nodes/nary.rb +45 -0
  33. data/lib/squeel/nodes/not.rb +9 -0
  34. data/lib/squeel/nodes/operation.rb +32 -0
  35. data/lib/squeel/nodes/operators.rb +43 -0
  36. data/lib/squeel/nodes/or.rb +9 -0
  37. data/lib/squeel/nodes/order.rb +53 -0
  38. data/lib/squeel/nodes/predicate.rb +71 -0
  39. data/lib/squeel/nodes/predicate_operators.rb +29 -0
  40. data/lib/squeel/nodes/stub.rb +125 -0
  41. data/lib/squeel/nodes/unary.rb +28 -0
  42. data/lib/squeel/nodes.rb +17 -0
  43. data/lib/squeel/predicate_methods.rb +14 -0
  44. data/lib/squeel/version.rb +3 -0
  45. data/lib/squeel/visitors/attribute_visitor.rb +191 -0
  46. data/lib/squeel/visitors/base.rb +112 -0
  47. data/lib/squeel/visitors/predicate_visitor.rb +319 -0
  48. data/lib/squeel/visitors/symbol_visitor.rb +48 -0
  49. data/lib/squeel/visitors.rb +3 -0
  50. data/lib/squeel.rb +28 -0
  51. data/lib/squeel_rbg.rb +5 -0
  52. data/spec/blueprints/articles.rb +5 -0
  53. data/spec/blueprints/comments.rb +5 -0
  54. data/spec/blueprints/notes.rb +3 -0
  55. data/spec/blueprints/people.rb +4 -0
  56. data/spec/blueprints/tags.rb +3 -0
  57. data/spec/console.rb +22 -0
  58. data/spec/core_ext/symbol_spec.rb +75 -0
  59. data/spec/helpers/squeel_helper.rb +21 -0
  60. data/spec/spec_helper.rb +66 -0
  61. data/spec/squeel/adapters/active_record/context_spec.rb +44 -0
  62. data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
  63. data/spec/squeel/adapters/active_record/join_dependency_spec.rb +66 -0
  64. data/spec/squeel/adapters/active_record/relation_spec.rb +627 -0
  65. data/spec/squeel/dsl_spec.rb +92 -0
  66. data/spec/squeel/nodes/function_spec.rb +149 -0
  67. data/spec/squeel/nodes/join_spec.rb +47 -0
  68. data/spec/squeel/nodes/key_path_spec.rb +100 -0
  69. data/spec/squeel/nodes/operation_spec.rb +149 -0
  70. data/spec/squeel/nodes/operators_spec.rb +87 -0
  71. data/spec/squeel/nodes/order_spec.rb +30 -0
  72. data/spec/squeel/nodes/predicate_operators_spec.rb +88 -0
  73. data/spec/squeel/nodes/predicate_spec.rb +50 -0
  74. data/spec/squeel/nodes/stub_spec.rb +198 -0
  75. data/spec/squeel/visitors/attribute_visitor_spec.rb +142 -0
  76. data/spec/squeel/visitors/predicate_visitor_spec.rb +342 -0
  77. data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
  78. data/spec/support/schema.rb +104 -0
  79. data/squeel.gemspec +43 -0
  80. metadata +246 -0
@@ -0,0 +1,319 @@
1
+ require 'squeel/visitors/base'
2
+
3
+ module Squeel
4
+ module Visitors
5
+ class PredicateVisitor < Base
6
+
7
+ TRUE_SQL = Arel.sql('1=1').freeze
8
+ FALSE_SQL = Arel.sql('1=0').freeze
9
+
10
+ private
11
+
12
+ # Visit a Hash. This entails iterating through each key and value and
13
+ # visiting each value in turn.
14
+ #
15
+ # @param [Hash] o The Hash to visit
16
+ # @param parent The current parent object in the context
17
+ # @return [Array] An array of values for use in a where or having clause
18
+ def visit_Hash(o, parent)
19
+ predicates = o.map do |k, v|
20
+ if implies_context_change?(v)
21
+ visit_with_context_change(k, v, parent)
22
+ else
23
+ visit_without_context_change(k, v, parent)
24
+ end
25
+ end
26
+
27
+ predicates.flatten!
28
+
29
+ if predicates.size > 1
30
+ Arel::Nodes::Grouping.new(Arel::Nodes::And.new predicates)
31
+ else
32
+ predicates.first
33
+ end
34
+ end
35
+
36
+ # Visit an array, which involves accepting any values we know how to
37
+ # accept, and skipping the rest.
38
+ #
39
+ # @param [Array] o The Array to visit
40
+ # @param parent The current parent object in the context
41
+ # @return [Array] The visited array
42
+ def visit_Array(o, parent)
43
+ o.map { |v| can_accept?(v) ? accept(v, parent) : v }.flatten
44
+ end
45
+
46
+ # Visit ActiveRecord::Base objects. These should be converted to their
47
+ # id before being used in a comparison.
48
+ #
49
+ # @param [ActiveRecord::Base] o The AR::Base object to visit
50
+ # @param parent The current parent object in the context
51
+ # @return [Fixnum] The id of the object
52
+ def visit_ActiveRecord_Base(o, parent)
53
+ o.id
54
+ end
55
+
56
+ # Visit a KeyPath by traversing the path and then visiting the endpoint.
57
+ #
58
+ # @param [Nodes::KeyPath] o The KeyPath to visit
59
+ # @param parent The parent object in the context
60
+ # @return The visited endpoint, in the context of the KeyPath's path
61
+ def visit_Squeel_Nodes_KeyPath(o, parent)
62
+ parent = traverse(o, parent)
63
+
64
+ accept(o.endpoint, parent)
65
+ end
66
+
67
+ # Visit a Stub by converting it to an ARel attribute
68
+ #
69
+ # @param [Nodes::Stub] o The Stub to visit
70
+ # @param parent The parent object in the context
71
+ # @return [Arel::Attribute] An attribute of the parent table with the
72
+ # Stub's column
73
+ def visit_Squeel_Nodes_Stub(o, parent)
74
+ contextualize(parent)[o.symbol]
75
+ end
76
+
77
+ # Visit a Squeel predicate, converting it into an ARel predicate
78
+ #
79
+ # @param [Nodes::Predicate] o The predicate to visit
80
+ # @param parent The parent object in the context
81
+ # @return An ARel predicate node
82
+ # (Arel::Nodes::Equality, Arel::Nodes::Matches, etc)
83
+ def visit_Squeel_Nodes_Predicate(o, parent)
84
+ value = o.value
85
+
86
+ if Array === value && value.empty? && [:in, :not_in].include?(o.method_name)
87
+ # Special case, in/not_in with empty arrays should be false/true respectively
88
+ return o.method_name == :in ? FALSE_SQL : TRUE_SQL
89
+ end
90
+
91
+ if Nodes::KeyPath === value
92
+ value = can_accept?(value.endpoint) ? accept(value, parent) : contextualize(traverse(value, parent))[value.endpoint.to_sym]
93
+ else
94
+ value = accept(value, parent) if can_accept?(value)
95
+ end
96
+
97
+ case o.expr
98
+ when Nodes::Stub
99
+ accept(o.expr, parent).send(o.method_name, value)
100
+ when Nodes::Function
101
+ accept(o.expr, parent).send(o.method_name, quote(value))
102
+ else
103
+ contextualize(parent)[o.expr].send(o.method_name, value)
104
+ end
105
+ end
106
+
107
+ # Visit a Squeel function, returning an ARel NamedFunction node.
108
+ #
109
+ # @param [Nodes::Function] o The function node to visit
110
+ # @param parent The parent object in the context
111
+ # @return [Arel::Nodes::NamedFunction] A named function node. Function
112
+ # arguments are visited, if necessary, before being passed to the NamedFunction.
113
+ def visit_Squeel_Nodes_Function(o, parent)
114
+ args = o.args.map do |arg|
115
+ case arg
116
+ when Nodes::Function
117
+ accept(arg, parent)
118
+ when ActiveRecord::Relation
119
+ arg.arel.ast
120
+ when Nodes::KeyPath
121
+ can_accept?(arg.endpoint) ? accept(arg, parent) : contextualize(traverse(arg, parent))[arg.endpoint.to_sym]
122
+ when Symbol, Nodes::Stub
123
+ Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
124
+ else
125
+ quote arg
126
+ end
127
+ end
128
+ Arel::Nodes::NamedFunction.new(o.name, args, o.alias)
129
+ end
130
+
131
+ # Visit an ActiveRecord Relation, returning an Arel::SelectManager
132
+ # @param [ActiveRecord::Relation] o The Relation to visit
133
+ # @param parent The parent object in the context
134
+ # @return [Arel::SelectManager] The ARel select manager that represents
135
+ # the relation's query
136
+ def visit_ActiveRecord_Relation(o, parent)
137
+ o.arel
138
+ end
139
+
140
+ # Visit a Squeel operation node, convering it to an ARel InfixOperation
141
+ # (or subclass, as appropriate)
142
+ #
143
+ # @param [Nodes::Operation] o The Operation node to visit
144
+ # @param parent The parent object in the context
145
+ # @return [Arel::Nodes::InfixOperation] The InfixOperation (or Addition,
146
+ # Multiplication, etc) node, with both operands visited, if needed.
147
+ def visit_Squeel_Nodes_Operation(o, parent)
148
+ args = o.args.map do |arg|
149
+ case arg
150
+ when Nodes::Function
151
+ accept(arg, parent)
152
+ when Nodes::KeyPath
153
+ can_accept?(arg.endpoint) ? accept(arg, parent) : contextualize(traverse(arg, parent))[arg.endpoint.to_sym]
154
+ when Symbol, Nodes::Stub
155
+ Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
156
+ else
157
+ quote arg
158
+ end
159
+ end
160
+
161
+ op = case o.operator
162
+ when :+
163
+ Arel::Nodes::Addition.new(args[0], args[1])
164
+ when :-
165
+ Arel::Nodes::Subtraction.new(args[0], args[1])
166
+ when :*
167
+ Arel::Nodes::Multiplication.new(args[0], args[1])
168
+ when :/
169
+ Arel::Nodes::Division.new(args[0], args[1])
170
+ else
171
+ Arel::Nodes::InfixOperation(o.operator, args[0], args[1])
172
+ end
173
+ o.alias ? op.as(o.alias) : op
174
+ end
175
+
176
+ # Visit a Squeel And node, returning an ARel Grouping containing an
177
+ # ARel And node.
178
+ #
179
+ # @param [Nodes::And] o The And node to visit
180
+ # @param parent The parent object in the context
181
+ # @return [Arel::Nodes::Grouping] A grouping node, containnig an ARel
182
+ # And node as its expression. All children will be visited before
183
+ # being passed to the And.
184
+ def visit_Squeel_Nodes_And(o, parent)
185
+ Arel::Nodes::Grouping.new(Arel::Nodes::And.new(accept(o.children, parent)))
186
+ end
187
+
188
+ # Visit a Squeel Or node, returning an ARel Or node.
189
+ #
190
+ # @param [Nodes::Or] o The Or node to visit
191
+ # @param parent The parent object in the context
192
+ # @return [Arel::Nodes::Or] An ARel Or node, with left and right sides visited
193
+ def visit_Squeel_Nodes_Or(o, parent)
194
+ accept(o.left, parent).or(accept(o.right, parent))
195
+ end
196
+
197
+ def visit_Squeel_Nodes_Not(o, parent)
198
+ accept(o.expr, parent).not
199
+ end
200
+
201
+ # @return [Boolean] Whether the given value implies a context change
202
+ # @param v The value to consider
203
+ def implies_context_change?(v)
204
+ case v
205
+ when Hash, Nodes::Predicate, Nodes::Unary, Nodes::Binary, Nodes::Nary
206
+ true
207
+ when Nodes::KeyPath
208
+ can_accept?(v.endpoint) && !(Nodes::Stub === v.endpoint)
209
+ else
210
+ false
211
+ end
212
+ end
213
+
214
+ # Change context (by setting the new parent to the result of a #find or
215
+ # #traverse on the key), then accept the given value.
216
+ #
217
+ # @param k The hash key
218
+ # @param v The hash value
219
+ # @param parent The current parent object in the context
220
+ # @return The visited value
221
+ def visit_with_context_change(k, v, parent)
222
+ parent = case k
223
+ when Nodes::KeyPath
224
+ traverse(k, parent, true)
225
+ else
226
+ find(k, parent)
227
+ end
228
+
229
+ case v
230
+ when Hash, Nodes::KeyPath, Nodes::Predicate, Nodes::Unary, Nodes::Binary, Nodes::Nary
231
+ accept(v, parent || k)
232
+ when Array
233
+ v.map {|val| accept(val, parent || k)}
234
+ else
235
+ raise ArgumentError, <<-END
236
+ Hashes, Predicates, and arrays of visitables as values imply that their
237
+ corresponding keys are a parent. This didn't work out so well in the case
238
+ of key = #{k} and value = #{v}"
239
+ END
240
+ end
241
+ end
242
+
243
+ # Create a predicate for a given key/value pair. If the value is
244
+ # a Symbol, Stub, or KeyPath, it's converted to a table.column for
245
+ # the predicate value.
246
+ #
247
+ # @param k The hash key
248
+ # @param v The hash value
249
+ # @param parent The current parent object in the context
250
+ # @return An ARel predicate
251
+ def visit_without_context_change(k, v, parent)
252
+ case v
253
+ when Nodes::Stub, Symbol
254
+ v = contextualize(parent)[v.to_sym]
255
+ when Nodes::KeyPath # If we could accept the endpoint, we wouldn't be here
256
+ v = contextualize(traverse(v, parent))[v.endpoint.to_sym]
257
+ end
258
+
259
+ case k
260
+ when Nodes::Predicate
261
+ accept(k % quote_for_node(k.expr, v), parent)
262
+ when Nodes::Function
263
+ arel_predicate_for(accept(k, parent), quote(v), parent)
264
+ when Nodes::KeyPath
265
+ accept(k % quote_for_node(k.endpoint, v), parent)
266
+ else
267
+ attr_name = k.to_s
268
+ attribute = if attr_name.include?('.')
269
+ table_name, attr_name = attr_name.split(/\./, 2)
270
+ Arel::Table.new(table_name.to_sym, :engine => engine)[attr_name.to_sym]
271
+ else
272
+ contextualize(parent)[k.to_sym]
273
+ end
274
+ arel_predicate_for(attribute, v, parent)
275
+ end
276
+ end
277
+
278
+ # Determine whether to use IN or equality testing for a predicate,
279
+ # based on its value class, then return the appropriate predicate.
280
+ #
281
+ # @param attribute The ARel attribute (or function/operation) the
282
+ # predicate will be created for
283
+ # @param value The value to be compared against
284
+ # @return [Arel::Nodes::Node] An ARel predicate node
285
+ def arel_predicate_for(attribute, value, parent)
286
+ value = can_accept?(value) ? accept(value, parent) : value
287
+ if [Array, Range, Arel::SelectManager].include?(value.class)
288
+ if Array === value && value.empty?
289
+ FALSE_SQL
290
+ else
291
+ attribute.in(value)
292
+ end
293
+ else
294
+ attribute.eq(value)
295
+ end
296
+ end
297
+
298
+ # Function nodes require us to do the quoting before the ARel
299
+ # visitor gets a chance to try, because we want to avoid having our
300
+ # values quoted as a type of the last visited column. Otherwise, we
301
+ # can end up with annoyances like having "joe" quoted to 0, if the
302
+ # last visited column was of an integer type.
303
+ #
304
+ # @param node The node we (might) be quoting for
305
+ # @param v The value to (possibly) quote
306
+ def quote_for_node(node, v)
307
+ case node
308
+ when Nodes::Function
309
+ quote(v)
310
+ when Nodes::Predicate
311
+ Nodes::Function === node.expr ? quote(v) : v
312
+ else
313
+ v
314
+ end
315
+ end
316
+
317
+ end
318
+ end
319
+ end
@@ -0,0 +1,48 @@
1
+ require 'squeel/visitors/base'
2
+
3
+ module Squeel
4
+ module Visitors
5
+ class SymbolVisitor < Base
6
+
7
+ def initialize
8
+ end
9
+
10
+ def accept(object, parent = nil)
11
+ visit(object, parent)
12
+ end
13
+
14
+ private
15
+
16
+ def visit_Array(o, parent)
17
+ o.map {|e| accept(e, parent)}.flatten
18
+ end
19
+
20
+ def visit_Hash(o, parent)
21
+ {}.tap do |hash|
22
+ o.each do |key, value|
23
+ hash[accept(key, parent)] = accept(value, parent)
24
+ end
25
+ end
26
+ end
27
+
28
+ def visit_Symbol(o, parent)
29
+ o
30
+ end
31
+
32
+ def visit_Squeel_Nodes_Stub(o, parent)
33
+ o.symbol
34
+ end
35
+
36
+ def visit_Squeel_Nodes_KeyPath(o, parent)
37
+ o.path_with_endpoint.reverse.map(&:to_sym).inject do |hash, key|
38
+ {key => hash}
39
+ end
40
+ end
41
+
42
+ def visit_Squeel_Nodes_Join(o, parent)
43
+ o._name
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ require 'squeel/visitors/predicate_visitor'
2
+ require 'squeel/visitors/attribute_visitor'
3
+ require 'squeel/visitors/symbol_visitor'
data/lib/squeel.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'squeel/configuration'
2
+
3
+ module Squeel
4
+
5
+ extend Configuration
6
+
7
+ # Prevent warnings on the console when doing things some might describe as "evil"
8
+ def self.evil_things
9
+ original_verbosity = $VERBOSE
10
+ $VERBOSE = nil
11
+ yield
12
+ ensure
13
+ $VERBOSE = original_verbosity
14
+ end
15
+
16
+ # Set up initial predicate aliases
17
+ Constants::PREDICATE_ALIASES.each do |original, aliases|
18
+ aliases.each do |aliaz|
19
+ alias_predicate aliaz, original
20
+ end
21
+ end
22
+
23
+ end
24
+
25
+ require 'squeel/nodes'
26
+ require 'squeel/dsl'
27
+ require 'squeel/visitors'
28
+ require 'squeel/adapters/active_record'
data/lib/squeel_rbg.rb ADDED
@@ -0,0 +1,5 @@
1
+ module SqueelRbg
2
+
3
+ extend Squeel
4
+
5
+ end
@@ -0,0 +1,5 @@
1
+ Article.blueprint do
2
+ person
3
+ title
4
+ body
5
+ end
@@ -0,0 +1,5 @@
1
+ Comment.blueprint do
2
+ article
3
+ person
4
+ body
5
+ end
@@ -0,0 +1,3 @@
1
+ Note.blueprint do
2
+ note
3
+ end
@@ -0,0 +1,4 @@
1
+ Person.blueprint do
2
+ name
3
+ salary
4
+ end
@@ -0,0 +1,3 @@
1
+ Tag.blueprint do
2
+ name { Sham.tag_name }
3
+ end
data/spec/console.rb ADDED
@@ -0,0 +1,22 @@
1
+ Bundler.setup
2
+ require 'machinist/active_record'
3
+ require 'sham'
4
+ require 'faker'
5
+
6
+ Dir[File.expand_path('../../spec/{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
7
+ require f
8
+ end
9
+
10
+ Sham.define do
11
+ name { Faker::Name.name }
12
+ title { Faker::Lorem.sentence }
13
+ body { Faker::Lorem.paragraph }
14
+ salary {|index| 30000 + (index * 1000)}
15
+ tag_name { Faker::Lorem.words(3).join(' ') }
16
+ note { Faker::Lorem.words(7).join(' ') }
17
+ end
18
+
19
+ Schema.create
20
+
21
+ require 'squeel'
22
+
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ describe Symbol do
4
+
5
+ Squeel::Constants::PREDICATES.each do |method_name|
6
+ it "creates #{method_name} predicates with no value" do
7
+ predicate = :attribute.send(method_name)
8
+ predicate.expr.should eq :attribute
9
+ predicate.method_name.should eq method_name
10
+ predicate.value?.should be_false
11
+ end
12
+
13
+ it "creates #{method_name} predicates with a value" do
14
+ predicate = :attribute.send(method_name, 'value')
15
+ predicate.expr.should eq :attribute
16
+ predicate.method_name.should eq method_name
17
+ predicate.value.should eq 'value'
18
+ end
19
+ end
20
+
21
+ Squeel::Constants::PREDICATE_ALIASES.each do |method_name, aliases|
22
+ aliases.each do |aliaz|
23
+ ['', '_any', '_all'].each do |suffix|
24
+ it "creates #{method_name.to_s + suffix} predicates with no value using the alias #{aliaz.to_s + suffix}" do
25
+ predicate = :attribute.send(aliaz.to_s + suffix)
26
+ predicate.expr.should eq :attribute
27
+ predicate.method_name.should eq "#{method_name}#{suffix}".to_sym
28
+ predicate.value?.should be_false
29
+ end
30
+
31
+ it "creates #{method_name.to_s + suffix} predicates with a value using the alias #{aliaz.to_s + suffix}" do
32
+ predicate = :attribute.send((aliaz.to_s + suffix), 'value')
33
+ predicate.expr.should eq :attribute
34
+ predicate.method_name.should eq "#{method_name}#{suffix}".to_sym
35
+ predicate.value.should eq 'value'
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ it 'creates ascending orders' do
42
+ order = :attribute.asc
43
+ order.should be_ascending
44
+ end
45
+
46
+ it 'creates descending orders' do
47
+ order = :attribute.desc
48
+ order.should be_descending
49
+ end
50
+
51
+ it 'creates functions' do
52
+ function = :function.func
53
+ function.should be_a Squeel::Nodes::Function
54
+ end
55
+
56
+ it 'creates inner joins' do
57
+ join = :join.inner
58
+ join.should be_a Squeel::Nodes::Join
59
+ join._type.should eq Arel::InnerJoin
60
+ end
61
+
62
+ it 'creates outer joins' do
63
+ join = :join.outer
64
+ join.should be_a Squeel::Nodes::Join
65
+ join._type.should eq Arel::OuterJoin
66
+ end
67
+
68
+ it 'creates as nodes' do
69
+ as = :column.as('other_name')
70
+ as.should be_a Squeel::Nodes::As
71
+ as.left.should eq :column
72
+ as.right.should eq 'other_name'
73
+ end
74
+
75
+ end
@@ -0,0 +1,21 @@
1
+ module SqueelHelper
2
+ def dsl(&block)
3
+ Squeel::DSL.eval(&block)
4
+ end
5
+
6
+ def queries_for
7
+ $queries_executed = []
8
+ yield
9
+ $queries_executed
10
+ ensure
11
+ %w{ BEGIN COMMIT }.each { |x| $queries_executed.delete(x) }
12
+ end
13
+
14
+ def new_join_dependency(*args)
15
+ if defined?(ActiveRecord::Associations::JoinDependency)
16
+ ActiveRecord::Associations::JoinDependency.new(*args)
17
+ else
18
+ ActiveRecord::Associations::ClassMethods::JoinDependency.new(*args)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,66 @@
1
+ require 'machinist/active_record'
2
+ require 'sham'
3
+ require 'faker'
4
+
5
+ module ActiveRecord
6
+ class SQLCounter
7
+ IGNORED_SQL = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/,
8
+ /SELECT name\s+FROM sqlite_master\s+WHERE type = 'table' AND NOT name = 'sqlite_sequence'/]
9
+
10
+ # FIXME: this needs to be refactored so specific database can add their own
11
+ # ignored SQL. This ignored SQL is for Oracle.
12
+ IGNORED_SQL.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
13
+
14
+ def initialize
15
+ $queries_executed = []
16
+ end
17
+
18
+ def call(name, start, finish, message_id, values)
19
+ sql = values[:sql]
20
+
21
+ unless 'CACHE' == values[:name]
22
+ $queries_executed << sql unless IGNORED_SQL.any? { |r| sql =~ r }
23
+ end
24
+ end
25
+ end
26
+ ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
27
+ end
28
+
29
+ Dir[File.expand_path('../{helpers,support,blueprints}/*.rb', __FILE__)].each do |f|
30
+ require f
31
+ end
32
+
33
+ Sham.define do
34
+ name { Faker::Name.name }
35
+ title { Faker::Lorem.sentence }
36
+ body { Faker::Lorem.paragraph }
37
+ salary {|index| 30000 + (index * 1000)}
38
+ tag_name { Faker::Lorem.words(3).join(' ') }
39
+ note { Faker::Lorem.words(7).join(' ') }
40
+ end
41
+
42
+ RSpec.configure do |config|
43
+ config.before(:suite) do
44
+ puts '=' * 80
45
+ puts "Running specs against ActiveRecord #{ActiveRecord::VERSION::STRING} and ARel #{Arel::VERSION}..."
46
+ puts '=' * 80
47
+ Schema.create
48
+ end
49
+ config.before(:all) { Sham.reset(:before_all) }
50
+ config.before(:each) { Sham.reset(:before_each) }
51
+
52
+ config.include SqueelHelper
53
+ end
54
+
55
+ RSpec::Matchers.define :be_like do |expected|
56
+ match do |actual|
57
+ actual.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip ==
58
+ expected.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip
59
+ end
60
+ end
61
+
62
+ require 'squeel'
63
+
64
+ Squeel.configure do |config|
65
+ config.load_core_extensions :hash, :symbol
66
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ module Squeel
4
+ module Adapters
5
+ module ActiveRecord
6
+ describe Context do
7
+ before do
8
+ @jd = new_join_dependency(Person, {
9
+ :children => {
10
+ :children => {
11
+ :parent => :parent
12
+ }
13
+ }
14
+ }, [])
15
+ @c = Context.new(@jd)
16
+ end
17
+
18
+ it 'contextualizes join parts with the proper alias' do
19
+ table = @c.contextualize @jd.join_parts.last
20
+ table.table_alias.should eq 'parents_people_2'
21
+ end
22
+
23
+ it 'contextualizes symbols as a generic table' do
24
+ table = @c.contextualize :table
25
+ table.name.should eq 'table'
26
+ table.table_alias.should be_nil
27
+ end
28
+
29
+ it 'contextualizes polymorphic Join nodes to the arel_table of their klass' do
30
+ table = @c.contextualize Nodes::Join.new(:notable, Arel::InnerJoin, Article)
31
+ table.name.should eq 'articles'
32
+ table.table_alias.should be_nil
33
+ end
34
+
35
+ it 'contextualizes non-polymorphic Join nodes to the table for their name' do
36
+ table = @c.contextualize Nodes::Join.new(:notes, Arel::InnerJoin)
37
+ table.name.should eq 'notes'
38
+ table.table_alias.should be_nil
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ module Squeel
2
+ module Adapters
3
+ module ActiveRecord
4
+ describe JoinAssociation do
5
+ before do
6
+ @jd = new_join_dependency(Note, {}, [])
7
+ @notable = Note.reflect_on_association(:notable)
8
+ end
9
+
10
+ it 'accepts a 4th parameter to set a polymorphic class' do
11
+ join_association = JoinAssociation.new(@notable, @jd, @jd.join_base, Article)
12
+ join_association.reflection.klass.should eq Article
13
+ end
14
+
15
+ end
16
+ end
17
+ end
18
+ end