squeel 0.5.5 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/.yardopts +3 -0
  2. data/Gemfile +8 -3
  3. data/README.md +368 -0
  4. data/lib/core_ext/hash.rb +8 -8
  5. data/lib/core_ext/symbol.rb +7 -6
  6. data/lib/squeel.rb +2 -0
  7. data/lib/squeel/adapters/active_record.rb +25 -20
  8. data/lib/squeel/adapters/active_record/3.0/compat.rb +1 -2
  9. data/lib/squeel/adapters/active_record/3.0/context.rb +6 -7
  10. data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +5 -5
  11. data/lib/squeel/adapters/active_record/context.rb +6 -7
  12. data/lib/squeel/adapters/active_record/join_dependency.rb +5 -5
  13. data/lib/squeel/configuration.rb +29 -0
  14. data/lib/squeel/constants.rb +1 -0
  15. data/lib/squeel/context.rb +36 -7
  16. data/lib/squeel/dsl.rb +57 -2
  17. data/lib/squeel/nodes.rb +6 -0
  18. data/lib/squeel/nodes/and.rb +1 -0
  19. data/lib/squeel/nodes/binary.rb +11 -2
  20. data/lib/squeel/nodes/function.rb +30 -48
  21. data/lib/squeel/nodes/join.rb +56 -12
  22. data/lib/squeel/nodes/key_path.rb +68 -2
  23. data/lib/squeel/nodes/nary.rb +12 -2
  24. data/lib/squeel/nodes/not.rb +1 -0
  25. data/lib/squeel/nodes/operation.rb +9 -0
  26. data/lib/squeel/nodes/operators.rb +16 -0
  27. data/lib/squeel/nodes/or.rb +1 -0
  28. data/lib/squeel/nodes/order.rb +19 -1
  29. data/lib/squeel/nodes/predicate.rb +25 -3
  30. data/lib/squeel/nodes/predicate_operators.rb +12 -0
  31. data/lib/squeel/nodes/stub.rb +55 -48
  32. data/lib/squeel/nodes/unary.rb +7 -1
  33. data/lib/squeel/predicate_methods.rb +2 -10
  34. data/lib/squeel/version.rb +1 -1
  35. data/lib/squeel/visitors/attribute_visitor.rb +80 -4
  36. data/lib/squeel/visitors/base.rb +70 -4
  37. data/lib/squeel/visitors/predicate_visitor.rb +28 -9
  38. data/lib/squeel/visitors/symbol_visitor.rb +1 -1
  39. data/spec/core_ext/symbol_spec.rb +2 -2
  40. data/spec/spec_helper.rb +6 -1
  41. data/spec/squeel/adapters/active_record/context_spec.rb +0 -7
  42. data/spec/squeel/adapters/active_record/relation_spec.rb +27 -0
  43. data/spec/squeel/dsl_spec.rb +20 -1
  44. data/spec/squeel/nodes/join_spec.rb +11 -4
  45. data/spec/squeel/nodes/key_path_spec.rb +1 -1
  46. data/spec/squeel/nodes/predicate_spec.rb +0 -42
  47. data/spec/squeel/nodes/stub_spec.rb +9 -8
  48. data/spec/squeel/visitors/predicate_visitor_spec.rb +34 -9
  49. data/squeel.gemspec +6 -9
  50. metadata +8 -10
  51. data/README.rdoc +0 -117
  52. data/lib/squeel/predicate_methods/function.rb +0 -9
  53. data/lib/squeel/predicate_methods/predicate.rb +0 -11
  54. data/lib/squeel/predicate_methods/stub.rb +0 -9
  55. data/lib/squeel/predicate_methods/symbol.rb +0 -9
@@ -1,40 +1,72 @@
1
1
  module Squeel
2
2
  module Nodes
3
+ # A node representing a joined association
3
4
  class Join
4
- attr_reader :name, :type, :klass
5
+ undef_method :id if method_defined?(:id)
5
6
 
7
+ # @return [Symbol] The join's association name
8
+ attr_reader :_name
9
+
10
+ # @return [Arel::InnerJoin, Arel::OuterJoin] The ARel join type
11
+ attr_reader :_type
12
+
13
+ # @return [Class] The polymorphic belongs_to join class
14
+ # @return [NilClass] If the join is not a polymorphic belongs_to join
15
+ attr_reader :_klass
16
+
17
+ # Create a new Join node
18
+ # @param [Symbol] name The association name
19
+ # @param [Arel::InnerJoin, Arel::OuterJoin] type The ARel join class
20
+ # @param [Class, String, Symbol] klass The polymorphic belongs_to class or class name
6
21
  def initialize(name, type = Arel::InnerJoin, klass = nil)
7
- @name, @type = name, type
8
- @klass = convert_to_class(klass) if klass
22
+ @_name, @_type = name, type
23
+ @_klass = convert_to_class(klass) if klass
9
24
  end
10
25
 
26
+ # Set the join type to an inner join
27
+ # @return [Join] The join, with an updated join type.
11
28
  def inner
12
- @type = Arel::InnerJoin
29
+ @_type = Arel::InnerJoin
13
30
  self
14
31
  end
15
32
 
33
+ # Set the join type to an outer join
34
+ # @return [Join] The join, with an updated join type.
16
35
  def outer
17
- @type = Arel::OuterJoin
36
+ @_type = Arel::OuterJoin
18
37
  self
19
38
  end
20
39
 
21
- def klass=(class_or_class_name)
22
- @klass = convert_to_class(class_or_class_name)
40
+ # Set the polymorphic belongs_to class
41
+ # @param [Class, String, Symbol] class_or_class_name The polymorphic belongs_to class or class name
42
+ # @return [Class] The class that's just been set
43
+ def _klass=(class_or_class_name)
44
+ @_klass = convert_to_class(class_or_class_name)
23
45
  end
24
46
 
47
+ # Returns a true value (the class itself) if a polymorphic belongs_to class has been set
48
+ # @return [NilClass, Class] The class, if present.
25
49
  def polymorphic?
26
- @klass
50
+ @_klass
27
51
  end
28
52
 
53
+ # Compare with other objects
29
54
  def eql?(other)
30
55
  self.class == other.class &&
31
- self.name == other.name &&
32
- self.type == other.type &&
33
- self.klass == other.klass
56
+ self._name == other._name &&
57
+ self._type == other._type &&
58
+ self._klass == other._klass
34
59
  end
35
-
36
60
  alias :== :eql?
37
61
 
62
+ # Ensures that a Join can be used as the base of a new KeyPath
63
+ # @overload node_name
64
+ # Creates a new KeyPath with this Join as the base and the method_name as the endpoint
65
+ # @return [KeyPath] The new keypath
66
+ # @overload node_name(klass)
67
+ # Creates a new KeyPath with this Join as the base and a polymorphic belongs_to join as the endpoint
68
+ # @param [Class] klass The polymorphic class for the join
69
+ # @return [KeyPath] The new keypath
38
70
  def method_missing(method_id, *args)
39
71
  super if method_id == :to_ary
40
72
  if (args.size == 1) && (Class === args[0])
@@ -44,15 +76,27 @@ module Squeel
44
76
  end
45
77
  end
46
78
 
79
+ # Return a KeyPath containing only this Join, but flagged as absolute.
80
+ # This helps Joins behave more like a KeyPath, as anyone using the Squeel
81
+ # DSL is likely to think of them as such.
82
+ # @return [KeyPath] An absolute KeyPath, containing only this Join
83
+ def ~
84
+ KeyPath.new [], self, true
85
+ end
86
+
47
87
  # expand_hash_conditions_for_aggregates assumes our hash keys can be
48
88
  # converted to symbols, so this has to be implemented, but it doesn't
49
89
  # really have to do anything useful.
90
+ # @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
50
91
  def to_sym
51
92
  nil
52
93
  end
53
94
 
54
95
  private
55
96
 
97
+ # Convert the given value into a class.
98
+ # @param [Class, String, Symbol] value The value to be converted
99
+ # @return [Class] The class after conversion
56
100
  def convert_to_class(value)
57
101
  case value
58
102
  when String, Symbol
@@ -3,12 +3,25 @@ require 'squeel/nodes/predicate_operators'
3
3
 
4
4
  module Squeel
5
5
  module Nodes
6
+ # A node that stores a path of keys (of Symbol, Stub, or Join values) and
7
+ # an endpoint. Used similarly to a nested hash.
6
8
  class KeyPath
7
9
  include PredicateOperators
8
10
  include Operators
9
11
 
10
- attr_reader :path, :endpoint
12
+ undef_method :id if method_defined?(:id)
11
13
 
14
+ # @return [Array<Symbol, Stub, Join>] The path
15
+ attr_reader :path
16
+
17
+ # @return The endpoint, either another key as in the path, or a predicate, function, etc.
18
+ attr_reader :endpoint
19
+
20
+ # Create a new KeyPath.
21
+ # @param [Array, Object] path The intial path. Will be converted to an array if it isn't already.
22
+ # @param endpoint the endpoint of the KeyPath
23
+ # @param [Boolean] absolute If the KeyPath should start from the base
24
+ # or remain relative to whatever location it's found.
12
25
  def initialize(path, endpoint, absolute = false)
13
26
  @path, @endpoint = path, endpoint
14
27
  @path = [@path] unless Array === @path
@@ -16,10 +29,14 @@ module Squeel
16
29
  @absolute = absolute
17
30
  end
18
31
 
32
+ # Whether or not the KeyPath should be interpreted relative to its current location
33
+ # (if nested in a Hash, for instance) or as though it's at the base.
34
+ # @return [Boolean] The flag's value
19
35
  def absolute?
20
36
  @absolute
21
37
  end
22
38
 
39
+ # Object comparison
23
40
  def eql?(other)
24
41
  self.class == other.class &&
25
42
  self.path == other.path &&
@@ -27,38 +44,72 @@ module Squeel
27
44
  self.absolute? == other.absolute?
28
45
  end
29
46
 
47
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
48
+ # responds to |
49
+ # @param other The right hand side of the operation
50
+ # @return [Or] An Or node with the KeyPath on the left side and the other object on the right.
30
51
  def |(other)
31
52
  endpoint.respond_to?(:|) ? super : no_method_error(:|)
32
53
  end
33
54
 
55
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
56
+ # responds to &
57
+ # @param other The right hand side of the operation
58
+ # @return [And] An And node with the KeyPath on the left side and the other object on the right.
34
59
  def &(other)
35
60
  endpoint.respond_to?(:&) ? super : no_method_error(:&)
36
61
  end
37
62
 
63
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
64
+ # responds to -@
65
+ # @return [Not] A not node with the KeyPath as its expression
38
66
  def -@
39
67
  endpoint.respond_to?(:-@) ? super : no_method_error(:-@)
40
68
  end
41
69
 
70
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
71
+ # responds to +
72
+ # @param other The right hand side of the operation
73
+ # @return [Operation] An operation (with operator +) with the KeyPath on its left and the other object on the right.
42
74
  def +(other)
43
75
  endpoint.respond_to?(:+) ? super : no_method_error(:+)
44
76
  end
45
77
 
78
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
79
+ # responds to -
80
+ # @param other The right hand side of the operation
81
+ # @return [Operation] An operation (with operator -) with the KeyPath on its left and the other object on the right.
46
82
  def -(other)
47
83
  endpoint.respond_to?(:-) ? super : no_method_error(:-)
48
84
  end
49
85
 
86
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
87
+ # responds to *
88
+ # @param other The right hand side of the operation
89
+ # @return [Operation] An operation (with operator *) with the KeyPath on its left and the other object on the right.
50
90
  def *(other)
51
91
  endpoint.respond_to?(:*) ? super : no_method_error(:*)
52
92
  end
53
93
 
94
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
95
+ # responds to /
96
+ # @param other The right hand side of the operation
97
+ # @return [Operation] An operation (with operator /) with the KeyPath on its left and the other object on the right.
54
98
  def /(other)
55
99
  endpoint.respond_to?(:/) ? super : no_method_error(:/)
56
100
  end
57
101
 
102
+ # Allow KeyPath to function like its endpoint, in the case where its endpoint
103
+ # responds to #op
104
+ # @param [String, Symbol] operator The custom operator
105
+ # @param other The right hand side of the operation
106
+ # @return [Operation] An operation with the given custom operator, the KeyPath on its left and the other object on the right.
58
107
  def op(operator, other)
59
108
  endpoint.respond_to?(:op) ? super : no_method_error(:/)
60
109
  end
61
110
 
111
+ # Set the absolute flag on this KeyPath
112
+ # @return [KeyPath] This keypath, with its absolute flag set to true
62
113
  def ~
63
114
  @absolute = true
64
115
  self
@@ -69,18 +120,26 @@ module Squeel
69
120
  undef_method operator
70
121
  end
71
122
 
123
+ # For use with equality tests
72
124
  def hash
73
125
  [self.class, endpoint, *path].hash
74
126
  end
75
127
 
128
+ # expand_hash_conditions_for_aggregates assumes our hash keys can be
129
+ # converted to symbols, so this has to be implemented, but it doesn't
130
+ # really have to do anything useful.
131
+ # @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
76
132
  def to_sym
77
133
  nil
78
134
  end
79
135
 
136
+ # Delegate % to the KeyPath's endpoint, with a bit of special logic for stubs or functions.
137
+ # @param val The value to be supplied to the created/existing predicate
138
+ # @return [KeyPath] This KeyPath, with a predicate endpoint containing the given value
80
139
  def %(val)
81
140
  case endpoint
82
141
  when Stub, Function
83
- eq(val)
142
+ Array === val ? self.in(val) : self.eq(val)
84
143
  self
85
144
  else
86
145
  endpoint % val
@@ -88,14 +147,19 @@ module Squeel
88
147
  end
89
148
  end
90
149
 
150
+ # @return [Array] The KeyPath's path, including its endpoint, as a single array.
91
151
  def path_with_endpoint
92
152
  path + [endpoint]
93
153
  end
94
154
 
155
+ # Impleneted (and aliased to :to_str) to play nicely with ActiveRecord grouped calculations
95
156
  def to_s
96
157
  path.map(&:to_s).join('.') << ".#{endpoint}"
97
158
  end
159
+ alias :to_str :to_s
98
160
 
161
+ # Appends to the KeyPath or delegates to the endpoint, as appropriate
162
+ # @return [KeyPath] The updated KeyPath
99
163
  def method_missing(method_id, *args)
100
164
  super if method_id == :to_ary
101
165
  if endpoint.respond_to? method_id
@@ -118,6 +182,8 @@ module Squeel
118
182
 
119
183
  private
120
184
 
185
+ # Raises a NoMethodError manually, bypassing #method_missing.
186
+ # Used by special-case operator overrides.
121
187
  def no_method_error(method_id)
122
188
  raise NoMethodError, "undefined method `#{method_id}' for #{self}:#{self.class}"
123
189
  end
@@ -1,33 +1,43 @@
1
1
  module Squeel
2
2
  module Nodes
3
+ # A node containing multiple children. Just the And node for now.
3
4
  class Nary
4
5
  include PredicateOperators
5
6
 
7
+ # @return [Array] This node's children
6
8
  attr_reader :children
7
9
 
10
+ # Creates a new Nary node with the given array of children
11
+ # @param [Array] children The node's children
8
12
  def initialize(children)
9
13
  raise ArgumentError, '#{self.class} requires an array' unless Array === children
10
14
  # We don't dup here, as incoming arrays should be created by the
11
- # Operators#& method on other nodes. If you're creating And nodes
15
+ # PredicateOperators#& method on other nodes. If you're creating And nodes
12
16
  # manually, by sure that they're new arays.
13
17
  @children = children
14
18
  end
15
19
 
20
+ # Add a new child to the node's children
21
+ # @param other A new child node
22
+ # @return [Nary] This node, with its updated children.
16
23
  def &(other)
17
24
  @children << other
18
25
  self
19
26
  end
20
27
 
28
+ # Append a new Not node to this node's children
29
+ # @param other A new child node
30
+ # @return [Nary] This node, with its new, negated child
21
31
  def -(other)
22
32
  @children << Not.new(other)
23
33
  self
24
34
  end
25
35
 
36
+ # Object comparison
26
37
  def eql?(other)
27
38
  self.class == other.class &&
28
39
  self.children == other.children
29
40
  end
30
-
31
41
  alias :== :eql?
32
42
 
33
43
  end
@@ -2,6 +2,7 @@ require 'squeel/nodes/unary'
2
2
 
3
3
  module Squeel
4
4
  module Nodes
5
+ # A node that represents SQL NOT, and will result in same when visited
5
6
  class Not < Unary
6
7
  end
7
8
  end
@@ -2,18 +2,27 @@ require 'squeel/nodes/function'
2
2
 
3
3
  module Squeel
4
4
  module Nodes
5
+ # An Operation is basically a function with its operands limited, and rearranged,
6
+ # so Squeel implements it as such.
5
7
  class Operation < Function
6
8
 
9
+ # Create a new Operation with the given operator and left and right operands
10
+ # @param left The left operand
11
+ # @param [String, Symbol] operator The operator
12
+ # @param right The right operand
7
13
  def initialize(left, operator, right)
8
14
  super(operator, [left, right])
9
15
  end
10
16
 
17
+ # An operation should probably call its "function" name an "operator", shouldn't it?
11
18
  alias :operator :name
12
19
 
20
+ # @return The left operand
13
21
  def left
14
22
  args[0]
15
23
  end
16
24
 
25
+ # @return The right operand
17
26
  def right
18
27
  args[1]
19
28
  end
@@ -1,23 +1,39 @@
1
1
  module Squeel
2
2
  module Nodes
3
+ # Module containing Operation factory methods for inclusion in Squeel nodes
3
4
  module Operators
4
5
 
6
+ # Factory for a new addition operation with this node as its left operand.
7
+ # @param value The right operand
8
+ # @return [Operation] The new addition operation
5
9
  def +(value)
6
10
  Operation.new(self, :+, value)
7
11
  end
8
12
 
13
+ # Factory for a new subtraction operation with this node as its left operand.
14
+ # @param value The right operand
15
+ # @return [Operation] The new subtraction operation
9
16
  def -(value)
10
17
  Operation.new(self, :-, value)
11
18
  end
12
19
 
20
+ # Factory for a new multiplication operation with this node as its left operand.
21
+ # @param value The right operand
22
+ # @return [Operation] The new multiplication operation
13
23
  def *(value)
14
24
  Operation.new(self, :*, value)
15
25
  end
16
26
 
27
+ # Factory for a new division operation with this node as its left operand.
28
+ # @param value The right operand
29
+ # @return [Operation] The new division operation
17
30
  def /(value)
18
31
  Operation.new(self, :/, value)
19
32
  end
20
33
 
34
+ # Factory for a new custom operation with this node as its left operand.
35
+ # @param value The right operand
36
+ # @return [Operation] The new operation
21
37
  def op(operator, value)
22
38
  Operation.new(self, operator, value)
23
39
  end
@@ -2,6 +2,7 @@ require 'squeel/nodes/binary'
2
2
 
3
3
  module Squeel
4
4
  module Nodes
5
+ # A node representing an SQL OR, which will result in same when visited.
5
6
  class Or < Binary
6
7
  end
7
8
  end
@@ -1,31 +1,49 @@
1
1
  module Squeel
2
2
  module Nodes
3
+ # A node that represents SQL orderings, such as "people.id DESC"
3
4
  class Order
4
- attr_reader :expr, :direction
5
+ # @return The expression being ordered on. Might be an attribute, function, or operation
6
+ attr_reader :expr
5
7
 
8
+ # @return [Fixnum] 1 or -1, depending on ascending or descending direction, respectively
9
+ attr_reader :direction
10
+
11
+ # Create a new Order node with the given expression and direction
12
+ # @param expr The expression to order on
13
+ # @param [Fixnum] direction 1 or -1, depending on the desired sort direction
6
14
  def initialize(expr, direction = 1)
7
15
  raise ArgumentError, "Direction #{direction} is not valid. Must be -1 or 1." unless [-1,1].include? direction
8
16
  @expr, @direction = expr, direction
9
17
  end
10
18
 
19
+ # Set this node's direction to ascending
20
+ # @return [Order] This order node with an ascending direction
11
21
  def asc
12
22
  @direction = 1
13
23
  self
14
24
  end
15
25
 
26
+ # Set this node's direction to descending
27
+ # @return [Order] This order node with a descending direction
16
28
  def desc
17
29
  @direction = -1
18
30
  self
19
31
  end
20
32
 
33
+ # Whether or not this node represents an ascending order
34
+ # @return [Boolean] True if the order is ascending
21
35
  def ascending?
22
36
  @direction == 1
23
37
  end
24
38
 
39
+ # Whether or not this node represents a descending order
40
+ # @return [Boolean] True if the order is descending
25
41
  def descending?
26
42
  @direction == -1
27
43
  end
28
44
 
45
+ # Reverse the node's direction
46
+ # @return [Order] This node with a reversed direction
29
47
  def reverse!
30
48
  @direction = - @direction
31
49
  self