squeel 0.8.4 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
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