squeel 1.1.1 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.rspec +3 -0
- data/.travis.yml +36 -4
- data/CHANGELOG.md +15 -0
- data/Gemfile +1 -1
- data/README.md +47 -6
- data/Rakefile +14 -2
- data/lib/squeel.rb +9 -1
- data/lib/squeel/adapters/active_record.rb +0 -1
- data/lib/squeel/adapters/active_record/3.0/join_dependency_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/3.0/relation_extensions.rb +12 -1
- data/lib/squeel/adapters/active_record/3.1/join_dependency_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/3.2/join_dependency_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/4.0/join_dependency_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/4.0/relation_extensions.rb +92 -0
- data/lib/squeel/adapters/active_record/4.1/compat.rb +15 -0
- data/lib/squeel/adapters/active_record/4.1/context.rb +88 -0
- data/lib/squeel/adapters/active_record/4.1/preloader_extensions.rb +31 -0
- data/lib/squeel/adapters/active_record/4.1/reflection_extensions.rb +37 -0
- data/lib/squeel/adapters/active_record/4.1/relation_extensions.rb +307 -0
- data/lib/squeel/adapters/active_record/4.2/compat.rb +1 -0
- data/lib/squeel/adapters/active_record/4.2/context.rb +1 -0
- data/lib/squeel/adapters/active_record/4.2/preloader_extensions.rb +1 -0
- data/lib/squeel/adapters/active_record/4.2/relation_extensions.rb +108 -0
- data/lib/squeel/adapters/active_record/context.rb +7 -7
- data/lib/squeel/adapters/active_record/join_dependency_extensions.rb +9 -13
- data/lib/squeel/adapters/active_record/relation_extensions.rb +38 -8
- data/lib/squeel/core_ext/symbol.rb +3 -3
- data/lib/squeel/dsl.rb +1 -1
- data/lib/squeel/nodes.rb +1 -0
- data/lib/squeel/nodes/as.rb +12 -0
- data/lib/squeel/nodes/join.rb +8 -4
- data/lib/squeel/nodes/key_path.rb +10 -1
- data/lib/squeel/nodes/node.rb +21 -0
- data/lib/squeel/nodes/stub.rb +8 -4
- data/lib/squeel/nodes/subquery_join.rb +44 -0
- data/lib/squeel/version.rb +1 -1
- data/lib/squeel/visitors.rb +2 -0
- data/lib/squeel/visitors/enumeration_visitor.rb +101 -0
- data/lib/squeel/visitors/order_visitor.rb +9 -2
- data/lib/squeel/visitors/predicate_visitor.rb +11 -0
- data/lib/squeel/visitors/preload_visitor.rb +12 -0
- data/lib/squeel/visitors/visitor.rb +89 -13
- data/spec/config.travis.yml +13 -0
- data/spec/config.yml +12 -0
- data/spec/console.rb +3 -12
- data/spec/core_ext/symbol_spec.rb +3 -3
- data/spec/helpers/squeel_helper.rb +8 -5
- data/spec/spec_helper.rb +4 -16
- data/spec/squeel/adapters/active_record/context_spec.rb +8 -4
- data/spec/squeel/adapters/active_record/join_dependency_extensions_spec.rb +123 -38
- data/spec/squeel/adapters/active_record/relation_extensions_spec.rb +350 -124
- data/spec/squeel/core_ext/symbol_spec.rb +3 -3
- data/spec/squeel/nodes/join_spec.rb +4 -4
- data/spec/squeel/nodes/stub_spec.rb +3 -3
- data/spec/squeel/nodes/subquery_join_spec.rb +46 -0
- data/spec/squeel/visitors/order_visitor_spec.rb +3 -3
- data/spec/squeel/visitors/predicate_visitor_spec.rb +69 -36
- data/spec/squeel/visitors/select_visitor_spec.rb +1 -1
- data/spec/squeel/visitors/visitor_spec.rb +7 -6
- data/spec/support/models.rb +99 -15
- data/spec/support/schema.rb +109 -4
- data/squeel.gemspec +8 -6
- metadata +89 -107
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/spec/blueprints/articles.rb +0 -5
- data/spec/blueprints/comments.rb +0 -5
- data/spec/blueprints/notes.rb +0 -3
- data/spec/blueprints/people.rb +0 -4
- data/spec/blueprints/tags.rb +0 -3
@@ -3,6 +3,12 @@ require 'squeel/context'
|
|
3
3
|
module Squeel
|
4
4
|
module Adapters
|
5
5
|
module ActiveRecord
|
6
|
+
JoinPart = if defined?(::ActiveRecord::Associations::JoinDependency::JoinPart)
|
7
|
+
::ActiveRecord::Associations::JoinDependency::JoinPart
|
8
|
+
elsif defined?(::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase)
|
9
|
+
::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase
|
10
|
+
end
|
11
|
+
|
6
12
|
class Context < ::Squeel::Context
|
7
13
|
|
8
14
|
def initialize(object)
|
@@ -23,7 +29,7 @@ module Squeel
|
|
23
29
|
}
|
24
30
|
when Nodes::Join
|
25
31
|
@object.join_associations.detect { |j|
|
26
|
-
j.reflection.name == object.
|
32
|
+
j.reflection.name == object._name && j.parent == parent &&
|
27
33
|
(object.polymorphic? ? j.reflection.klass == object._klass : true)
|
28
34
|
}
|
29
35
|
else
|
@@ -73,12 +79,6 @@ module Squeel
|
|
73
79
|
end
|
74
80
|
|
75
81
|
end
|
76
|
-
|
77
|
-
if defined?(::ActiveRecord::Associations::JoinDependency::JoinPart)
|
78
|
-
JoinPart = ::ActiveRecord::Associations::JoinDependency::JoinPart
|
79
|
-
elsif defined?(::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase)
|
80
|
-
JoinPart = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase
|
81
|
-
end
|
82
82
|
end
|
83
83
|
end
|
84
84
|
end
|
@@ -1,17 +1,22 @@
|
|
1
|
-
require 'polyamorous'
|
2
|
-
|
3
1
|
module Squeel
|
4
2
|
module Adapters
|
5
3
|
module ActiveRecord
|
6
|
-
|
4
|
+
if defined?(::ActiveRecord::Associations::JoinDependency)
|
5
|
+
JoinAssociation = ::ActiveRecord::Associations::JoinDependency::JoinAssociation
|
6
|
+
JoinDependency = ::ActiveRecord::Associations::JoinDependency
|
7
|
+
elsif defined?(::ActiveRecord::Associations::ClassMethods::JoinDependency)
|
8
|
+
JoinAssociation = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
|
9
|
+
JoinDependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency
|
10
|
+
end
|
7
11
|
|
12
|
+
module JoinDependencyExtensions
|
8
13
|
def self.included(base)
|
9
14
|
base.class_eval do
|
10
15
|
alias_method_chain :build, :squeel
|
11
16
|
end
|
12
17
|
end
|
13
18
|
|
14
|
-
def build_with_squeel(associations, parent = nil, join_type =
|
19
|
+
def build_with_squeel(associations, parent = nil, join_type = InnerJoin)
|
15
20
|
case associations
|
16
21
|
when Nodes::Stub
|
17
22
|
associations = associations.symbol
|
@@ -29,15 +34,6 @@ module Squeel
|
|
29
34
|
build_without_squeel(associations, parent, join_type)
|
30
35
|
end
|
31
36
|
end
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
if defined?(::ActiveRecord::Associations::JoinDependency)
|
36
|
-
JoinAssociation = ::ActiveRecord::Associations::JoinDependency::JoinAssociation
|
37
|
-
JoinDependency = ::ActiveRecord::Associations::JoinDependency
|
38
|
-
elsif defined?(::ActiveRecord::Associations::ClassMethods::JoinDependency)
|
39
|
-
JoinAssociation = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
|
40
|
-
JoinDependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency
|
41
37
|
end
|
42
38
|
|
43
39
|
JoinDependency.send :include, Adapters::ActiveRecord::JoinDependencyExtensions
|
@@ -45,7 +45,6 @@ module Squeel
|
|
45
45
|
merge_resolving_duplicate_squeel_equalities(r)
|
46
46
|
end
|
47
47
|
else
|
48
|
-
puts r.inspect if r.is_a?(Proc)
|
49
48
|
super(r)
|
50
49
|
end
|
51
50
|
end
|
@@ -115,6 +114,8 @@ module Squeel
|
|
115
114
|
:stashed_join
|
116
115
|
when Arel::Nodes::Join
|
117
116
|
:join_node
|
117
|
+
when Nodes::SubqueryJoin
|
118
|
+
:subquery_join
|
118
119
|
else
|
119
120
|
raise 'unknown class: %s' % join.class.name
|
120
121
|
end
|
@@ -123,6 +124,7 @@ module Squeel
|
|
123
124
|
association_joins = buckets[:association_join] || []
|
124
125
|
stashed_association_joins = buckets[:stashed_join] || []
|
125
126
|
join_nodes = (buckets[:join_node] || []).uniq
|
127
|
+
subquery_joins = (buckets[:subquery_join] || []).uniq
|
126
128
|
string_joins = (buckets[:string_join] || []).map { |x|
|
127
129
|
x.strip
|
128
130
|
}.uniq
|
@@ -144,11 +146,12 @@ module Squeel
|
|
144
146
|
association.join_to(manager)
|
145
147
|
end
|
146
148
|
|
147
|
-
manager.join_sources.concat
|
149
|
+
manager.join_sources.concat(join_list)
|
150
|
+
manager.join_sources.concat(build_join_from_subquery(subquery_joins))
|
148
151
|
|
149
152
|
manager
|
150
153
|
end
|
151
|
-
# For 4.
|
154
|
+
# For 4.0 adapters
|
152
155
|
alias :build_joins :build_join_dependency
|
153
156
|
|
154
157
|
def includes(*args)
|
@@ -279,12 +282,12 @@ module Squeel
|
|
279
282
|
end
|
280
283
|
end
|
281
284
|
|
282
|
-
def find_equality_predicates(nodes)
|
285
|
+
def find_equality_predicates(nodes, relation_table_name = table_name)
|
283
286
|
nodes.map { |node|
|
284
287
|
case node
|
285
288
|
when Arel::Nodes::Equality
|
286
289
|
if node.left.respond_to?(:relation) &&
|
287
|
-
node.left.relation.name ==
|
290
|
+
node.left.relation.name == relation_table_name
|
288
291
|
node
|
289
292
|
end
|
290
293
|
when Arel::Nodes::Grouping
|
@@ -395,9 +398,8 @@ module Squeel
|
|
395
398
|
# your model's default scope. We hijack it in order to dig down into
|
396
399
|
# And and Grouping nodes, which are equivalent to seeing top-level
|
397
400
|
# Equality nodes in stock AR terms.
|
398
|
-
def where_values_hash_with_squeel
|
399
|
-
equalities = find_equality_predicates(where_visit(with_default_scope.where_values))
|
400
|
-
|
401
|
+
def where_values_hash_with_squeel(relation_table_name = table_name)
|
402
|
+
equalities = find_equality_predicates(where_visit(with_default_scope.where_values), relation_table_name)
|
401
403
|
binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }]
|
402
404
|
|
403
405
|
Hash[equalities.map { |where|
|
@@ -406,6 +408,34 @@ module Squeel
|
|
406
408
|
}]
|
407
409
|
end
|
408
410
|
|
411
|
+
def build_join_from_subquery(subquery_joins)
|
412
|
+
subquery_joins.map do |join|
|
413
|
+
join.type.new(
|
414
|
+
Arel::Nodes::TableAlias.new(
|
415
|
+
Arel::Nodes::Grouping.new(join.subquery.left.arel.ast),
|
416
|
+
join.subquery.right),
|
417
|
+
Arel::Nodes::On.new(where_visit(join.constraints))
|
418
|
+
)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
def preprocess_attrs_with_ar(attributes)
|
423
|
+
attributes.map do |key, value|
|
424
|
+
case key
|
425
|
+
when Squeel::Nodes::Node
|
426
|
+
{key => value}
|
427
|
+
when Symbol
|
428
|
+
if value.is_a?(Hash)
|
429
|
+
{key => value}
|
430
|
+
else
|
431
|
+
::ActiveRecord::PredicateBuilder.build_from_hash(klass, {key => value}, table)
|
432
|
+
end
|
433
|
+
else
|
434
|
+
::ActiveRecord::PredicateBuilder.build_from_hash(klass, {key => value}, table)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
409
439
|
end
|
410
440
|
end
|
411
441
|
end
|
@@ -15,15 +15,15 @@ class Symbol
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def inner
|
18
|
-
Squeel::Nodes::Join.new(self,
|
18
|
+
Squeel::Nodes::Join.new(self, Squeel::InnerJoin)
|
19
19
|
end
|
20
20
|
|
21
21
|
def outer
|
22
|
-
Squeel::Nodes::Join.new(self,
|
22
|
+
Squeel::Nodes::Join.new(self, Squeel::OuterJoin)
|
23
23
|
end
|
24
24
|
|
25
25
|
def of_class(klass)
|
26
|
-
Squeel::Nodes::Join.new(self,
|
26
|
+
Squeel::Nodes::Join.new(self, Squeel::InnerJoin, klass)
|
27
27
|
end
|
28
28
|
|
29
29
|
end
|
data/lib/squeel/dsl.rb
CHANGED
@@ -110,7 +110,7 @@ module Squeel
|
|
110
110
|
if args.empty?
|
111
111
|
Nodes::Stub.new method_id
|
112
112
|
elsif (args.size == 1) && (Class === args[0])
|
113
|
-
Nodes::Join.new(method_id,
|
113
|
+
Nodes::Join.new(method_id, InnerJoin, args[0])
|
114
114
|
else
|
115
115
|
Nodes::Function.new method_id, args
|
116
116
|
end
|
data/lib/squeel/nodes.rb
CHANGED
data/lib/squeel/nodes/as.rb
CHANGED
@@ -12,6 +12,18 @@ module Squeel
|
|
12
12
|
def initialize(left, right)
|
13
13
|
@left, @right = left, right
|
14
14
|
end
|
15
|
+
|
16
|
+
def on(*args)
|
17
|
+
raise "only can convert ActiveRecord::Relation to a join node" unless left.is_a?(ActiveRecord::Relation)
|
18
|
+
proc =
|
19
|
+
if block_given?
|
20
|
+
DSL.eval(&Proc.new)
|
21
|
+
else
|
22
|
+
args
|
23
|
+
end
|
24
|
+
|
25
|
+
SubqueryJoin.new(self, proc)
|
26
|
+
end
|
15
27
|
end
|
16
28
|
end
|
17
29
|
end
|
data/lib/squeel/nodes/join.rb
CHANGED
@@ -14,21 +14,21 @@ module Squeel
|
|
14
14
|
# @param [Symbol] name The association name
|
15
15
|
# @param [Arel::InnerJoin, Arel::OuterJoin] type The Arel join class
|
16
16
|
# @param [Class, String, Symbol] klass The polymorphic belongs_to class or class name
|
17
|
-
def initialize(name, type =
|
17
|
+
def initialize(name, type = InnerJoin, klass = nil)
|
18
18
|
@_join = Polyamorous::Join.new(name, type, klass)
|
19
19
|
end
|
20
20
|
|
21
21
|
# Set the join type to an inner join
|
22
22
|
# @return [Join] The join, with an updated join type.
|
23
23
|
def inner
|
24
|
-
self._type =
|
24
|
+
self._type = InnerJoin
|
25
25
|
self
|
26
26
|
end
|
27
27
|
|
28
28
|
# Set the join type to an outer join
|
29
29
|
# @return [Join] The join, with an updated join type.
|
30
30
|
def outer
|
31
|
-
self._type =
|
31
|
+
self._type = OuterJoin
|
32
32
|
self
|
33
33
|
end
|
34
34
|
|
@@ -61,7 +61,7 @@ module Squeel
|
|
61
61
|
def method_missing(method_id, *args)
|
62
62
|
super if method_id == :to_ary
|
63
63
|
if (args.size == 1) && (Class === args[0])
|
64
|
-
KeyPath.new([self, Join.new(method_id,
|
64
|
+
KeyPath.new([self, Join.new(method_id, InnerJoin, args[0])])
|
65
65
|
else
|
66
66
|
KeyPath.new([self, method_id])
|
67
67
|
end
|
@@ -83,6 +83,10 @@ module Squeel
|
|
83
83
|
nil
|
84
84
|
end
|
85
85
|
|
86
|
+
def add_to_tree(hash)
|
87
|
+
hash[_join] ||= {}
|
88
|
+
end
|
89
|
+
|
86
90
|
end
|
87
91
|
end
|
88
92
|
end
|
@@ -177,6 +177,10 @@ module Squeel
|
|
177
177
|
end
|
178
178
|
alias :to_str :to_s
|
179
179
|
|
180
|
+
def add_to_tree(hash)
|
181
|
+
walk_through_path(path.dup, hash)
|
182
|
+
end
|
183
|
+
|
180
184
|
# Appends to the KeyPath or delegates to the endpoint, as appropriate
|
181
185
|
# @return [KeyPath] The updated KeyPath
|
182
186
|
def method_missing(method_id, *args, &block)
|
@@ -194,7 +198,7 @@ module Squeel
|
|
194
198
|
if args.empty?
|
195
199
|
@path << Stub.new(method_id)
|
196
200
|
elsif (args.size == 1) && (Class === args[0])
|
197
|
-
@path << Join.new(method_id,
|
201
|
+
@path << Join.new(method_id, InnerJoin, args[0])
|
198
202
|
else
|
199
203
|
@path << Nodes::Function.new(method_id, args)
|
200
204
|
end
|
@@ -213,6 +217,11 @@ module Squeel
|
|
213
217
|
@path = @path.dup
|
214
218
|
end
|
215
219
|
|
220
|
+
def walk_through_path(path, hash)
|
221
|
+
cache = path.shift.add_to_tree(hash)
|
222
|
+
path.empty? ? cache : walk_through_path(path, cache)
|
223
|
+
end
|
224
|
+
|
216
225
|
# Raises a NoMethodError manually, bypassing #method_missing.
|
217
226
|
# Used by special-case operator overrides.
|
218
227
|
def no_method_error(method_id)
|
data/lib/squeel/nodes/node.rb
CHANGED
@@ -1,6 +1,27 @@
|
|
1
|
+
require 'polyamorous/tree_node'
|
2
|
+
|
1
3
|
module Squeel
|
2
4
|
module Nodes
|
3
5
|
class Node
|
6
|
+
include ::Polyamorous::TreeNode
|
7
|
+
|
8
|
+
def each(&block)
|
9
|
+
return enum_for(:each) unless block_given?
|
10
|
+
|
11
|
+
Visitors::EnumerationVisitor.new(block).accept(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
# We don't want the full Enumerable method list, because it will mess
|
15
|
+
# with stuff like KeyPath
|
16
|
+
def grep(object, &block)
|
17
|
+
if block_given?
|
18
|
+
each { |value| yield value if object === value }
|
19
|
+
else
|
20
|
+
[].tap do |results|
|
21
|
+
each { |value| results << value if object === value }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
4
25
|
end
|
5
26
|
end
|
6
27
|
end
|
data/lib/squeel/nodes/stub.rb
CHANGED
@@ -79,7 +79,7 @@ module Squeel
|
|
79
79
|
if args.empty?
|
80
80
|
KeyPath.new([self, method_id])
|
81
81
|
elsif (args.size == 1) && (Class === args[0])
|
82
|
-
KeyPath.new([self, Join.new(method_id,
|
82
|
+
KeyPath.new([self, Join.new(method_id, InnerJoin, args[0])])
|
83
83
|
else
|
84
84
|
KeyPath.new([self, Nodes::Function.new(method_id, args)])
|
85
85
|
end
|
@@ -102,7 +102,7 @@ module Squeel
|
|
102
102
|
# Create an inner Join node for the association named by this Stub
|
103
103
|
# @return [Join] The new inner Join node
|
104
104
|
def inner
|
105
|
-
Join.new(self.symbol,
|
105
|
+
Join.new(self.symbol, InnerJoin)
|
106
106
|
end
|
107
107
|
|
108
108
|
# Create a keypath with a sifter as its endpoint
|
@@ -114,14 +114,18 @@ module Squeel
|
|
114
114
|
# Create an outer Join node for the association named by this Stub
|
115
115
|
# @return [Join] The new outer Join node
|
116
116
|
def outer
|
117
|
-
Join.new(self.symbol,
|
117
|
+
Join.new(self.symbol, OuterJoin)
|
118
118
|
end
|
119
119
|
|
120
120
|
# Create a polymorphic Join node for the association named by this Stub,
|
121
121
|
# @param [Class] klass The polymorphic belongs_to class for this Join
|
122
122
|
# @return [Join] The new polymorphic Join node
|
123
123
|
def of_class(klass)
|
124
|
-
Join.new(self.symbol,
|
124
|
+
Join.new(self.symbol, InnerJoin, klass)
|
125
|
+
end
|
126
|
+
|
127
|
+
def add_to_tree(hash)
|
128
|
+
hash[symbol] ||= {}
|
125
129
|
end
|
126
130
|
|
127
131
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Squeel
|
2
|
+
module Nodes
|
3
|
+
class SubqueryJoin < Node
|
4
|
+
attr_accessor :subquery, :type, :constraints
|
5
|
+
|
6
|
+
def initialize(subquery, constraints, type = Squeel::InnerJoin)
|
7
|
+
raise ArgumentError,
|
8
|
+
"subquery(#{subquery}) isn't an Squeel::Nodes::As" unless subquery.is_a?(As)
|
9
|
+
|
10
|
+
raise ArgumentError,
|
11
|
+
"constraints(#{constraints}) isn't a Squeel::Nodes::Node" unless constraints.is_a?(Node)
|
12
|
+
|
13
|
+
self.subquery = subquery
|
14
|
+
self.constraints = constraints
|
15
|
+
self.type = type
|
16
|
+
end
|
17
|
+
|
18
|
+
# Implemented for equality testing
|
19
|
+
def hash
|
20
|
+
[subquery, type, constraints].hash
|
21
|
+
end
|
22
|
+
|
23
|
+
def inner
|
24
|
+
self.type = Squeel::InnerJoin
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def outer
|
29
|
+
self.type = Squeel::OuterJoin
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Compare with other objects
|
34
|
+
def eql?(other)
|
35
|
+
self.class.eql?(other.class) &&
|
36
|
+
self.subquery.eql?(other.subquery) &&
|
37
|
+
self.type.eql?(other.type) &&
|
38
|
+
self.constraints.eql?(other.constraints)
|
39
|
+
end
|
40
|
+
alias :== :eql?
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/squeel/version.rb
CHANGED
data/lib/squeel/visitors.rb
CHANGED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'active_support/core_ext/module'
|
2
|
+
require 'squeel/nodes'
|
3
|
+
|
4
|
+
module Squeel
|
5
|
+
module Visitors
|
6
|
+
# The Enumeration visitor class, used to implement Node#each
|
7
|
+
class EnumerationVisitor
|
8
|
+
|
9
|
+
# Create a new EnumerationVisitor.
|
10
|
+
#
|
11
|
+
# @param [Proc] block The block to execute against each node.
|
12
|
+
def initialize(block = Proc.new)
|
13
|
+
@block = block
|
14
|
+
end
|
15
|
+
|
16
|
+
# Accept an object.
|
17
|
+
#
|
18
|
+
# @param object The object to visit
|
19
|
+
# @return The results of the node visitation, which will be the last
|
20
|
+
# call to the @block
|
21
|
+
def accept(object)
|
22
|
+
visit(object)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
# A hash that caches the method name to use for a visitor for a given
|
27
|
+
# class
|
28
|
+
DISPATCH = Hash.new do |hash, klass|
|
29
|
+
hash[klass] = "visit_#{(klass.name || '').gsub('::', '_')}"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Visit the object.
|
33
|
+
#
|
34
|
+
# @param object The object to visit
|
35
|
+
def visit(object)
|
36
|
+
send(DISPATCH[object.class], object)
|
37
|
+
@block.call(object)
|
38
|
+
rescue NoMethodError => e
|
39
|
+
raise e if respond_to?(DISPATCH[object.class], true)
|
40
|
+
|
41
|
+
superklass = object.class.ancestors.find { |klass|
|
42
|
+
respond_to?(DISPATCH[klass], true)
|
43
|
+
}
|
44
|
+
raise(TypeError, "Cannot visit #{object.class}") unless superklass
|
45
|
+
DISPATCH[object.class] = DISPATCH[superklass]
|
46
|
+
retry
|
47
|
+
end
|
48
|
+
|
49
|
+
def visit_terminal(o)
|
50
|
+
end
|
51
|
+
alias :visit_Object :visit_terminal
|
52
|
+
|
53
|
+
def visit_Array(o)
|
54
|
+
o.map { |v| visit(v) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def visit_Hash(o)
|
58
|
+
o.each { |k, v| visit(k); visit(v) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def visit_Squeel_Nodes_Nary(o)
|
62
|
+
visit(o.children)
|
63
|
+
end
|
64
|
+
|
65
|
+
def visit_Squeel_Nodes_Binary(o)
|
66
|
+
visit(o.left)
|
67
|
+
visit(o.right)
|
68
|
+
end
|
69
|
+
|
70
|
+
def visit_Squeel_Nodes_Unary(o)
|
71
|
+
visit(o.expr)
|
72
|
+
end
|
73
|
+
|
74
|
+
def visit_Squeel_Nodes_Order(o)
|
75
|
+
visit(o.expr)
|
76
|
+
end
|
77
|
+
|
78
|
+
def visit_Squeel_Nodes_Function(o)
|
79
|
+
visit(o.args)
|
80
|
+
end
|
81
|
+
|
82
|
+
def visit_Squeel_Nodes_Predicate(o)
|
83
|
+
visit(o.expr)
|
84
|
+
visit(o.value)
|
85
|
+
end
|
86
|
+
|
87
|
+
def visit_Squeel_Nodes_KeyPath(o)
|
88
|
+
visit(o.path)
|
89
|
+
end
|
90
|
+
|
91
|
+
def visit_Squeel_Nodes_Join(o)
|
92
|
+
visit(o._join)
|
93
|
+
end
|
94
|
+
|
95
|
+
def visit_Squeel_Nodes_Literal(o)
|
96
|
+
visit(o.expr)
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|