squeel_rbg 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.yardopts +3 -0
- data/Gemfile +13 -0
- data/LICENSE +20 -0
- data/README.md +398 -0
- data/Rakefile +19 -0
- data/lib/core_ext/hash.rb +13 -0
- data/lib/core_ext/symbol.rb +39 -0
- data/lib/squeel/adapters/active_record/3.0/association_preload.rb +15 -0
- data/lib/squeel/adapters/active_record/3.0/compat.rb +142 -0
- data/lib/squeel/adapters/active_record/3.0/context.rb +66 -0
- data/lib/squeel/adapters/active_record/3.0/join_association.rb +54 -0
- data/lib/squeel/adapters/active_record/3.0/join_dependency.rb +84 -0
- data/lib/squeel/adapters/active_record/3.0/relation.rb +327 -0
- data/lib/squeel/adapters/active_record/context.rb +66 -0
- data/lib/squeel/adapters/active_record/join_association.rb +44 -0
- data/lib/squeel/adapters/active_record/join_dependency.rb +83 -0
- data/lib/squeel/adapters/active_record/preloader.rb +21 -0
- data/lib/squeel/adapters/active_record/relation.rb +351 -0
- data/lib/squeel/adapters/active_record.rb +28 -0
- data/lib/squeel/configuration.rb +54 -0
- data/lib/squeel/constants.rb +24 -0
- data/lib/squeel/context.rb +67 -0
- data/lib/squeel/dsl.rb +86 -0
- data/lib/squeel/nodes/aliasing.rb +13 -0
- data/lib/squeel/nodes/and.rb +9 -0
- data/lib/squeel/nodes/as.rb +14 -0
- data/lib/squeel/nodes/binary.rb +32 -0
- data/lib/squeel/nodes/function.rb +66 -0
- data/lib/squeel/nodes/join.rb +113 -0
- data/lib/squeel/nodes/key_path.rb +192 -0
- data/lib/squeel/nodes/nary.rb +45 -0
- data/lib/squeel/nodes/not.rb +9 -0
- data/lib/squeel/nodes/operation.rb +32 -0
- data/lib/squeel/nodes/operators.rb +43 -0
- data/lib/squeel/nodes/or.rb +9 -0
- data/lib/squeel/nodes/order.rb +53 -0
- data/lib/squeel/nodes/predicate.rb +71 -0
- data/lib/squeel/nodes/predicate_operators.rb +29 -0
- data/lib/squeel/nodes/stub.rb +125 -0
- data/lib/squeel/nodes/unary.rb +28 -0
- data/lib/squeel/nodes.rb +17 -0
- data/lib/squeel/predicate_methods.rb +14 -0
- data/lib/squeel/version.rb +3 -0
- data/lib/squeel/visitors/attribute_visitor.rb +191 -0
- data/lib/squeel/visitors/base.rb +112 -0
- data/lib/squeel/visitors/predicate_visitor.rb +319 -0
- data/lib/squeel/visitors/symbol_visitor.rb +48 -0
- data/lib/squeel/visitors.rb +3 -0
- data/lib/squeel.rb +28 -0
- data/lib/squeel_rbg.rb +5 -0
- data/spec/blueprints/articles.rb +5 -0
- data/spec/blueprints/comments.rb +5 -0
- data/spec/blueprints/notes.rb +3 -0
- data/spec/blueprints/people.rb +4 -0
- data/spec/blueprints/tags.rb +3 -0
- data/spec/console.rb +22 -0
- data/spec/core_ext/symbol_spec.rb +75 -0
- data/spec/helpers/squeel_helper.rb +21 -0
- data/spec/spec_helper.rb +66 -0
- data/spec/squeel/adapters/active_record/context_spec.rb +44 -0
- data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
- data/spec/squeel/adapters/active_record/join_dependency_spec.rb +66 -0
- data/spec/squeel/adapters/active_record/relation_spec.rb +627 -0
- data/spec/squeel/dsl_spec.rb +92 -0
- data/spec/squeel/nodes/function_spec.rb +149 -0
- data/spec/squeel/nodes/join_spec.rb +47 -0
- data/spec/squeel/nodes/key_path_spec.rb +100 -0
- data/spec/squeel/nodes/operation_spec.rb +149 -0
- data/spec/squeel/nodes/operators_spec.rb +87 -0
- data/spec/squeel/nodes/order_spec.rb +30 -0
- data/spec/squeel/nodes/predicate_operators_spec.rb +88 -0
- data/spec/squeel/nodes/predicate_spec.rb +50 -0
- data/spec/squeel/nodes/stub_spec.rb +198 -0
- data/spec/squeel/visitors/attribute_visitor_spec.rb +142 -0
- data/spec/squeel/visitors/predicate_visitor_spec.rb +342 -0
- data/spec/squeel/visitors/symbol_visitor_spec.rb +42 -0
- data/spec/support/schema.rb +104 -0
- data/squeel.gemspec +43 -0
- metadata +246 -0
data/lib/squeel/dsl.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module Squeel
|
2
|
+
# Interprets DSL blocks, generating various Squeel nodes as appropriate.
|
3
|
+
class DSL
|
4
|
+
|
5
|
+
# We're creating a BlankSlate-type class here, since we want most
|
6
|
+
# method calls to fall through to method_missing.
|
7
|
+
Squeel.evil_things do
|
8
|
+
(instance_methods + private_instance_methods).each do |method|
|
9
|
+
unless method.to_s =~ /^(__|instance_eval)/
|
10
|
+
undef_method method
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Called from an adapter, not directly.
|
16
|
+
# Evaluates a block of Squeel DSL code.
|
17
|
+
#
|
18
|
+
# @example A DSL block that uses instance_eval
|
19
|
+
# Post.where{title == 'Hello world!'}
|
20
|
+
#
|
21
|
+
# @example A DSL block with access to methods from the closure
|
22
|
+
# Post.where{|dsl| dsl.title == local_method(local_var)}
|
23
|
+
#
|
24
|
+
# @yield [dsl] A block of Squeel DSL code, with an optional argument if
|
25
|
+
# access to closure methods is desired.
|
26
|
+
# @return The results of the interpreted DSL code.
|
27
|
+
def self.eval(&block)
|
28
|
+
if block.arity > 0
|
29
|
+
yield self.new(block.binding)
|
30
|
+
else
|
31
|
+
self.new(block.binding).instance_eval(&block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# This isn't normally called directly, but via DSL.eval, which will
|
38
|
+
# pass the block's binding to the new instance, for use with #my.
|
39
|
+
#
|
40
|
+
# @param [Binding] The block's binding.
|
41
|
+
def initialize(caller_binding)
|
42
|
+
@caller = caller_binding.eval 'self'
|
43
|
+
end
|
44
|
+
|
45
|
+
# If you really need to get at an instance variable or method inside
|
46
|
+
# a DSL block, this method will let you do it. It passes a block back
|
47
|
+
# to the DSL's caller for instance_eval.
|
48
|
+
#
|
49
|
+
# It's also pretty evil, so I hope you enjoy using it while I'm burning in
|
50
|
+
# programmer hell.
|
51
|
+
#
|
52
|
+
# @param &block A block to instance_eval against the DSL's caller.
|
53
|
+
# @return
|
54
|
+
def my(&block)
|
55
|
+
@caller.instance_eval &block
|
56
|
+
end
|
57
|
+
|
58
|
+
# Node generation inside DSL blocks.
|
59
|
+
#
|
60
|
+
# @overload node_name
|
61
|
+
# Creates a Stub. Method calls chained from this Stub will determine
|
62
|
+
# what type of node we eventually end up with.
|
63
|
+
# @return [Nodes::Stub] A stub with the name of the method
|
64
|
+
# @overload node_name(klass)
|
65
|
+
# Creates a Join with a polymorphic class matching the given parameter
|
66
|
+
# @param [Class] klass The polymorphic class of the join node
|
67
|
+
# @return [Nodes::Join] A join node with the name of the method and the given class
|
68
|
+
# @overload node_name(first_arg, *other_args)
|
69
|
+
# Creates a Function with the given arguments becoming the function's arguments
|
70
|
+
# @param first_arg The first argument
|
71
|
+
# @param *other_args Optional additional arguments
|
72
|
+
# @return [Nodes::Function] A function node for the given method name with the given arguments
|
73
|
+
def method_missing(method_id, *args)
|
74
|
+
super if method_id == :to_ary
|
75
|
+
|
76
|
+
if args.empty?
|
77
|
+
Nodes::Stub.new method_id
|
78
|
+
elsif (args.size == 1) && (Class === args[0])
|
79
|
+
Nodes::Join.new(method_id, Arel::InnerJoin, args[0])
|
80
|
+
else
|
81
|
+
Nodes::Function.new method_id, args
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'squeel/nodes/binary'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Nodes
|
5
|
+
# A node representing an SQL alias, which will result in same when visited.
|
6
|
+
class As < Binary
|
7
|
+
# @param left The node to be aliased
|
8
|
+
# @param right The alias name
|
9
|
+
def initialize(left, right)
|
10
|
+
@left, @right = left, right
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'squeel/nodes/predicate_operators'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Nodes
|
5
|
+
# A node that represents an operation with two operands.
|
6
|
+
class Binary
|
7
|
+
|
8
|
+
include PredicateOperators
|
9
|
+
|
10
|
+
# The left operand
|
11
|
+
attr_reader :left
|
12
|
+
|
13
|
+
# The right operand
|
14
|
+
attr_reader :right
|
15
|
+
|
16
|
+
# @param left The left operand
|
17
|
+
# @param right The right operand
|
18
|
+
def initialize(left, right)
|
19
|
+
@left, @right = left, right
|
20
|
+
end
|
21
|
+
|
22
|
+
# Comparison with other nodes
|
23
|
+
def eql?(other)
|
24
|
+
self.class == other.class &&
|
25
|
+
self.left == other.left &&
|
26
|
+
self.right == other.right
|
27
|
+
end
|
28
|
+
alias :== :eql?
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'squeel/predicate_methods'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Nodes
|
5
|
+
# A node that represents an SQL function call
|
6
|
+
class Function
|
7
|
+
|
8
|
+
include PredicateMethods
|
9
|
+
include Operators
|
10
|
+
|
11
|
+
alias :== :eq
|
12
|
+
alias :'^' :not_eq
|
13
|
+
alias :'!=' :not_eq if respond_to?(:'!=')
|
14
|
+
alias :>> :in
|
15
|
+
alias :<< :not_in
|
16
|
+
alias :=~ :matches
|
17
|
+
alias :'!~' :does_not_match if respond_to?(:'!~')
|
18
|
+
alias :> :gt
|
19
|
+
alias :>= :gteq
|
20
|
+
alias :< :lt
|
21
|
+
alias :<= :lteq
|
22
|
+
|
23
|
+
# @return [Symbol] The name of the SQL function to be called
|
24
|
+
attr_reader :name
|
25
|
+
|
26
|
+
# @return [Array] The arguments to be passed to the SQL function
|
27
|
+
attr_reader :args
|
28
|
+
|
29
|
+
# @return [String] The SQL function's alias
|
30
|
+
# @return [NilClass] If no alias
|
31
|
+
attr_reader :alias
|
32
|
+
|
33
|
+
# Create a node representing an SQL Function with the given name and arguments
|
34
|
+
# @param [Symbol] name The function name
|
35
|
+
# @param [Array] args The array of arguments to pass to the function.
|
36
|
+
def initialize(name, args)
|
37
|
+
@name, @args = name, args
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set an alias for the function
|
41
|
+
# @param [String, Symbol] The alias name
|
42
|
+
# @return [Function] This function with the new alias value.
|
43
|
+
def as(alias_name)
|
44
|
+
@alias = alias_name.to_s
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def asc
|
49
|
+
Order.new self, 1
|
50
|
+
end
|
51
|
+
|
52
|
+
def desc
|
53
|
+
Order.new self, -1
|
54
|
+
end
|
55
|
+
|
56
|
+
# expand_hash_conditions_for_aggregates assumes our hash keys can be
|
57
|
+
# converted to symbols, so this has to be implemented, but it doesn't
|
58
|
+
# really have to do anything useful.
|
59
|
+
# @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
|
60
|
+
def to_sym
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Squeel
|
2
|
+
module Nodes
|
3
|
+
# A node representing a joined association
|
4
|
+
class Join
|
5
|
+
undef_method :id if method_defined?(:id)
|
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
|
21
|
+
def initialize(name, type = Arel::InnerJoin, klass = nil)
|
22
|
+
@_name, @_type = name, type
|
23
|
+
@_klass = convert_to_class(klass) if klass
|
24
|
+
end
|
25
|
+
|
26
|
+
# Set the join type to an inner join
|
27
|
+
# @return [Join] The join, with an updated join type.
|
28
|
+
def inner
|
29
|
+
@_type = Arel::InnerJoin
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Set the join type to an outer join
|
34
|
+
# @return [Join] The join, with an updated join type.
|
35
|
+
def outer
|
36
|
+
@_type = Arel::OuterJoin
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
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)
|
45
|
+
end
|
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.
|
49
|
+
def polymorphic?
|
50
|
+
@_klass
|
51
|
+
end
|
52
|
+
|
53
|
+
# Compare with other objects
|
54
|
+
def eql?(other)
|
55
|
+
self.class == other.class &&
|
56
|
+
self._name == other._name &&
|
57
|
+
self._type == other._type &&
|
58
|
+
self._klass == other._klass
|
59
|
+
end
|
60
|
+
alias :== :eql?
|
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
|
70
|
+
def method_missing(method_id, *args)
|
71
|
+
super if method_id == :to_ary
|
72
|
+
if (args.size == 1) && (Class === args[0])
|
73
|
+
KeyPath.new(self, Join.new(method_id, Arel::InnerJoin, args[0]))
|
74
|
+
else
|
75
|
+
KeyPath.new(self, method_id)
|
76
|
+
end
|
77
|
+
end
|
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
|
+
|
87
|
+
# expand_hash_conditions_for_aggregates assumes our hash keys can be
|
88
|
+
# converted to symbols, so this has to be implemented, but it doesn't
|
89
|
+
# really have to do anything useful.
|
90
|
+
# @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
|
91
|
+
def to_sym
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
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
|
100
|
+
def convert_to_class(value)
|
101
|
+
case value
|
102
|
+
when String, Symbol
|
103
|
+
Kernel.const_get(value)
|
104
|
+
when Class
|
105
|
+
value
|
106
|
+
else
|
107
|
+
raise ArgumentError, "#{value} cannot be converted to a Class"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'squeel/nodes/operators'
|
2
|
+
require 'squeel/nodes/predicate_operators'
|
3
|
+
|
4
|
+
module Squeel
|
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.
|
8
|
+
class KeyPath
|
9
|
+
include PredicateOperators
|
10
|
+
include Operators
|
11
|
+
|
12
|
+
# We need some methods to fall through to the endpoint or create a new
|
13
|
+
# stub of the given name
|
14
|
+
%w(id == != =~ !~).each do |method_name|
|
15
|
+
undef_method method_name if method_defined?(method_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Array<Symbol, Stub, Join>] The path
|
19
|
+
attr_reader :path
|
20
|
+
|
21
|
+
# @return The endpoint, either another key as in the path, or a predicate, function, etc.
|
22
|
+
attr_reader :endpoint
|
23
|
+
|
24
|
+
# Create a new KeyPath.
|
25
|
+
# @param [Array, Object] path The intial path. Will be converted to an array if it isn't already.
|
26
|
+
# @param endpoint the endpoint of the KeyPath
|
27
|
+
# @param [Boolean] absolute If the KeyPath should start from the base
|
28
|
+
# or remain relative to whatever location it's found.
|
29
|
+
def initialize(path, endpoint, absolute = false)
|
30
|
+
@path, @endpoint = path, endpoint
|
31
|
+
@path = [@path] unless Array === @path
|
32
|
+
@endpoint = Stub.new(@endpoint) if Symbol === @endpoint
|
33
|
+
@absolute = absolute
|
34
|
+
end
|
35
|
+
|
36
|
+
# Whether or not the KeyPath should be interpreted relative to its current location
|
37
|
+
# (if nested in a Hash, for instance) or as though it's at the base.
|
38
|
+
# @return [Boolean] The flag's value
|
39
|
+
def absolute?
|
40
|
+
@absolute
|
41
|
+
end
|
42
|
+
|
43
|
+
# Object comparison
|
44
|
+
def eql?(other)
|
45
|
+
self.class == other.class &&
|
46
|
+
self.path == other.path &&
|
47
|
+
self.endpoint.eql?(other.endpoint) &&
|
48
|
+
self.absolute? == other.absolute?
|
49
|
+
end
|
50
|
+
|
51
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
52
|
+
# responds to |
|
53
|
+
# @param other The right hand side of the operation
|
54
|
+
# @return [Or] An Or node with the KeyPath on the left side and the other object on the right.
|
55
|
+
def |(other)
|
56
|
+
endpoint.respond_to?(:|) ? super : no_method_error(:|)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
60
|
+
# responds to &
|
61
|
+
# @param other The right hand side of the operation
|
62
|
+
# @return [And] An And node with the KeyPath on the left side and the other object on the right.
|
63
|
+
def &(other)
|
64
|
+
endpoint.respond_to?(:&) ? super : no_method_error(:&)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
68
|
+
# responds to -@
|
69
|
+
# @return [Not] A not node with the KeyPath as its expression
|
70
|
+
def -@
|
71
|
+
endpoint.respond_to?(:-@) ? super : no_method_error(:-@)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
75
|
+
# responds to +
|
76
|
+
# @param other The right hand side of the operation
|
77
|
+
# @return [Operation] An operation (with operator +) with the KeyPath on its left and the other object on the right.
|
78
|
+
def +(other)
|
79
|
+
endpoint.respond_to?(:+) ? super : no_method_error(:+)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
83
|
+
# responds to -
|
84
|
+
# @param other The right hand side of the operation
|
85
|
+
# @return [Operation] An operation (with operator -) with the KeyPath on its left and the other object on the right.
|
86
|
+
def -(other)
|
87
|
+
endpoint.respond_to?(:-) ? super : no_method_error(:-)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
91
|
+
# responds to *
|
92
|
+
# @param other The right hand side of the operation
|
93
|
+
# @return [Operation] An operation (with operator *) with the KeyPath on its left and the other object on the right.
|
94
|
+
def *(other)
|
95
|
+
endpoint.respond_to?(:*) ? super : no_method_error(:*)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
99
|
+
# responds to /
|
100
|
+
# @param other The right hand side of the operation
|
101
|
+
# @return [Operation] An operation (with operator /) with the KeyPath on its left and the other object on the right.
|
102
|
+
def /(other)
|
103
|
+
endpoint.respond_to?(:/) ? super : no_method_error(:/)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Allow KeyPath to function like its endpoint, in the case where its endpoint
|
107
|
+
# responds to #op
|
108
|
+
# @param [String, Symbol] operator The custom operator
|
109
|
+
# @param other The right hand side of the operation
|
110
|
+
# @return [Operation] An operation with the given custom operator, the KeyPath on its left and the other object on the right.
|
111
|
+
def op(operator, other)
|
112
|
+
endpoint.respond_to?(:op) ? super : no_method_error(:/)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Set the absolute flag on this KeyPath
|
116
|
+
# @return [KeyPath] This keypath, with its absolute flag set to true
|
117
|
+
def ~
|
118
|
+
@absolute = true
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
# For use with equality tests
|
123
|
+
def hash
|
124
|
+
[self.class, endpoint, *path].hash
|
125
|
+
end
|
126
|
+
|
127
|
+
# expand_hash_conditions_for_aggregates assumes our hash keys can be
|
128
|
+
# converted to symbols, so this has to be implemented, but it doesn't
|
129
|
+
# really have to do anything useful.
|
130
|
+
# @return [NilClass] Just to avoid bombing out on expand_hash_conditions_for_aggregates
|
131
|
+
def to_sym
|
132
|
+
nil
|
133
|
+
end
|
134
|
+
|
135
|
+
# Delegate % to the KeyPath's endpoint, with a bit of special logic for stubs or functions.
|
136
|
+
# @param val The value to be supplied to the created/existing predicate
|
137
|
+
# @return [KeyPath] This KeyPath, with a predicate endpoint containing the given value
|
138
|
+
def %(val)
|
139
|
+
case endpoint
|
140
|
+
when Stub, Function
|
141
|
+
Array === val ? self.in(val) : self.eq(val)
|
142
|
+
self
|
143
|
+
else
|
144
|
+
endpoint % val
|
145
|
+
self
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# @return [Array] The KeyPath's path, including its endpoint, as a single array.
|
150
|
+
def path_with_endpoint
|
151
|
+
path + [endpoint]
|
152
|
+
end
|
153
|
+
|
154
|
+
# Impleneted (and aliased to :to_str) to play nicely with ActiveRecord grouped calculations
|
155
|
+
def to_s
|
156
|
+
path.map(&:to_s).join('.') << ".#{endpoint}"
|
157
|
+
end
|
158
|
+
alias :to_str :to_s
|
159
|
+
|
160
|
+
# Appends to the KeyPath or delegates to the endpoint, as appropriate
|
161
|
+
# @return [KeyPath] The updated KeyPath
|
162
|
+
def method_missing(method_id, *args)
|
163
|
+
super if method_id == :to_ary
|
164
|
+
if endpoint.respond_to? method_id
|
165
|
+
@endpoint = @endpoint.send(method_id, *args)
|
166
|
+
self
|
167
|
+
elsif Stub === endpoint || Join === endpoint
|
168
|
+
@path << endpoint
|
169
|
+
if args.empty?
|
170
|
+
@endpoint = Stub.new(method_id)
|
171
|
+
elsif (args.size == 1) && (Class === args[0])
|
172
|
+
@endpoint = Join.new(method_id, Arel::InnerJoin, args[0])
|
173
|
+
else
|
174
|
+
@endpoint = Nodes::Function.new method_id, args
|
175
|
+
end
|
176
|
+
self
|
177
|
+
else
|
178
|
+
super
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
private
|
183
|
+
|
184
|
+
# Raises a NoMethodError manually, bypassing #method_missing.
|
185
|
+
# Used by special-case operator overrides.
|
186
|
+
def no_method_error(method_id)
|
187
|
+
raise NoMethodError, "undefined method `#{method_id}' for #{self}:#{self.class}"
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Squeel
|
2
|
+
module Nodes
|
3
|
+
# A node containing multiple children. Just the And node for now.
|
4
|
+
class Nary
|
5
|
+
include PredicateOperators
|
6
|
+
|
7
|
+
# @return [Array] This node's children
|
8
|
+
attr_reader :children
|
9
|
+
|
10
|
+
# Creates a new Nary node with the given array of children
|
11
|
+
# @param [Array] children The node's children
|
12
|
+
def initialize(children)
|
13
|
+
raise ArgumentError, '#{self.class} requires an array' unless Array === children
|
14
|
+
# We don't dup here, as incoming arrays should be created by the
|
15
|
+
# PredicateOperators#& method on other nodes. If you're creating And nodes
|
16
|
+
# manually, by sure that they're new arays.
|
17
|
+
@children = children
|
18
|
+
end
|
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.
|
23
|
+
def &(other)
|
24
|
+
@children << other
|
25
|
+
self
|
26
|
+
end
|
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
|
31
|
+
def -(other)
|
32
|
+
@children << Not.new(other)
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
# Object comparison
|
37
|
+
def eql?(other)
|
38
|
+
self.class == other.class &&
|
39
|
+
self.children == other.children
|
40
|
+
end
|
41
|
+
alias :== :eql?
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'squeel/nodes/function'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Nodes
|
5
|
+
# An Operation is basically a function with its operands limited, and rearranged,
|
6
|
+
# so Squeel implements it as such.
|
7
|
+
class Operation < Function
|
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
|
13
|
+
def initialize(left, operator, right)
|
14
|
+
super(operator, [left, right])
|
15
|
+
end
|
16
|
+
|
17
|
+
# An operation should probably call its "function" name an "operator", shouldn't it?
|
18
|
+
alias :operator :name
|
19
|
+
|
20
|
+
# @return The left operand
|
21
|
+
def left
|
22
|
+
args[0]
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return The right operand
|
26
|
+
def right
|
27
|
+
args[1]
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Squeel
|
2
|
+
module Nodes
|
3
|
+
# Module containing Operation factory methods for inclusion in Squeel nodes
|
4
|
+
module Operators
|
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
|
9
|
+
def +(value)
|
10
|
+
Operation.new(self, :+, value)
|
11
|
+
end
|
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
|
16
|
+
def -(value)
|
17
|
+
Operation.new(self, :-, value)
|
18
|
+
end
|
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
|
23
|
+
def *(value)
|
24
|
+
Operation.new(self, :*, value)
|
25
|
+
end
|
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
|
30
|
+
def /(value)
|
31
|
+
Operation.new(self, :/, value)
|
32
|
+
end
|
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
|
37
|
+
def op(operator, value)
|
38
|
+
Operation.new(self, operator, value)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|