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
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'squeel/context'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
class Context < ::Squeel::Context
|
7
|
+
# Because the AR::Associations namespace is insane
|
8
|
+
JoinBase = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinBase
|
9
|
+
|
10
|
+
def initialize(object)
|
11
|
+
super
|
12
|
+
@base = object.join_base
|
13
|
+
@engine = @base.arel_engine
|
14
|
+
@arel_visitor = Arel::Visitors.visitor_for @engine
|
15
|
+
@default_table = Arel::Table.new(@base.table_name, :as => @base.aliased_table_name, :engine => @engine)
|
16
|
+
end
|
17
|
+
|
18
|
+
def find(object, parent = @base)
|
19
|
+
if JoinBase === parent
|
20
|
+
object = object.to_sym if String === object
|
21
|
+
case object
|
22
|
+
when Symbol, Nodes::Stub
|
23
|
+
@object.join_associations.detect { |j|
|
24
|
+
j.reflection.name == object.to_sym && j.parent == parent
|
25
|
+
}
|
26
|
+
when Nodes::Join
|
27
|
+
@object.join_associations.detect { |j|
|
28
|
+
j.reflection.name == object.name && j.parent == parent &&
|
29
|
+
(object.polymorphic? ? j.reflection.klass == object._klass : true)
|
30
|
+
}
|
31
|
+
else
|
32
|
+
@object.join_associations.detect { |j|
|
33
|
+
j.reflection == object && j.parent == parent
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def traverse(keypath, parent = @base, include_endpoint = false)
|
40
|
+
parent = @base if keypath.absolute?
|
41
|
+
keypath.path.each do |key|
|
42
|
+
parent = find(key, parent) || key
|
43
|
+
end
|
44
|
+
parent = find(keypath.endpoint, parent) if include_endpoint
|
45
|
+
|
46
|
+
parent
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def get_table(object)
|
52
|
+
if [Symbol, String, Nodes::Stub].include?(object.class)
|
53
|
+
Arel::Table.new(object.to_sym, :engine => @engine)
|
54
|
+
elsif Nodes::Join === object
|
55
|
+
object._klass ? object._klass.arel_table : Arel::Table.new(object._name, :engine => @engine)
|
56
|
+
elsif object.respond_to?(:aliased_table_name)
|
57
|
+
Arel::Table.new(object.table_name, :as => object.aliased_table_name, :engine => @engine)
|
58
|
+
else
|
59
|
+
raise ArgumentError, "Unable to get table for #{object}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
class JoinAssociation < ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
|
7
|
+
|
8
|
+
def initialize(reflection, join_dependency, parent = nil, polymorphic_class = nil)
|
9
|
+
if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
|
10
|
+
swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
|
11
|
+
super(reflection, join_dependency, parent)
|
12
|
+
end
|
13
|
+
else
|
14
|
+
super(reflection, join_dependency, parent)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def swapping_reflection_klass(reflection, klass)
|
19
|
+
reflection = reflection.clone
|
20
|
+
original_polymorphic = reflection.options.delete(:polymorphic)
|
21
|
+
reflection.instance_variable_set(:@klass, klass)
|
22
|
+
yield reflection
|
23
|
+
ensure
|
24
|
+
reflection.options[:polymorphic] = original_polymorphic
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
super && active_record == other.active_record
|
29
|
+
end
|
30
|
+
|
31
|
+
def association_join
|
32
|
+
return @join if @Join
|
33
|
+
|
34
|
+
@join = super
|
35
|
+
|
36
|
+
if reflection.macro == :belongs_to && reflection.options[:polymorphic]
|
37
|
+
aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name,
|
38
|
+
:engine => arel_engine,
|
39
|
+
:columns => klass.columns)
|
40
|
+
|
41
|
+
parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name,
|
42
|
+
:engine => arel_engine,
|
43
|
+
:columns => parent.active_record.columns)
|
44
|
+
|
45
|
+
@join << parent_table[reflection.options[:foreign_type]].eq(reflection.klass.name)
|
46
|
+
end
|
47
|
+
|
48
|
+
@join
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
module JoinDependency
|
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
|
+
def self.included(base)
|
13
|
+
base.class_eval do
|
14
|
+
alias_method_chain :build, :squeel
|
15
|
+
alias_method_chain :graft, :squeel
|
16
|
+
alias :join_parts :joins
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def graft_with_squeel(*associations)
|
21
|
+
associations.each do |association|
|
22
|
+
unless join_associations.detect {|a| association == a}
|
23
|
+
if association.reflection.options[:polymorphic]
|
24
|
+
build(Nodes::Join.new(association.reflection.name, association.join_type, association.reflection.klass),
|
25
|
+
association.find_parent_in(self) || join_base, association.join_type)
|
26
|
+
else
|
27
|
+
build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_type)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def build_with_squeel(associations, parent = nil, join_type = Arel::InnerJoin)
|
35
|
+
associations = associations.symbol if Nodes::Stub === associations
|
36
|
+
|
37
|
+
case associations
|
38
|
+
when Nodes::Join
|
39
|
+
parent ||= @joins.last
|
40
|
+
reflection = parent.reflections[associations._name] or
|
41
|
+
raise ::ActiveRecord::ConfigurationError, "Association named '#{ associations._name }' was not found; perhaps you misspelled it?"
|
42
|
+
|
43
|
+
unless join_association = find_join_association_respecting_polymorphism(reflection, parent, associations._klass)
|
44
|
+
@reflections << reflection
|
45
|
+
join_association = build_join_association_respecting_polymorphism(reflection, parent, associations._klass)
|
46
|
+
join_association.join_type = associations._type
|
47
|
+
@joins << join_association
|
48
|
+
cache_joined_association(join_association)
|
49
|
+
end
|
50
|
+
|
51
|
+
join_association
|
52
|
+
when Nodes::KeyPath
|
53
|
+
parent ||= @joins.last
|
54
|
+
associations.path_with_endpoint.each do |key|
|
55
|
+
parent = build(key, parent, join_type)
|
56
|
+
end
|
57
|
+
parent
|
58
|
+
else
|
59
|
+
build_without_squeel(associations, parent, join_type)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def find_join_association_respecting_polymorphism(reflection, parent, klass)
|
64
|
+
if association = find_join_association(reflection, parent)
|
65
|
+
unless reflection.options[:polymorphic]
|
66
|
+
association
|
67
|
+
else
|
68
|
+
association if association.active_record == klass
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_join_association_respecting_polymorphism(reflection, parent, klass)
|
74
|
+
if reflection.options[:polymorphic] && klass
|
75
|
+
JoinAssociation.new(reflection, self, parent, klass)
|
76
|
+
else
|
77
|
+
JoinAssociation.new(reflection, self, parent)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,327 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
module Relation
|
7
|
+
|
8
|
+
JoinAssociation = ::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
|
9
|
+
JoinDependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency
|
10
|
+
|
11
|
+
attr_writer :join_dependency
|
12
|
+
private :join_dependency=
|
13
|
+
|
14
|
+
# Returns a JoinDependency for the current relation.
|
15
|
+
#
|
16
|
+
# We don't need to clear out @join_dependency by overriding #reset, because
|
17
|
+
# the default #reset already does this, despite never setting it anywhere that
|
18
|
+
# I can find. Serendipity, I say!
|
19
|
+
def join_dependency
|
20
|
+
@join_dependency ||= (build_join_dependency(table, @joins_values) && @join_dependency)
|
21
|
+
end
|
22
|
+
|
23
|
+
def predicate_visitor
|
24
|
+
Visitors::PredicateVisitor.new(
|
25
|
+
Context.new(join_dependency)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def attribute_visitor
|
30
|
+
Visitors::AttributeVisitor.new(
|
31
|
+
Context.new(join_dependency)
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
# We need to be able to support merging two relations that have a different
|
36
|
+
# base class. Stock ActiveRecord doesn't have to do anything too special, because
|
37
|
+
# it's already created predicates out of the where_values by now, and they're
|
38
|
+
# already bound to the proper table.
|
39
|
+
#
|
40
|
+
# Squeel, on the other hand, needs to do its best to ensure the predicates are still
|
41
|
+
# winding up against the proper table. Merging relations is a really nifty shortcut
|
42
|
+
# but another little corner of ActiveRecord where the magic quickly fades. :(
|
43
|
+
def merge(r, association_name = nil)
|
44
|
+
if association_name || relation_with_different_base?(r)
|
45
|
+
r = r.clone
|
46
|
+
association_name ||= infer_association_for_relation_merge(r)
|
47
|
+
prepare_relation_for_association_merge!(r, association_name)
|
48
|
+
self.joins_values += [association_name] if reflect_on_association(association_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
super(r)
|
52
|
+
end
|
53
|
+
|
54
|
+
def relation_with_different_base?(r)
|
55
|
+
::ActiveRecord::Relation === r &&
|
56
|
+
base_class.name != r.klass.base_class.name
|
57
|
+
end
|
58
|
+
|
59
|
+
def infer_association_for_relation_merge(r)
|
60
|
+
default_association = reflect_on_all_associations.detect {|a| a.class_name == r.klass.name}
|
61
|
+
default_association ? default_association.name : r.table_name.to_sym
|
62
|
+
end
|
63
|
+
|
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}
|
67
|
+
r.joins_values.map! {|j| [Symbol, Hash, Nodes::Stub, Nodes::Join].include?(j.class) ? {association_name => j} : j}
|
68
|
+
end
|
69
|
+
|
70
|
+
def build_arel
|
71
|
+
arel = table
|
72
|
+
|
73
|
+
arel = build_join_dependency(arel, @joins_values) unless @joins_values.empty?
|
74
|
+
|
75
|
+
predicate_viz = predicate_visitor
|
76
|
+
attribute_viz = attribute_visitor
|
77
|
+
|
78
|
+
arel = collapse_wheres(arel, predicate_viz.accept((@where_values - ['']).uniq))
|
79
|
+
|
80
|
+
arel = arel.having(*predicate_viz.accept(@having_values.uniq.reject{|h| h.blank?})) unless @having_values.empty?
|
81
|
+
|
82
|
+
arel = arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
|
83
|
+
arel = arel.skip(@offset_value) if @offset_value
|
84
|
+
|
85
|
+
arel = arel.group(*attribute_viz.accept(@group_values.uniq.reject{|g| g.blank?})) unless @group_values.empty?
|
86
|
+
|
87
|
+
arel = arel.order(*attribute_viz.accept(@order_values.uniq.reject{|o| o.blank?})) unless @order_values.empty?
|
88
|
+
|
89
|
+
arel = build_select(arel, attribute_viz.accept(@select_values.uniq))
|
90
|
+
|
91
|
+
arel = arel.from(@from_value) if @from_value
|
92
|
+
arel = arel.lock(@lock_value) if @lock_value
|
93
|
+
|
94
|
+
arel
|
95
|
+
end
|
96
|
+
|
97
|
+
def build_join_dependency(relation, joins)
|
98
|
+
association_joins = []
|
99
|
+
|
100
|
+
joins = joins.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
|
101
|
+
|
102
|
+
joins.each do |join|
|
103
|
+
association_joins << join if [Hash, Array, Symbol, Nodes::Stub, Nodes::Join, Nodes::KeyPath].include?(join.class) && !array_of_strings?(join)
|
104
|
+
end
|
105
|
+
|
106
|
+
stashed_association_joins = joins.grep(::ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
|
107
|
+
|
108
|
+
non_association_joins = (joins - association_joins - stashed_association_joins)
|
109
|
+
custom_joins = custom_join_sql(*non_association_joins)
|
110
|
+
|
111
|
+
self.join_dependency = JoinDependency.new(@klass, association_joins, custom_joins)
|
112
|
+
|
113
|
+
join_dependency.graft(*stashed_association_joins)
|
114
|
+
|
115
|
+
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
|
116
|
+
|
117
|
+
to_join = []
|
118
|
+
|
119
|
+
join_dependency.join_associations.each do |association|
|
120
|
+
if (association_relation = association.relation).is_a?(Array)
|
121
|
+
to_join << [association_relation.first, association.join_type, association.association_join.first]
|
122
|
+
to_join << [association_relation.last, association.join_type, association.association_join.last]
|
123
|
+
else
|
124
|
+
to_join << [association_relation, association.join_type, association.association_join]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
to_join.uniq.each do |left, join_type, right|
|
129
|
+
relation = relation.join(left, join_type).on(*right)
|
130
|
+
end
|
131
|
+
|
132
|
+
relation = relation.join(custom_joins)
|
133
|
+
end
|
134
|
+
|
135
|
+
def includes(*args)
|
136
|
+
if block_given? && args.empty?
|
137
|
+
super(DSL.eval &Proc.new)
|
138
|
+
else
|
139
|
+
super
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def preload(*args)
|
144
|
+
if block_given? && args.empty?
|
145
|
+
super(DSL.eval &Proc.new)
|
146
|
+
else
|
147
|
+
super
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def eager_load(*args)
|
152
|
+
if block_given? && args.empty?
|
153
|
+
super(DSL.eval &Proc.new)
|
154
|
+
else
|
155
|
+
super
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def select(value = Proc.new)
|
160
|
+
if block_given? && Proc === value
|
161
|
+
if value.arity > 0
|
162
|
+
to_a.select {|*block_args| value.call(*block_args)}
|
163
|
+
else
|
164
|
+
relation = clone
|
165
|
+
relation.select_values += Array.wrap(DSL.eval &value)
|
166
|
+
relation
|
167
|
+
end
|
168
|
+
else
|
169
|
+
super
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def group(*args)
|
174
|
+
if block_given? && args.empty?
|
175
|
+
super(DSL.eval &Proc.new)
|
176
|
+
else
|
177
|
+
super
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def order(*args)
|
182
|
+
if block_given? && args.empty?
|
183
|
+
super(DSL.eval &Proc.new)
|
184
|
+
else
|
185
|
+
super
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def reorder(*args)
|
190
|
+
if block_given? && args.empty?
|
191
|
+
super(DSL.eval &Proc.new)
|
192
|
+
else
|
193
|
+
super
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def joins(*args)
|
198
|
+
if block_given? && args.empty?
|
199
|
+
super(DSL.eval &Proc.new)
|
200
|
+
else
|
201
|
+
super
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def where(opts = Proc.new, *rest)
|
206
|
+
if block_given? && Proc === opts
|
207
|
+
super(DSL.eval &opts)
|
208
|
+
else
|
209
|
+
super
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def having(*args)
|
214
|
+
if block_given? && args.empty?
|
215
|
+
super(DSL.eval &Proc.new)
|
216
|
+
else
|
217
|
+
super
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def build_where(opts, other = [])
|
222
|
+
case opts
|
223
|
+
when String, Array
|
224
|
+
super
|
225
|
+
else # Let's prevent PredicateBuilder from doing its thing
|
226
|
+
[opts, *other].map do |arg|
|
227
|
+
case arg
|
228
|
+
when Array # Just in case there's an array in there somewhere
|
229
|
+
@klass.send(:sanitize_sql, arg)
|
230
|
+
when Hash
|
231
|
+
@klass.send(:expand_hash_conditions_for_aggregates, arg)
|
232
|
+
else
|
233
|
+
arg
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def collapse_wheres(arel, wheres)
|
240
|
+
wheres = [wheres] unless Array === wheres
|
241
|
+
binaries = wheres.grep(Arel::Nodes::Binary)
|
242
|
+
|
243
|
+
groups = binaries.group_by {|b| [b.class, b.left]}
|
244
|
+
|
245
|
+
groups.each do |_, bins|
|
246
|
+
arel = arel.where(bins.inject(&:and))
|
247
|
+
end
|
248
|
+
|
249
|
+
(wheres - binaries).each do |where|
|
250
|
+
where = Arel.sql(where) if String === where
|
251
|
+
arel = arel.where(Arel::Nodes::Grouping.new(where))
|
252
|
+
end
|
253
|
+
|
254
|
+
arel
|
255
|
+
end
|
256
|
+
|
257
|
+
def find_equality_predicates(nodes)
|
258
|
+
nodes.map { |node|
|
259
|
+
case node
|
260
|
+
when Arel::Nodes::Equality
|
261
|
+
node if node.left.relation.name == table_name
|
262
|
+
when Arel::Nodes::Grouping
|
263
|
+
find_equality_predicates([node.expr])
|
264
|
+
when Arel::Nodes::And
|
265
|
+
find_equality_predicates(node.children)
|
266
|
+
else
|
267
|
+
nil
|
268
|
+
end
|
269
|
+
}.compact.flatten
|
270
|
+
end
|
271
|
+
|
272
|
+
# Simulate the logic that occurs in #to_a
|
273
|
+
#
|
274
|
+
# This will let us get a dump of the SQL that will be run against the
|
275
|
+
# DB for debug purposes without actually running the query.
|
276
|
+
def debug_sql
|
277
|
+
if eager_loading?
|
278
|
+
including = (@eager_load_values + @includes_values).uniq
|
279
|
+
join_dependency = JoinDependency.new(@klass, including, nil)
|
280
|
+
construct_relation_for_association_find(join_dependency).to_sql
|
281
|
+
else
|
282
|
+
arel.to_sql
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
### ZOMG ALIAS_METHOD_CHAIN IS BELOW. HIDE YOUR EYES!
|
287
|
+
# ...
|
288
|
+
# ...
|
289
|
+
# ...
|
290
|
+
# Since you're still looking, let me explain this horrible
|
291
|
+
# transgression you see before you.
|
292
|
+
# You see, Relation#where_values_hash is defined on the
|
293
|
+
# ActiveRecord::Relation class. Since it's defined there, but
|
294
|
+
# I would very much like to modify its behavior, I have three
|
295
|
+
# choices.
|
296
|
+
#
|
297
|
+
# 1. Inherit from ActiveRecord::Relation in a Squeel::Relation
|
298
|
+
# class, and make an attempt to usurp all of the various calls
|
299
|
+
# to methods on ActiveRecord::Relation by doing some really
|
300
|
+
# evil stuff with constant reassignment, all for the sake of
|
301
|
+
# being able to use super().
|
302
|
+
#
|
303
|
+
# 2. Submit a patch to Rails core, breaking this method off into
|
304
|
+
# another module, all for my own selfish desire to use super()
|
305
|
+
# while mucking about in Rails internals.
|
306
|
+
#
|
307
|
+
# 3. Use alias_method_chain, and say 10 hail Hanssons as penance.
|
308
|
+
#
|
309
|
+
# I opted to go with #3. Except for the hail Hansson thing.
|
310
|
+
# Unless you're DHH, in which case, I totally said them.
|
311
|
+
|
312
|
+
def self.included(base)
|
313
|
+
base.class_eval do
|
314
|
+
alias_method_chain :where_values_hash, :squeel
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def where_values_hash_with_squeel
|
319
|
+
equalities = find_equality_predicates(predicate_visitor.accept(@where_values))
|
320
|
+
|
321
|
+
Hash[equalities.map { |where| [where.left.name, where.right] }]
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'squeel/context'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
class Context < ::Squeel::Context
|
7
|
+
# Because the AR::Associations namespace is insane
|
8
|
+
JoinPart = ::ActiveRecord::Associations::JoinDependency::JoinPart
|
9
|
+
|
10
|
+
def initialize(object)
|
11
|
+
super
|
12
|
+
@base = object.join_base
|
13
|
+
@engine = @base.arel_engine
|
14
|
+
@arel_visitor = Arel::Visitors.visitor_for @engine
|
15
|
+
@default_table = Arel::Table.new(@base.table_name, :as => @base.aliased_table_name, :engine => @engine)
|
16
|
+
end
|
17
|
+
|
18
|
+
def find(object, parent = @base)
|
19
|
+
if JoinPart === parent
|
20
|
+
object = object.to_sym if String === object
|
21
|
+
case object
|
22
|
+
when Symbol, Nodes::Stub
|
23
|
+
@object.join_associations.detect { |j|
|
24
|
+
j.reflection.name == object.to_sym && j.parent == parent
|
25
|
+
}
|
26
|
+
when Nodes::Join
|
27
|
+
@object.join_associations.detect { |j|
|
28
|
+
j.reflection.name == object.name && j.parent == parent &&
|
29
|
+
(object.polymorphic? ? j.reflection.klass == object._klass : true)
|
30
|
+
}
|
31
|
+
else
|
32
|
+
@object.join_associations.detect { |j|
|
33
|
+
j.reflection == object && j.parent == parent
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def traverse(keypath, parent = @base, include_endpoint = false)
|
40
|
+
parent = @base if keypath.absolute?
|
41
|
+
keypath.path.each do |key|
|
42
|
+
parent = find(key, parent) || key
|
43
|
+
end
|
44
|
+
parent = find(keypath.endpoint, parent) if include_endpoint
|
45
|
+
|
46
|
+
parent
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def get_table(object)
|
52
|
+
if [Symbol, String, Nodes::Stub].include?(object.class)
|
53
|
+
Arel::Table.new(object.to_sym, :engine => @engine)
|
54
|
+
elsif Nodes::Join === object
|
55
|
+
object._klass ? object._klass.arel_table : Arel::Table.new(object._name, :engine => @engine)
|
56
|
+
elsif object.respond_to?(:aliased_table_name)
|
57
|
+
Arel::Table.new(object.table_name, :as => object.aliased_table_name, :engine => @engine)
|
58
|
+
else
|
59
|
+
raise ArgumentError, "Unable to get table for #{object}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
class JoinAssociation < ::ActiveRecord::Associations::JoinDependency::JoinAssociation
|
7
|
+
|
8
|
+
def initialize(reflection, join_dependency, parent = nil, polymorphic_class = nil)
|
9
|
+
if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
|
10
|
+
swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
|
11
|
+
super(reflection, join_dependency, parent)
|
12
|
+
end
|
13
|
+
else
|
14
|
+
super(reflection, join_dependency, parent)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def swapping_reflection_klass(reflection, klass)
|
19
|
+
reflection = reflection.clone
|
20
|
+
original_polymorphic = reflection.options.delete(:polymorphic)
|
21
|
+
reflection.instance_variable_set(:@klass, klass)
|
22
|
+
yield reflection
|
23
|
+
ensure
|
24
|
+
reflection.options[:polymorphic] = original_polymorphic
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
super && active_record == other.active_record
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_constraint(reflection, table, key, foreign_table, foreign_key)
|
32
|
+
if reflection.options[:polymorphic]
|
33
|
+
super.and(
|
34
|
+
foreign_table[reflection.foreign_type].eq(reflection.klass.name)
|
35
|
+
)
|
36
|
+
else
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|