squeel 0.5.5 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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