squeel 0.8.4 → 0.8.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/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - ree
5
+ - jruby
6
+ - rbx
7
+ - rbx-2.0
8
+ - ruby-head
data/Gemfile CHANGED
@@ -1,6 +1,8 @@
1
1
  source "http://rubygems.org"
2
2
  gemspec
3
3
 
4
+ gem 'rake'
5
+
4
6
  if ENV['RAILS_VERSION'] == 'release'
5
7
  gem 'activesupport'
6
8
  gem 'activerecord'
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Squeel
1
+ # Squeel [![Build Status](http://travis-ci.org/ernie/squeel.png)](http://travis-ci.org/ernie/squeel)
2
2
 
3
3
  Squeel lets you write your ActiveRecord queries with with fewer strings, and more Ruby,
4
4
  by making the ARel awesomeness that lies beneath ActiveRecord more accessible.
@@ -5,10 +5,6 @@ module Squeel
5
5
  module ActiveRecord
6
6
  module JoinDependency
7
7
 
8
- # Yes, I'm using alias_method_chain here. No, I don't feel too
9
- # bad about it. JoinDependency, or, to call it by its full proper
10
- # name, ::ActiveRecord::Associations::JoinDependency, is one of the
11
- # most "for internal use only" chunks of ActiveRecord.
12
8
  def self.included(base)
13
9
  base.class_eval do
14
10
  alias_method_chain :build, :squeel
@@ -62,8 +62,8 @@ module Squeel
62
62
  end
63
63
 
64
64
  def prepare_relation_for_association_merge!(r, association_name)
65
- r.where_values.map! {|w| Squeel::Visitors::PredicateVisitor.can_accept?(w) ? {association_name => w} : w}
66
- r.having_values.map! {|h| Squeel::Visitors::PredicateVisitor.can_accept?(h) ? {association_name => h} : h}
65
+ r.where_values.map! {|w| Squeel::Visitors::PredicateVisitor.can_visit?(w) ? {association_name => w} : w}
66
+ r.having_values.map! {|h| Squeel::Visitors::PredicateVisitor.can_visit?(h) ? {association_name => h} : h}
67
67
  r.joins_values.map! {|j| [Symbol, Hash, Nodes::Stub, Nodes::Join].include?(j.class) ? {association_name => j} : j}
68
68
  end
69
69
 
@@ -5,10 +5,6 @@ module Squeel
5
5
  module ActiveRecord
6
6
  module JoinDependency
7
7
 
8
- # Yes, I'm using alias_method_chain here. No, I don't feel too
9
- # bad about it. JoinDependency, or, to call it by its full proper
10
- # name, ::ActiveRecord::Associations::JoinDependency, is one of the
11
- # most "for internal use only" chunks of ActiveRecord.
12
8
  def self.included(base)
13
9
  base.class_eval do
14
10
  alias_method_chain :build, :squeel
@@ -61,11 +61,11 @@ module Squeel
61
61
  end
62
62
 
63
63
  def prepare_relation_for_association_merge!(r, association_name)
64
- r.where_values.map! {|w| Squeel::Visitors::PredicateVisitor.can_accept?(w) ? {association_name => w} : w}
65
- r.having_values.map! {|h| Squeel::Visitors::PredicateVisitor.can_accept?(h) ? {association_name => h} : h}
66
- r.group_values.map! {|g| Squeel::Visitors::AttributeVisitor.can_accept?(g) ? {association_name => g} : g}
67
- r.order_values.map! {|o| Squeel::Visitors::AttributeVisitor.can_accept?(o) ? {association_name => o} : o}
68
- r.select_values.map! {|s| Squeel::Visitors::AttributeVisitor.can_accept?(s) ? {association_name => s} : s}
64
+ r.where_values.map! {|w| Squeel::Visitors::PredicateVisitor.can_visit?(w) ? {association_name => w} : w}
65
+ r.having_values.map! {|h| Squeel::Visitors::PredicateVisitor.can_visit?(h) ? {association_name => h} : h}
66
+ r.group_values.map! {|g| Squeel::Visitors::AttributeVisitor.can_visit?(g) ? {association_name => g} : g}
67
+ r.order_values.map! {|o| Squeel::Visitors::AttributeVisitor.can_visit?(o) ? {association_name => o} : o}
68
+ r.select_values.map! {|s| Squeel::Visitors::AttributeVisitor.can_visit?(s) ? {association_name => s} : s}
69
69
  r.joins_values.map! {|j| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(j.class) ? {association_name => j} : j}
70
70
  r.includes_values.map! {|i| [Symbol, Hash, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(i.class) ? {association_name => i} : i}
71
71
  end
data/lib/squeel/dsl.rb CHANGED
@@ -50,11 +50,19 @@ module Squeel
50
50
  # programmer hell.
51
51
  #
52
52
  # @param &block A block to instance_eval against the DSL's caller.
53
- # @return
53
+ # @return The results of evaluating the block in the instance of the DSL's caller.
54
54
  def my(&block)
55
55
  @caller.instance_eval &block
56
56
  end
57
57
 
58
+ # Shorthand for creating ARel SqlLiteral nodes.
59
+ #
60
+ # @param [String] string The string to convert to an SQL literal.
61
+ # @return [Arel::Nodes::SqlLiteral] The SQL literal.
62
+ def `(string)
63
+ Nodes::Literal.new(string)
64
+ end
65
+
58
66
  # Node generation inside DSL blocks.
59
67
  #
60
68
  # @overload node_name
data/lib/squeel/nodes.rb CHANGED
@@ -4,6 +4,7 @@ module Squeel
4
4
  module Nodes
5
5
  end
6
6
  end
7
+ require 'squeel/nodes/literal'
7
8
  require 'squeel/nodes/stub'
8
9
  require 'squeel/nodes/key_path'
9
10
  require 'squeel/nodes/predicate'
@@ -0,0 +1,70 @@
1
+ require 'squeel/predicate_methods'
2
+ require 'squeel/nodes/operators'
3
+ require 'squeel/nodes/aliasing'
4
+
5
+ module Squeel
6
+ module Nodes
7
+ # Literal nodes are a container for raw SQL.
8
+ class Literal
9
+ include PredicateMethods
10
+ include Operators
11
+ include Aliasing
12
+
13
+ attr_reader :expr
14
+
15
+ def initialize(expr)
16
+ @expr = expr
17
+ end
18
+
19
+ alias :== :eq
20
+ alias :'^' :not_eq
21
+ alias :'!=' :not_eq if respond_to?(:'!=')
22
+ alias :>> :in
23
+ alias :<< :not_in
24
+ alias :=~ :matches
25
+ alias :'!~' :does_not_match if respond_to?(:'!~')
26
+ alias :> :gt
27
+ alias :>= :gteq
28
+ alias :< :lt
29
+ alias :<= :lteq
30
+
31
+ # Create an ascending Order node with this Literal as its expression
32
+ # @return [Order] The new Order node
33
+ def asc
34
+ Order.new self, 1
35
+ end
36
+
37
+ # Create a descending Order node with this Literal as its expression
38
+ # @return [Order] The new Order node
39
+ def desc
40
+ Order.new self, -1
41
+ end
42
+
43
+ # Object comparison
44
+ def eql?(other)
45
+ self.class == other.class &&
46
+ self.expr == other.expr
47
+ end
48
+
49
+ # To support object equality tests
50
+ def hash
51
+ expr.hash
52
+ end
53
+
54
+ # expand_hash_conditions_for_aggregates assumes our hash keys can be
55
+ # converted to symbols, so this has to be implemented, but it doesn't
56
+ # really have to do anything useful.
57
+ # @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
58
+ def to_sym
59
+ nil
60
+ end
61
+
62
+ # @return [String] The Literal's String equivalent.
63
+ def to_s
64
+ expr.to_s
65
+ end
66
+ alias :to_str :to_s
67
+
68
+ end
69
+ end
70
+ end
@@ -1,3 +1,3 @@
1
1
  module Squeel
2
- VERSION = "0.8.4"
2
+ VERSION = "0.8.5"
3
3
  end
@@ -31,7 +31,7 @@ module Squeel
31
31
  # @param parent The array's parent within the context
32
32
  # @return [Array] The flattened array with elements visited
33
33
  def visit_Array(o, parent)
34
- o.map { |v| can_accept?(v) ? accept(v, parent) : v }.flatten
34
+ o.map { |v| can_visit?(v) ? visit(v, parent) : v }.flatten
35
35
  end
36
36
 
37
37
  # Visit a symbol. This will return an attribute named after the symbol against
@@ -54,6 +54,15 @@ module Squeel
54
54
  contextualize(parent)[o.symbol]
55
55
  end
56
56
 
57
+ # Visit a Literal by converting it to an ARel SqlLiteral
58
+ #
59
+ # @param [Nodes::Literal] o The Literal to visit
60
+ # @param parent The parent object in the context (unused)
61
+ # @return [Arel::Nodes::SqlLiteral] An SqlLiteral
62
+ def visit_Squeel_Nodes_Literal(o, parent)
63
+ Arel.sql(o.expr)
64
+ end
65
+
57
66
  # Visit a keypath. This will traverse the keypath's "path", setting a new
58
67
  # parent as though the keypath's endpoint was in a deeply-nested hash,
59
68
  # then visit the endpoint with the new parent.
@@ -64,7 +73,7 @@ module Squeel
64
73
  def visit_Squeel_Nodes_KeyPath(o, parent)
65
74
  parent = traverse(o, parent)
66
75
 
67
- accept(o.endpoint, parent)
76
+ visit(o.endpoint, parent)
68
77
  end
69
78
 
70
79
  # Visit an Order node.
@@ -73,10 +82,10 @@ module Squeel
73
82
  # @param parent The node's parent within the context
74
83
  # @return [Arel::Nodes::Ordering] An ascending or desending ordering
75
84
  def visit_Squeel_Nodes_Order(o, parent)
76
- accept(o.expr, parent).send(o.descending? ? :desc : :asc)
85
+ visit(o.expr, parent).send(o.descending? ? :desc : :asc)
77
86
  end
78
87
 
79
- # Visit a Function node. Each function argument will be accepted or
88
+ # Visit a Function node. Each function argument will be visiteded or
80
89
  # contextualized if appropriate. Keep in mind that this occurs with
81
90
  # the current parent within the context.
82
91
  #
@@ -92,8 +101,8 @@ module Squeel
92
101
  def visit_Squeel_Nodes_Function(o, parent)
93
102
  args = o.args.map do |arg|
94
103
  case arg
95
- when Nodes::Function, Nodes::KeyPath
96
- accept(arg, parent)
104
+ when Nodes::Function, Nodes::KeyPath, Nodes::As, Nodes::Literal
105
+ visit(arg, parent)
97
106
  when Symbol, Nodes::Stub
98
107
  Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
99
108
  else
@@ -114,8 +123,8 @@ module Squeel
114
123
  def visit_Squeel_Nodes_Operation(o, parent)
115
124
  args = o.args.map do |arg|
116
125
  case arg
117
- when Nodes::Function
118
- accept(arg, parent)
126
+ when Nodes::Function, Nodes::KeyPath, Nodes::As, Nodes::Literal
127
+ visit(arg, parent)
119
128
  when Symbol, Nodes::Stub
120
129
  Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
121
130
  else
@@ -144,7 +153,7 @@ module Squeel
144
153
  # @param parent The parent object in the context
145
154
  # @return [Arel::Nodes::As] The resulting as node.
146
155
  def visit_Squeel_Nodes_As(o, parent)
147
- accept(o.left, parent).as(o.right)
156
+ visit(o.left, parent).as(o.right)
148
157
  end
149
158
 
150
159
  # Visit an ActiveRecord Relation, returning an Arel::SelectManager
@@ -159,7 +168,7 @@ module Squeel
159
168
  # @return [Boolean] Whether the given value implies a context change
160
169
  # @param v The value to consider
161
170
  def implies_context_change?(v)
162
- can_accept?(v)
171
+ can_visit?(v)
163
172
  end
164
173
 
165
174
  # Change context (by setting the new parent to the result of a #find or
@@ -178,9 +187,9 @@ module Squeel
178
187
  end
179
188
 
180
189
  if Array === v
181
- v.map {|val| accept(val, parent || k)}
190
+ v.map {|val| visit(val, parent || k)}
182
191
  else
183
- can_accept?(v) ? accept(v, parent || k) : v
192
+ can_visit?(v) ? visit(v, parent || k) : v
184
193
  end
185
194
  end
186
195
 
@@ -27,20 +27,20 @@ module Squeel
27
27
  end
28
28
 
29
29
  # @param object The object to check
30
- # @return [Boolean] Whether or not the visitor can accept the given object
31
- def can_accept?(object)
32
- self.class.can_accept? object
30
+ # @return [Boolean] Whether or not the visitor can visit the given object
31
+ def can_visit?(object)
32
+ self.class.can_visit? object
33
33
  end
34
34
 
35
35
  # @param object The object to check
36
- # @return [Boolean] Whether or not visitors of this class can accept the given object
37
- def self.can_accept?(object)
38
- @can_accept ||= Hash.new do |hash, klass|
36
+ # @return [Boolean] Whether or not visitors of this class can visit the given object
37
+ def self.can_visit?(object)
38
+ @can_visit ||= Hash.new do |hash, klass|
39
39
  hash[klass] = klass.ancestors.detect { |ancestor|
40
40
  private_method_defined? DISPATCH[ancestor]
41
41
  } ? true : false
42
42
  end
43
- @can_accept[object.class]
43
+ @can_visit[object.class]
44
44
  end
45
45
 
46
46
  private
@@ -89,8 +89,7 @@ module Squeel
89
89
  end
90
90
  end
91
91
 
92
- # Visit the object. This is not called directly, but instead via the public
93
- # #accept method.
92
+ # Visit the object.
94
93
  #
95
94
  # @param object The object to visit
96
95
  # @param parent The object's parent within the context
@@ -40,7 +40,7 @@ module Squeel
40
40
  # @param parent The current parent object in the context
41
41
  # @return [Array] The visited array
42
42
  def visit_Array(o, parent)
43
- o.map { |v| can_accept?(v) ? accept(v, parent) : v }.flatten
43
+ o.map { |v| can_visit?(v) ? visit(v, parent) : v }.flatten
44
44
  end
45
45
 
46
46
  # Visit ActiveRecord::Base objects. These should be converted to their
@@ -61,7 +61,7 @@ module Squeel
61
61
  def visit_Squeel_Nodes_KeyPath(o, parent)
62
62
  parent = traverse(o, parent)
63
63
 
64
- accept(o.endpoint, parent)
64
+ visit(o.endpoint, parent)
65
65
  end
66
66
 
67
67
  # Visit a Stub by converting it to an ARel attribute
@@ -74,6 +74,15 @@ module Squeel
74
74
  contextualize(parent)[o.symbol]
75
75
  end
76
76
 
77
+ # Visit a Literal by converting it to an ARel SqlLiteral
78
+ #
79
+ # @param [Nodes::Literal] o The Literal to visit
80
+ # @param parent The parent object in the context (unused)
81
+ # @return [Arel::Nodes::SqlLiteral] An SqlLiteral
82
+ def visit_Squeel_Nodes_Literal(o, parent)
83
+ Arel.sql(o.expr)
84
+ end
85
+
77
86
  # Visit a Squeel predicate, converting it into an ARel predicate
78
87
  #
79
88
  # @param [Nodes::Predicate] o The predicate to visit
@@ -83,24 +92,25 @@ module Squeel
83
92
  def visit_Squeel_Nodes_Predicate(o, parent)
84
93
  value = o.value
85
94
 
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
95
+ if Nodes::KeyPath === value
96
+ value = can_visit?(value.endpoint) ? visit(value, parent) : contextualize(traverse(value, parent))[value.endpoint.to_sym]
97
+ else
98
+ value = visit(value, parent) if can_visit?(value)
89
99
  end
90
100
 
91
- if Nodes::KeyPath === value
92
- value = can_accept?(value.endpoint) ? accept(value, parent) : contextualize(traverse(value, parent))[value.endpoint.to_sym]
101
+ value = quote_for_node(o.expr, value)
102
+
103
+ attribute = case o.expr
104
+ when Nodes::Stub, Nodes::Function, Nodes::Literal
105
+ visit(o.expr, parent)
93
106
  else
94
- value = accept(value, parent) if can_accept?(value)
107
+ contextualize(parent)[o.expr]
95
108
  end
96
109
 
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))
110
+ if Array === value && [:in, :not_in].include?(o.method_name)
111
+ o.method_name == :in ? attribute_in_array(attribute, value) : attribute_not_in_array(attribute, value)
102
112
  else
103
- contextualize(parent)[o.expr].send(o.method_name, value)
113
+ attribute.send(o.method_name, value)
104
114
  end
105
115
  end
106
116
 
@@ -113,12 +123,12 @@ module Squeel
113
123
  def visit_Squeel_Nodes_Function(o, parent)
114
124
  args = o.args.map do |arg|
115
125
  case arg
116
- when Nodes::Function
117
- accept(arg, parent)
126
+ when Nodes::Function, Nodes::As, Nodes::Literal
127
+ visit(arg, parent)
118
128
  when ActiveRecord::Relation
119
129
  arg.arel.ast
120
130
  when Nodes::KeyPath
121
- can_accept?(arg.endpoint) ? accept(arg, parent) : contextualize(traverse(arg, parent))[arg.endpoint.to_sym]
131
+ can_visit?(arg.endpoint) ? visit(arg, parent) : contextualize(traverse(arg, parent))[arg.endpoint.to_sym]
122
132
  when Symbol, Nodes::Stub
123
133
  Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
124
134
  else
@@ -149,10 +159,10 @@ module Squeel
149
159
  def visit_Squeel_Nodes_Operation(o, parent)
150
160
  args = o.args.map do |arg|
151
161
  case arg
152
- when Nodes::Function
153
- accept(arg, parent)
162
+ when Nodes::Function, Nodes::As, Nodes::Literal
163
+ visit(arg, parent)
154
164
  when Nodes::KeyPath
155
- can_accept?(arg.endpoint) ? accept(arg, parent) : contextualize(traverse(arg, parent))[arg.endpoint.to_sym]
165
+ can_visit?(arg.endpoint) ? visit(arg, parent) : contextualize(traverse(arg, parent))[arg.endpoint.to_sym]
156
166
  when Symbol, Nodes::Stub
157
167
  Arel.sql(arel_visitor.accept contextualize(parent)[arg.to_sym])
158
168
  else
@@ -184,7 +194,7 @@ module Squeel
184
194
  # And node as its expression. All children will be visited before
185
195
  # being passed to the And.
186
196
  def visit_Squeel_Nodes_And(o, parent)
187
- Arel::Nodes::Grouping.new(Arel::Nodes::And.new(accept(o.children, parent)))
197
+ Arel::Nodes::Grouping.new(Arel::Nodes::And.new(visit(o.children, parent)))
188
198
  end
189
199
 
190
200
  # Visit a Squeel Or node, returning an ARel Or node.
@@ -193,11 +203,11 @@ module Squeel
193
203
  # @param parent The parent object in the context
194
204
  # @return [Arel::Nodes::Or] An ARel Or node, with left and right sides visited
195
205
  def visit_Squeel_Nodes_Or(o, parent)
196
- accept(o.left, parent).or(accept(o.right, parent))
206
+ visit(o.left, parent).or(visit(o.right, parent))
197
207
  end
198
208
 
199
209
  def visit_Squeel_Nodes_Not(o, parent)
200
- accept(o.expr, parent).not
210
+ visit(o.expr, parent).not
201
211
  end
202
212
 
203
213
  # @return [Boolean] Whether the given value implies a context change
@@ -207,7 +217,7 @@ module Squeel
207
217
  when Hash, Nodes::Predicate, Nodes::Unary, Nodes::Binary, Nodes::Nary
208
218
  true
209
219
  when Nodes::KeyPath
210
- can_accept?(v.endpoint) && !(Nodes::Stub === v.endpoint)
220
+ can_visit?(v.endpoint) && !(Nodes::Stub === v.endpoint)
211
221
  else
212
222
  false
213
223
  end
@@ -230,9 +240,9 @@ module Squeel
230
240
 
231
241
  case v
232
242
  when Hash, Nodes::KeyPath, Nodes::Predicate, Nodes::Unary, Nodes::Binary, Nodes::Nary
233
- accept(v, parent || k)
243
+ visit(v, parent || k)
234
244
  when Array
235
- v.map {|val| accept(val, parent || k)}
245
+ v.map {|val| visit(val, parent || k)}
236
246
  else
237
247
  raise ArgumentError, <<-END
238
248
  Hashes, Predicates, and arrays of visitables as values imply that their
@@ -254,17 +264,17 @@ module Squeel
254
264
  case v
255
265
  when Nodes::Stub, Symbol
256
266
  v = contextualize(parent)[v.to_sym]
257
- when Nodes::KeyPath # If we could accept the endpoint, we wouldn't be here
267
+ when Nodes::KeyPath # If we could visit the endpoint, we wouldn't be here
258
268
  v = contextualize(traverse(v, parent))[v.endpoint.to_sym]
259
269
  end
260
270
 
261
271
  case k
262
272
  when Nodes::Predicate
263
- accept(k % quote_for_node(k.expr, v), parent)
264
- when Nodes::Function
265
- arel_predicate_for(accept(k, parent), quote(v), parent)
273
+ visit(k % quote_for_node(k.expr, v), parent)
274
+ when Nodes::Function, Nodes::Literal
275
+ arel_predicate_for(visit(k, parent), quote(v), parent)
266
276
  when Nodes::KeyPath
267
- accept(k % quote_for_node(k.endpoint, v), parent)
277
+ visit(k % quote_for_node(k.endpoint, v), parent)
268
278
  else
269
279
  attr_name = k.to_s
270
280
  attribute = if attr_name.include?('.')
@@ -285,19 +295,40 @@ module Squeel
285
295
  # @param value The value to be compared against
286
296
  # @return [Arel::Nodes::Node] An ARel predicate node
287
297
  def arel_predicate_for(attribute, value, parent)
288
- value = can_accept?(value) ? accept(value, parent) : value
289
- if [Array, Range, Arel::SelectManager].include?(value.class)
290
- if Array === value && value.empty?
291
- FALSE_SQL
292
- else
293
- attribute.in(value)
294
- end
298
+ value = can_visit?(value) ? visit(value, parent) : value
299
+ case value
300
+ when Array
301
+ attribute_in_array(attribute, value)
302
+ when Range, Arel::SelectManager
303
+ attribute.in(value)
295
304
  else
296
305
  attribute.eq(value)
297
306
  end
298
307
  end
299
308
 
300
- # Function nodes require us to do the quoting before the ARel
309
+ def attribute_in_array(attribute, array)
310
+ if array.empty?
311
+ FALSE_SQL
312
+ elsif array.include? nil
313
+ array = array.compact
314
+ array.empty? ? attribute.eq(nil) : attribute.in(array).or(attribute.eq nil)
315
+ else
316
+ attribute.in array
317
+ end
318
+ end
319
+
320
+ def attribute_not_in_array(attribute, array)
321
+ if array.empty?
322
+ TRUE_SQL
323
+ elsif array.include? nil
324
+ array = array.compact
325
+ array.empty? ? attribute.not_eq(nil) : attribute.not_in(array).and(attribute.not_eq nil)
326
+ else
327
+ attribute.not_in array
328
+ end
329
+ end
330
+
331
+ # Certain nodes require us to do the quoting before the ARel
301
332
  # visitor gets a chance to try, because we want to avoid having our
302
333
  # values quoted as a type of the last visited column. Otherwise, we
303
334
  # can end up with annoyances like having "joe" quoted to 0, if the
@@ -307,10 +338,10 @@ module Squeel
307
338
  # @param v The value to (possibly) quote
308
339
  def quote_for_node(node, v)
309
340
  case node
310
- when Nodes::Function
341
+ when Nodes::Function, Nodes::Literal
311
342
  quote(v)
312
343
  when Nodes::Predicate
313
- Nodes::Function === node.expr ? quote(v) : v
344
+ quote_for_node(node.expr, v)
314
345
  else
315
346
  v
316
347
  end
@@ -14,13 +14,13 @@ module Squeel
14
14
  private
15
15
 
16
16
  def visit_Array(o, parent)
17
- o.map {|e| accept(e, parent)}.flatten
17
+ o.map {|e| visit(e, parent)}.flatten
18
18
  end
19
19
 
20
20
  def visit_Hash(o, parent)
21
21
  {}.tap do |hash|
22
22
  o.each do |key, value|
23
- hash[accept(key, parent)] = accept(value, parent)
23
+ hash[visit(key, parent)] = visit(value, parent)
24
24
  end
25
25
  end
26
26
  end
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  module Squeel
2
4
  module Adapters
3
5
  module ActiveRecord
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  module Squeel
2
4
  module Adapters
3
5
  module ActiveRecord
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  module Squeel
2
4
  module Adapters
3
5
  module ActiveRecord
@@ -335,9 +337,9 @@ module Squeel
335
337
  end
336
338
 
337
339
  it 'falls back to Array#select behavior with a block that has an arity' do
338
- people = Person.select{|p| p.name =~ /John/}
340
+ people = Person.select{|p| p.id == 1}
339
341
  people.should have(1).person
340
- people.first.name.should eq 'Miss Cameron Johnson'
342
+ people.first.id.should eq 1
341
343
  end
342
344
 
343
345
  it 'behaves as normal with standard parameters' do
@@ -363,7 +365,7 @@ module Squeel
363
365
 
364
366
  it 'allows custom operators in the select values via block' do
365
367
  relation = Person.select{name.op('||', '-diddly').as(flanderized_name)}
366
- relation.first.flanderized_name.should eq 'Aric Smith-diddly'
368
+ relation.first.flanderized_name.should eq Person.first.name + '-diddly'
367
369
  end
368
370
 
369
371
  it 'allows a subquery in the select values' do
@@ -429,8 +431,9 @@ module Squeel
429
431
  end
430
432
 
431
433
  it 'allows a subquery on the value side of a predicate' do
432
- old_and_busted = Person.where(:name => ['Aric Smith', 'Gladyce Kulas'])
433
- new_hotness = Person.where{name.in(Person.select{name}.where{name.in(['Aric Smith', 'Gladyce Kulas'])})}
434
+ names = [Person.first.name, Person.last.name]
435
+ old_and_busted = Person.where(:name => names)
436
+ new_hotness = Person.where{name.in(Person.select{name}.where{name.in(names)})}
434
437
  new_hotness.should have(2).items
435
438
  old_and_busted.to_a.should eq new_hotness.to_a
436
439
  end
@@ -489,7 +492,7 @@ module Squeel
489
492
 
490
493
  it 'allows complex conditions on aggregate columns' do
491
494
  relation = Person.group(:parent_id).having{salary == max(salary)}
492
- relation.first.name.should eq 'Gladyce Kulas'
495
+ relation.first.name.should eq Person.last.name
493
496
  end
494
497
 
495
498
  it 'allows a condition on a function via block' do
@@ -613,7 +616,7 @@ module Squeel
613
616
  end
614
617
 
615
618
  it 'merges relations with a different base' do
616
- relation = Person.where{name == 'bob'}.joins(:articles).merge(Article.where{title == 'Hello world!'})
619
+ relation = Person.where{name == 'bob'}.joins(:articles).merge(Article.where{title == 'Hello world!'}, :articles)
617
620
  sql = relation.to_sql
618
621
  sql.should match /INNER JOIN "articles" ON "articles"."person_id" = "people"."id"/
619
622
  sql.should match /"people"."name" = 'bob'/
@@ -635,7 +638,7 @@ module Squeel
635
638
  where{{comments => {body => 'First post!'}}}
636
639
  relation.size.should be 1
637
640
  person = relation.first
638
- person.name.should eq 'Gladyce Kulas'
641
+ person.should eq Person.last
639
642
  person.comments.loaded?.should be true
640
643
  end
641
644
 
@@ -88,5 +88,13 @@ module Squeel
88
88
  end
89
89
  end
90
90
 
91
+ describe '`string`' do
92
+ it 'creates a Literal' do
93
+ result = dsl{`blah`}
94
+ result.should be_a Nodes::Literal
95
+ result.should eq 'blah'
96
+ end
97
+ end
98
+
91
99
  end
92
100
  end
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  module Squeel
2
4
  module Nodes
3
5
  describe Function do
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  module Squeel
2
4
  module Nodes
3
5
  describe KeyPath do
@@ -0,0 +1,155 @@
1
+ require 'spec_helper'
2
+
3
+ module Squeel
4
+ module Nodes
5
+ describe Literal do
6
+ before do
7
+ @l = Literal.new 'string'
8
+ end
9
+
10
+ it 'hashes like its expr' do
11
+ @l.hash.should eq 'string'.hash
12
+ end
13
+
14
+ it 'returns nil when sent to_sym' do
15
+ @l.to_sym.should be_nil
16
+ end
17
+
18
+ it 'returns a string matching its expr when sent to_s' do
19
+ @l.to_s.should eq 'string'
20
+ end
21
+
22
+ Squeel::Constants::PREDICATES.each do |method_name|
23
+ it "creates #{method_name} predicates with no value" do
24
+ predicate = @l.send(method_name)
25
+ predicate.expr.should eq @l
26
+ predicate.method_name.should eq method_name
27
+ predicate.value?.should be_false
28
+ end
29
+
30
+ it "creates #{method_name} predicates with a value" do
31
+ predicate = @l.send(method_name, 'value')
32
+ predicate.expr.should eq @l
33
+ predicate.method_name.should eq method_name
34
+ predicate.value.should eq 'value'
35
+ end
36
+ end
37
+
38
+ Squeel::Constants::PREDICATE_ALIASES.each do |method_name, aliases|
39
+ aliases.each do |aliaz|
40
+ ['', '_any', '_all'].each do |suffix|
41
+ it "creates #{method_name.to_s + suffix} predicates with no value using the alias #{aliaz.to_s + suffix}" do
42
+ predicate = @l.send(aliaz.to_s + suffix)
43
+ predicate.expr.should eq @l
44
+ predicate.method_name.should eq "#{method_name}#{suffix}".to_sym
45
+ predicate.value?.should be_false
46
+ end
47
+
48
+ it "creates #{method_name.to_s + suffix} predicates with a value using the alias #{aliaz.to_s + suffix}" do
49
+ predicate = @l.send((aliaz.to_s + suffix), 'value')
50
+ predicate.expr.should eq @l
51
+ predicate.method_name.should eq "#{method_name}#{suffix}".to_sym
52
+ predicate.value.should eq 'value'
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ it 'creates eq predicates with ==' do
59
+ predicate = @l == 1
60
+ predicate.expr.should eq @l
61
+ predicate.method_name.should eq :eq
62
+ predicate.value.should eq 1
63
+ end
64
+
65
+ it 'creates not_eq predicates with ^' do
66
+ predicate = @l ^ 1
67
+ predicate.expr.should eq @l
68
+ predicate.method_name.should eq :not_eq
69
+ predicate.value.should eq 1
70
+ end
71
+
72
+ it 'creates not_eq predicates with !=' do
73
+ predicate = @l != 1
74
+ predicate.expr.should eq @l
75
+ predicate.method_name.should eq :not_eq
76
+ predicate.value.should eq 1
77
+ end if respond_to?('!=')
78
+
79
+ it 'creates in predicates with >>' do
80
+ predicate = @l >> [1,2,3]
81
+ predicate.expr.should eq @l
82
+ predicate.method_name.should eq :in
83
+ predicate.value.should eq [1,2,3]
84
+ end
85
+
86
+ it 'creates not_in predicates with <<' do
87
+ predicate = @l << [1,2,3]
88
+ predicate.expr.should eq @l
89
+ predicate.method_name.should eq :not_in
90
+ predicate.value.should eq [1,2,3]
91
+ end
92
+
93
+ it 'creates matches predicates with =~' do
94
+ predicate = @l =~ '%bob%'
95
+ predicate.expr.should eq @l
96
+ predicate.method_name.should eq :matches
97
+ predicate.value.should eq '%bob%'
98
+ end
99
+
100
+ it 'creates does_not_match predicates with !~' do
101
+ predicate = @l !~ '%bob%'
102
+ predicate.expr.should eq @l
103
+ predicate.method_name.should eq :does_not_match
104
+ predicate.value.should eq '%bob%'
105
+ end if respond_to?('!~')
106
+
107
+ it 'creates gt predicates with >' do
108
+ predicate = @l > 1
109
+ predicate.expr.should eq @l
110
+ predicate.method_name.should eq :gt
111
+ predicate.value.should eq 1
112
+ end
113
+
114
+ it 'creates gteq predicates with >=' do
115
+ predicate = @l >= 1
116
+ predicate.expr.should eq @l
117
+ predicate.method_name.should eq :gteq
118
+ predicate.value.should eq 1
119
+ end
120
+
121
+ it 'creates lt predicates with <' do
122
+ predicate = @l < 1
123
+ predicate.expr.should eq @l
124
+ predicate.method_name.should eq :lt
125
+ predicate.value.should eq 1
126
+ end
127
+
128
+ it 'creates lteq predicates with <=' do
129
+ predicate = @l <= 1
130
+ predicate.expr.should eq @l
131
+ predicate.method_name.should eq :lteq
132
+ predicate.value.should eq 1
133
+ end
134
+
135
+ it 'creates ascending orders' do
136
+ order = @l.asc
137
+ order.should be_ascending
138
+ end
139
+
140
+ it 'creates descending orders' do
141
+ order = @l.desc
142
+ order.should be_descending
143
+ end
144
+
145
+ it 'creates as nodes with #as' do
146
+ as = @l.as('other_name')
147
+ as.should be_a Squeel::Nodes::As
148
+ as.left.should eq @l
149
+ as.right.should be_a Arel::Nodes::SqlLiteral
150
+ as.right.should eq 'other_name'
151
+ end
152
+
153
+ end
154
+ end
155
+ end
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  module Squeel
2
4
  module Nodes
3
5
  describe Operation do
@@ -1,3 +1,5 @@
1
+ require 'spec_helper'
2
+
1
3
  module Squeel
2
4
  module Visitors
3
5
  describe AttributeVisitor do
@@ -55,6 +55,36 @@ module Squeel
55
55
  predicate.should eq '1=1'
56
56
  end
57
57
 
58
+ it 'generates IS NULL for hash keys with a value of [nil]' do
59
+ predicate = @v.accept(:id => [nil])
60
+ predicate.to_sql.should be_like '"people"."id" IS NULL'
61
+ end
62
+
63
+ it 'generates IS NULL for in predicates with a value of [nil]' do
64
+ predicate = @v.accept(:id.in => [nil])
65
+ predicate.to_sql.should be_like '"people"."id" IS NULL'
66
+ end
67
+
68
+ it 'generates IS NOT NULL for not_in predicates with a value of [nil]' do
69
+ predicate = @v.accept(:id.not_in => [nil])
70
+ predicate.to_sql.should be_like '"people"."id" IS NOT NULL'
71
+ end
72
+
73
+ it 'generates IN OR IS NULL for hash keys with a value of [1, 2, 3, nil]' do
74
+ predicate = @v.accept(:id => [1, 2, 3, nil])
75
+ predicate.to_sql.should be_like '("people"."id" IN (1, 2, 3) OR "people"."id" IS NULL)'
76
+ end
77
+
78
+ it 'generates IN OR IS NULL for in predicates with a value of [1, 2, 3, nil]' do
79
+ predicate = @v.accept(:id.in => [1, 2, 3, nil])
80
+ predicate.to_sql.should be_like '("people"."id" IN (1, 2, 3) OR "people"."id" IS NULL)'
81
+ end
82
+
83
+ it 'generates IN AND IS NOT NULL for not_in predicates with a value of [1, 2, 3, nil]' do
84
+ predicate = @v.accept(:id.not_in => [1, 2, 3, nil])
85
+ predicate.to_sql.should be_like '"people"."id" NOT IN (1, 2, 3) AND "people"."id" IS NOT NULL'
86
+ end
87
+
58
88
  it 'allows a subquery on the value side of an explicit predicate' do
59
89
  predicate = @v.accept dsl{name.in(Person.select{name}.where{name.in(['Aric Smith', 'Gladyce Kulas'])})}
60
90
  predicate.should be_a Arel::Nodes::In
data/squeel.gemspec CHANGED
@@ -29,7 +29,7 @@ you're feeling especially appreciative. It'd help me justify this
29
29
 
30
30
  s.add_dependency 'activerecord', '~> 3.0'
31
31
  s.add_dependency 'activesupport', '~> 3.0'
32
- s.add_development_dependency 'rspec', '~> 2.5.0'
32
+ s.add_development_dependency 'rspec', '~> 2.6.0'
33
33
  s.add_development_dependency 'machinist', '~> 1.0.6'
34
34
  s.add_development_dependency 'faker', '~> 0.9.5'
35
35
  s.add_development_dependency 'sqlite3', '~> 1.3.3'
metadata CHANGED
@@ -1,93 +1,129 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: squeel
3
- version: !ruby/object:Gem::Version
4
- version: 0.8.4
3
+ version: !ruby/object:Gem::Version
4
+ hash: 53
5
5
  prerelease:
6
+ segments:
7
+ - 0
8
+ - 8
9
+ - 5
10
+ version: 0.8.5
6
11
  platform: ruby
7
- authors:
12
+ authors:
8
13
  - Ernie Miller
9
14
  autorequire:
10
15
  bindir: bin
11
16
  cert_chain: []
12
- date: 2011-06-20 00:00:00.000000000Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
17
+
18
+ date: 2011-07-02 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
15
22
  name: activerecord
16
- requirement: &2156148420 !ruby/object:Gem::Requirement
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
17
25
  none: false
18
- requirements:
26
+ requirements:
19
27
  - - ~>
20
- - !ruby/object:Gem::Version
21
- version: '3.0'
28
+ - !ruby/object:Gem::Version
29
+ hash: 7
30
+ segments:
31
+ - 3
32
+ - 0
33
+ version: "3.0"
22
34
  type: :runtime
23
- prerelease: false
24
- version_requirements: *2156148420
25
- - !ruby/object:Gem::Dependency
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
26
37
  name: activesupport
27
- requirement: &2156147780 !ruby/object:Gem::Requirement
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
28
40
  none: false
29
- requirements:
41
+ requirements:
30
42
  - - ~>
31
- - !ruby/object:Gem::Version
32
- version: '3.0'
43
+ - !ruby/object:Gem::Version
44
+ hash: 7
45
+ segments:
46
+ - 3
47
+ - 0
48
+ version: "3.0"
33
49
  type: :runtime
34
- prerelease: false
35
- version_requirements: *2156147780
36
- - !ruby/object:Gem::Dependency
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
37
52
  name: rspec
38
- requirement: &2156147200 !ruby/object:Gem::Requirement
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
39
55
  none: false
40
- requirements:
56
+ requirements:
41
57
  - - ~>
42
- - !ruby/object:Gem::Version
43
- version: 2.5.0
58
+ - !ruby/object:Gem::Version
59
+ hash: 23
60
+ segments:
61
+ - 2
62
+ - 6
63
+ - 0
64
+ version: 2.6.0
44
65
  type: :development
45
- prerelease: false
46
- version_requirements: *2156147200
47
- - !ruby/object:Gem::Dependency
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
48
68
  name: machinist
49
- requirement: &2156146440 !ruby/object:Gem::Requirement
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
50
71
  none: false
51
- requirements:
72
+ requirements:
52
73
  - - ~>
53
- - !ruby/object:Gem::Version
74
+ - !ruby/object:Gem::Version
75
+ hash: 27
76
+ segments:
77
+ - 1
78
+ - 0
79
+ - 6
54
80
  version: 1.0.6
55
81
  type: :development
56
- prerelease: false
57
- version_requirements: *2156146440
58
- - !ruby/object:Gem::Dependency
82
+ version_requirements: *id004
83
+ - !ruby/object:Gem::Dependency
59
84
  name: faker
60
- requirement: &2156145780 !ruby/object:Gem::Requirement
85
+ prerelease: false
86
+ requirement: &id005 !ruby/object:Gem::Requirement
61
87
  none: false
62
- requirements:
88
+ requirements:
63
89
  - - ~>
64
- - !ruby/object:Gem::Version
90
+ - !ruby/object:Gem::Version
91
+ hash: 49
92
+ segments:
93
+ - 0
94
+ - 9
95
+ - 5
65
96
  version: 0.9.5
66
97
  type: :development
67
- prerelease: false
68
- version_requirements: *2156145780
69
- - !ruby/object:Gem::Dependency
98
+ version_requirements: *id005
99
+ - !ruby/object:Gem::Dependency
70
100
  name: sqlite3
71
- requirement: &2156145020 !ruby/object:Gem::Requirement
101
+ prerelease: false
102
+ requirement: &id006 !ruby/object:Gem::Requirement
72
103
  none: false
73
- requirements:
104
+ requirements:
74
105
  - - ~>
75
- - !ruby/object:Gem::Version
106
+ - !ruby/object:Gem::Version
107
+ hash: 29
108
+ segments:
109
+ - 1
110
+ - 3
111
+ - 3
76
112
  version: 1.3.3
77
113
  type: :development
78
- prerelease: false
79
- version_requirements: *2156145020
80
- description: ! "\n Squeel unlocks the power of ARel in your Rails 3 application
81
- with\n a handy block-based syntax. You can write subqueries, access named\n
82
- \ functions provided by your RDBMS, and more, all without writing\n SQL
83
- strings.\n "
84
- email:
114
+ version_requirements: *id006
115
+ description: "\n Squeel unlocks the power of ARel in your Rails 3 application with\n a handy block-based syntax. You can write subqueries, access named\n functions provided by your RDBMS, and more, all without writing\n SQL strings.\n "
116
+ email:
85
117
  - ernie@metautonomo.us
86
118
  executables: []
119
+
87
120
  extensions: []
121
+
88
122
  extra_rdoc_files: []
89
- files:
123
+
124
+ files:
90
125
  - .gitignore
126
+ - .travis.yml
91
127
  - .yardopts
92
128
  - Gemfile
93
129
  - LICENSE
@@ -120,6 +156,7 @@ files:
120
156
  - lib/squeel/nodes/function.rb
121
157
  - lib/squeel/nodes/join.rb
122
158
  - lib/squeel/nodes/key_path.rb
159
+ - lib/squeel/nodes/literal.rb
123
160
  - lib/squeel/nodes/nary.rb
124
161
  - lib/squeel/nodes/not.rb
125
162
  - lib/squeel/nodes/operation.rb
@@ -155,6 +192,7 @@ files:
155
192
  - spec/squeel/nodes/function_spec.rb
156
193
  - spec/squeel/nodes/join_spec.rb
157
194
  - spec/squeel/nodes/key_path_spec.rb
195
+ - spec/squeel/nodes/literal_spec.rb
158
196
  - spec/squeel/nodes/operation_spec.rb
159
197
  - spec/squeel/nodes/operators_spec.rb
160
198
  - spec/squeel/nodes/order_spec.rb
@@ -166,44 +204,48 @@ files:
166
204
  - spec/squeel/visitors/symbol_visitor_spec.rb
167
205
  - spec/support/schema.rb
168
206
  - squeel.gemspec
207
+ has_rdoc: true
169
208
  homepage: http://metautonomo.us/projects/squeel
170
209
  licenses: []
171
- post_install_message: ! '
172
210
 
211
+ post_install_message: |+
212
+
173
213
  *** Thanks for installing Squeel! ***
174
-
175
214
  Be sure to check out http://metautonomo.us/projects/squeel/ for a
176
-
177
- walkthrough of Squeel''s features, and click the donate link if
178
-
179
- you''re feeling especially appreciative. It''d help me justify this
180
-
215
+ walkthrough of Squeel's features, and click the donate link if
216
+ you're feeling especially appreciative. It'd help me justify this
181
217
  "open source" stuff to my lovely wife. :)
182
-
183
-
184
- '
218
+
185
219
  rdoc_options: []
186
- require_paths:
220
+
221
+ require_paths:
187
222
  - lib
188
- required_ruby_version: !ruby/object:Gem::Requirement
223
+ required_ruby_version: !ruby/object:Gem::Requirement
189
224
  none: false
190
- requirements:
191
- - - ! '>='
192
- - !ruby/object:Gem::Version
193
- version: '0'
194
- required_rubygems_version: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - ">="
227
+ - !ruby/object:Gem::Version
228
+ hash: 3
229
+ segments:
230
+ - 0
231
+ version: "0"
232
+ required_rubygems_version: !ruby/object:Gem::Requirement
195
233
  none: false
196
- requirements:
197
- - - ! '>='
198
- - !ruby/object:Gem::Version
199
- version: '0'
234
+ requirements:
235
+ - - ">="
236
+ - !ruby/object:Gem::Version
237
+ hash: 3
238
+ segments:
239
+ - 0
240
+ version: "0"
200
241
  requirements: []
242
+
201
243
  rubyforge_project: squeel
202
- rubygems_version: 1.8.5
244
+ rubygems_version: 1.5.2
203
245
  signing_key:
204
246
  specification_version: 3
205
247
  summary: ActiveRecord 3, improved.
206
- test_files:
248
+ test_files:
207
249
  - spec/blueprints/articles.rb
208
250
  - spec/blueprints/comments.rb
209
251
  - spec/blueprints/notes.rb
@@ -222,6 +264,7 @@ test_files:
222
264
  - spec/squeel/nodes/function_spec.rb
223
265
  - spec/squeel/nodes/join_spec.rb
224
266
  - spec/squeel/nodes/key_path_spec.rb
267
+ - spec/squeel/nodes/literal_spec.rb
225
268
  - spec/squeel/nodes/operation_spec.rb
226
269
  - spec/squeel/nodes/operators_spec.rb
227
270
  - spec/squeel/nodes/order_spec.rb