squeel 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.rdoc +41 -0
- data/Rakefile +19 -0
- data/lib/core_ext/hash.rb +13 -0
- data/lib/core_ext/symbol.rb +36 -0
- data/lib/squeel.rb +26 -0
- data/lib/squeel/adapters/active_record.rb +6 -0
- data/lib/squeel/adapters/active_record/join_association.rb +90 -0
- data/lib/squeel/adapters/active_record/join_dependency.rb +68 -0
- data/lib/squeel/adapters/active_record/relation.rb +292 -0
- data/lib/squeel/configuration.rb +25 -0
- data/lib/squeel/constants.rb +23 -0
- data/lib/squeel/contexts/join_dependency_context.rb +74 -0
- data/lib/squeel/dsl.rb +31 -0
- data/lib/squeel/nodes.rb +10 -0
- data/lib/squeel/nodes/and.rb +8 -0
- data/lib/squeel/nodes/binary.rb +23 -0
- data/lib/squeel/nodes/function.rb +84 -0
- data/lib/squeel/nodes/join.rb +51 -0
- data/lib/squeel/nodes/key_path.rb +127 -0
- data/lib/squeel/nodes/nary.rb +35 -0
- data/lib/squeel/nodes/not.rb +8 -0
- data/lib/squeel/nodes/operation.rb +23 -0
- data/lib/squeel/nodes/operators.rb +27 -0
- data/lib/squeel/nodes/or.rb +8 -0
- data/lib/squeel/nodes/order.rb +35 -0
- data/lib/squeel/nodes/predicate.rb +49 -0
- data/lib/squeel/nodes/predicate_operators.rb +17 -0
- data/lib/squeel/nodes/stub.rb +113 -0
- data/lib/squeel/nodes/unary.rb +22 -0
- data/lib/squeel/predicate_methods.rb +22 -0
- data/lib/squeel/predicate_methods/function.rb +9 -0
- data/lib/squeel/predicate_methods/predicate.rb +11 -0
- data/lib/squeel/predicate_methods/stub.rb +9 -0
- data/lib/squeel/predicate_methods/symbol.rb +9 -0
- data/lib/squeel/version.rb +3 -0
- data/lib/squeel/visitors.rb +3 -0
- data/lib/squeel/visitors/base.rb +46 -0
- data/lib/squeel/visitors/order_visitor.rb +107 -0
- data/lib/squeel/visitors/predicate_visitor.rb +179 -0
- data/lib/squeel/visitors/select_visitor.rb +103 -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 +68 -0
- data/spec/helpers/squeel_helper.rb +5 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/squeel/adapters/active_record/join_association_spec.rb +18 -0
- data/spec/squeel/adapters/active_record/join_depdendency_spec.rb +60 -0
- data/spec/squeel/adapters/active_record/relation_spec.rb +437 -0
- data/spec/squeel/contexts/join_dependency_context_spec.rb +43 -0
- data/spec/squeel/dsl_spec.rb +73 -0
- data/spec/squeel/nodes/function_spec.rb +149 -0
- data/spec/squeel/nodes/join_spec.rb +27 -0
- data/spec/squeel/nodes/key_path_spec.rb +92 -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 +92 -0
- data/spec/squeel/nodes/stub_spec.rb +178 -0
- data/spec/squeel/visitors/order_visitor_spec.rb +128 -0
- data/spec/squeel/visitors/predicate_visitor_spec.rb +267 -0
- data/spec/squeel/visitors/select_visitor_spec.rb +115 -0
- data/spec/support/schema.rb +101 -0
- data/squeel.gemspec +44 -0
- metadata +221 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010-2011 Ernie Miller
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
=Squeel
|
2
|
+
|
3
|
+
This is a complete rewrite of the library formerly called MetaWhere. It's Rails 3.1-only
|
4
|
+
for now.
|
5
|
+
|
6
|
+
It's not really suitable for actual use yet, but you're welcome to test it and send
|
7
|
+
me feedback.
|
8
|
+
|
9
|
+
==What's new?
|
10
|
+
|
11
|
+
A lot.
|
12
|
+
|
13
|
+
* Symbol and hash methods aren't loaded by default. To enable them, do this in
|
14
|
+
your Squeel.configure block: <tt>config.load_core_extensions :hash, :symbol</tt>
|
15
|
+
* Speaking of, you can call Squeel.configure do |config| ... end and do
|
16
|
+
another bit of configuration, setting up your own aliases.
|
17
|
+
<tt>config.alias_predicate :new_name, :old_name</tt>
|
18
|
+
* The preferred way to use the various enhancements is now by passing a block to
|
19
|
+
the relation method you're calling. For example:
|
20
|
+
|
21
|
+
Person.select{max(id).as(max_id)} # Call SQL functions
|
22
|
+
Person.where{(name == 'bob') & (salary == 100000)} # Compounds & and | work
|
23
|
+
|
24
|
+
* Operators have changed. As before, operators starting with ! are only available
|
25
|
+
on Ruby 1.9. Upgrade, for the love of all that is good and holy.
|
26
|
+
|
27
|
+
* == - Equality
|
28
|
+
* != - Inequality
|
29
|
+
* ^ - Inequality, for those poor souls on 1.8.x
|
30
|
+
* >> - In, (mnemonic: value >> [1,2,3], the value is running INTO the array)
|
31
|
+
* << - Not in, (mnemonic: value << [1,2,3], the value is running OUT of the array)
|
32
|
+
* =~ - Matches (SQL LIKE)
|
33
|
+
* !~ - Not matches (SQL NOT LIKE) Again, only in Ruby 1.9
|
34
|
+
* > - Greater than
|
35
|
+
* >= - Greater than or equal to
|
36
|
+
* < - Less than
|
37
|
+
* <= - Less than or equal to
|
38
|
+
* [] - Alternative function syntax. Just use parentheses, not sure I'm gonna keep [].
|
39
|
+
|
40
|
+
There's more -- have a read through the specs for a better idea of what you can do,
|
41
|
+
or clone the repo, run <tt>bundle install</tt> and play around in <tt>rake console</tt>.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
|
4
|
+
Bundler::GemHelper.install_tasks
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |rspec|
|
7
|
+
rspec.rspec_opts = ['--backtrace']
|
8
|
+
end
|
9
|
+
|
10
|
+
task :default => :spec
|
11
|
+
|
12
|
+
desc "Open an irb session with Squeel and the sample data used in specs"
|
13
|
+
task :console do
|
14
|
+
require 'irb'
|
15
|
+
require 'irb/completion'
|
16
|
+
require 'console'
|
17
|
+
ARGV.clear
|
18
|
+
IRB.start
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'squeel/nodes/predicate_operators'
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
# Hashes are "acceptable" by PredicateVisitor, so they
|
5
|
+
# can be treated like nodes for the purposes of and/or/not
|
6
|
+
# if you load core extensions with:
|
7
|
+
#
|
8
|
+
# Squeel.configure do |config|
|
9
|
+
# config.load_core_extensions :hash
|
10
|
+
# end
|
11
|
+
|
12
|
+
include Squeel::Nodes::PredicateOperators
|
13
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'squeel/predicate_methods'
|
2
|
+
|
3
|
+
class Symbol
|
4
|
+
# These extensions to Symbol are loaded optionally, with:
|
5
|
+
#
|
6
|
+
# Squeel.configure do |config|
|
7
|
+
# config.load_core_extensions :symbol
|
8
|
+
# end
|
9
|
+
|
10
|
+
include Squeel::PredicateMethods
|
11
|
+
|
12
|
+
def asc
|
13
|
+
Squeel::Nodes::Order.new self, 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def desc
|
17
|
+
Squeel::Nodes::Order.new self, -1
|
18
|
+
end
|
19
|
+
|
20
|
+
def func(*args)
|
21
|
+
Squeel::Nodes::Function.new(self, args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def inner
|
25
|
+
Squeel::Nodes::Join.new(self, Arel::InnerJoin)
|
26
|
+
end
|
27
|
+
|
28
|
+
def outer
|
29
|
+
Squeel::Nodes::Join.new(self, Arel::OuterJoin)
|
30
|
+
end
|
31
|
+
|
32
|
+
def of_class(klass)
|
33
|
+
Squeel::Nodes::Join.new(self, Arel::InnerJoin, klass)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/lib/squeel.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'squeel/configuration'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
|
5
|
+
extend Configuration
|
6
|
+
|
7
|
+
def self.evil_things
|
8
|
+
original_verbosity = $VERBOSE
|
9
|
+
$VERBOSE = nil
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
$VERBOSE = original_verbosity
|
13
|
+
end
|
14
|
+
|
15
|
+
Constants::PREDICATE_ALIASES.each do |original, aliases|
|
16
|
+
aliases.each do |aliaz|
|
17
|
+
alias_predicate aliaz, original
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'squeel/nodes'
|
24
|
+
require 'squeel/dsl'
|
25
|
+
require 'squeel/visitors'
|
26
|
+
require 'squeel/adapters/active_record'
|
@@ -0,0 +1,6 @@
|
|
1
|
+
require 'squeel/adapters/active_record/relation'
|
2
|
+
require 'squeel/adapters/active_record/join_dependency'
|
3
|
+
require 'squeel/adapters/active_record/join_association'
|
4
|
+
|
5
|
+
ActiveRecord::Relation.send :include, Squeel::Adapters::ActiveRecord::Relation
|
6
|
+
ActiveRecord::Associations::JoinDependency.send :include, Squeel::Adapters::ActiveRecord::JoinDependency
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
|
7
|
+
class JoinAssociation < ::ActiveRecord::Associations::JoinDependency::JoinAssociation
|
8
|
+
|
9
|
+
def initialize(reflection, join_dependency, parent = nil, polymorphic_class = nil)
|
10
|
+
if polymorphic_class && ::ActiveRecord::Base > polymorphic_class
|
11
|
+
swapping_reflection_klass(reflection, polymorphic_class) do |reflection|
|
12
|
+
super(reflection, join_dependency, parent)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
super(reflection, join_dependency, parent)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def swapping_reflection_klass(reflection, klass)
|
20
|
+
reflection = reflection.clone
|
21
|
+
original_polymorphic = reflection.options.delete(:polymorphic)
|
22
|
+
reflection.instance_variable_set(:@klass, klass)
|
23
|
+
yield reflection
|
24
|
+
ensure
|
25
|
+
reflection.options[:polymorphic] = original_polymorphic
|
26
|
+
end
|
27
|
+
|
28
|
+
def join_to(relation)
|
29
|
+
tables = @tables.dup
|
30
|
+
foreign_table = parent_table
|
31
|
+
|
32
|
+
# The chain starts with the target table, but we want to end with it here (makes
|
33
|
+
# more sense in this context), so we reverse
|
34
|
+
chain.reverse.each_with_index do |reflection, i|
|
35
|
+
table = tables.shift
|
36
|
+
|
37
|
+
case reflection.source_macro
|
38
|
+
when :belongs_to
|
39
|
+
key = reflection.association_primary_key
|
40
|
+
foreign_key = reflection.foreign_key
|
41
|
+
when :has_and_belongs_to_many
|
42
|
+
# Join the join table first...
|
43
|
+
relation.from(join(
|
44
|
+
table,
|
45
|
+
table[reflection.foreign_key].
|
46
|
+
eq(foreign_table[reflection.active_record_primary_key])
|
47
|
+
))
|
48
|
+
|
49
|
+
foreign_table, table = table, tables.shift
|
50
|
+
|
51
|
+
key = reflection.association_primary_key
|
52
|
+
foreign_key = reflection.association_foreign_key
|
53
|
+
else
|
54
|
+
key = reflection.foreign_key
|
55
|
+
foreign_key = reflection.active_record_primary_key
|
56
|
+
end
|
57
|
+
|
58
|
+
constraint = table[key].eq(foreign_table[foreign_key])
|
59
|
+
|
60
|
+
if reflection.options[:polymorphic]
|
61
|
+
constraint = constraint.and(
|
62
|
+
foreign_table[reflection.foreign_type].eq(reflection.klass.name)
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
if reflection.klass.finder_needs_type_condition?
|
67
|
+
constraint = table.create_and([
|
68
|
+
constraint,
|
69
|
+
reflection.klass.send(:type_condition, table)
|
70
|
+
])
|
71
|
+
end
|
72
|
+
|
73
|
+
relation.from(join(table, constraint))
|
74
|
+
|
75
|
+
unless conditions[i].empty?
|
76
|
+
relation.where(sanitize(conditions[i], table))
|
77
|
+
end
|
78
|
+
|
79
|
+
# The current table in this iteration becomes the foreign table in the next
|
80
|
+
foreign_table = table
|
81
|
+
end
|
82
|
+
|
83
|
+
relation
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,68 @@
|
|
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
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_with_squeel(associations, parent = nil, join_type = Arel::InnerJoin)
|
19
|
+
associations = associations.symbol if Nodes::Stub === associations
|
20
|
+
|
21
|
+
case associations
|
22
|
+
when Nodes::Join
|
23
|
+
parent ||= join_parts.last
|
24
|
+
reflection = parent.reflections[associations.name] or
|
25
|
+
raise ::ActiveRecord::ConfigurationError, "Association named '#{ associations.name }' was not found; perhaps you misspelled it?"
|
26
|
+
|
27
|
+
unless join_association = find_join_association_respecting_polymorphism(reflection, parent, associations)
|
28
|
+
@reflections << reflection
|
29
|
+
join_association = build_join_association_respecting_polymorphism(reflection, parent, associations)
|
30
|
+
join_association.join_type = associations.type
|
31
|
+
@join_parts << join_association
|
32
|
+
cache_joined_association(join_association)
|
33
|
+
end
|
34
|
+
|
35
|
+
join_association
|
36
|
+
when Nodes::KeyPath
|
37
|
+
parent ||= join_parts.last
|
38
|
+
associations.path_with_endpoint.each do |key|
|
39
|
+
parent = build(key, parent, join_type)
|
40
|
+
end
|
41
|
+
parent
|
42
|
+
else
|
43
|
+
build_without_squeel(associations, parent, join_type)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_join_association_respecting_polymorphism(reflection, parent, join)
|
48
|
+
if association = find_join_association(reflection, parent)
|
49
|
+
unless reflection.options[:polymorphic]
|
50
|
+
association
|
51
|
+
else
|
52
|
+
association if association.active_record == join.klass
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def build_join_association_respecting_polymorphism(reflection, parent, join)
|
58
|
+
if reflection.options[:polymorphic] && join.polymorphic?
|
59
|
+
JoinAssociation.new(reflection, self, parent, join.klass)
|
60
|
+
else
|
61
|
+
JoinAssociation.new(reflection, self, parent)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,292 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module Squeel
|
4
|
+
module Adapters
|
5
|
+
module ActiveRecord
|
6
|
+
module Relation
|
7
|
+
|
8
|
+
JoinAssociation = ::ActiveRecord::Associations::JoinDependency::JoinAssociation
|
9
|
+
JoinDependency = ::ActiveRecord::Associations::JoinDependency
|
10
|
+
|
11
|
+
attr_writer :join_dependency
|
12
|
+
private :join_dependency=
|
13
|
+
|
14
|
+
def join_dependency
|
15
|
+
@join_dependency ||= (build_join_dependency(table.from(table), @joins_values) && @join_dependency)
|
16
|
+
end
|
17
|
+
|
18
|
+
def select_visitor
|
19
|
+
Visitors::SelectVisitor.new(
|
20
|
+
Contexts::JoinDependencyContext.new(join_dependency)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def predicate_visitor
|
25
|
+
Visitors::PredicateVisitor.new(
|
26
|
+
Contexts::JoinDependencyContext.new(join_dependency)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def order_visitor
|
31
|
+
Visitors::OrderVisitor.new(
|
32
|
+
Contexts::JoinDependencyContext.new(join_dependency)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def merge(r, association_name = nil)
|
37
|
+
if association_name || relation_with_different_base?(r)
|
38
|
+
r = r.clone
|
39
|
+
association_name ||= infer_association_for_relation_merge(r)
|
40
|
+
prepare_relation_for_association_merge!(r, association_name)
|
41
|
+
self.joins_values += [association_name] if reflect_on_association(association_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
super(r)
|
45
|
+
end
|
46
|
+
|
47
|
+
def relation_with_different_base?(r)
|
48
|
+
::ActiveRecord::Relation === r &&
|
49
|
+
base_class.name != r.klass.base_class.name
|
50
|
+
end
|
51
|
+
|
52
|
+
def infer_association_for_relation_merge(r)
|
53
|
+
default_association = reflect_on_all_associations.detect {|a| a.class_name == r.klass.name}
|
54
|
+
default_association ? default_association.name : r.table_name.to_sym
|
55
|
+
end
|
56
|
+
|
57
|
+
def prepare_relation_for_association_merge!(r, association_name)
|
58
|
+
r.where_values.map! {|w| Squeel::Visitors::PredicateVisitor.can_accept?(w) ? {association_name => w} : w}
|
59
|
+
r.having_values.map! {|h| Squeel::Visitors::PredicateVisitor.can_accept?(h) ? {association_name => h} : h}
|
60
|
+
r.joins_values.map! {|j| [Symbol, Hash, Nodes::Stub, Nodes::Join].include?(j.class) ? {association_name => j} : j}
|
61
|
+
end
|
62
|
+
|
63
|
+
def build_arel
|
64
|
+
arel = table.from table
|
65
|
+
|
66
|
+
build_join_dependency(arel, @joins_values) unless @joins_values.empty?
|
67
|
+
|
68
|
+
predicate_viz = predicate_visitor
|
69
|
+
|
70
|
+
collapse_wheres(arel, predicate_viz.accept((@where_values - ['']).uniq))
|
71
|
+
|
72
|
+
arel.having(*predicate_viz.accept(@having_values.uniq.reject{|h| h.blank?})) unless @having_values.empty?
|
73
|
+
|
74
|
+
arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
|
75
|
+
arel.skip(@offset_value) if @offset_value
|
76
|
+
|
77
|
+
arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
|
78
|
+
|
79
|
+
unless @order_values.empty?
|
80
|
+
order_viz = order_visitor
|
81
|
+
arel.order(*order_viz.accept(@order_values.uniq.reject{|o| o.blank?}))
|
82
|
+
end
|
83
|
+
|
84
|
+
build_select(arel, select_visitor.accept(@select_values.uniq))
|
85
|
+
|
86
|
+
arel.from(@from_value) if @from_value
|
87
|
+
arel.lock(@lock_value) if @lock_value
|
88
|
+
|
89
|
+
arel
|
90
|
+
end
|
91
|
+
|
92
|
+
def build_join_dependency(manager, joins)
|
93
|
+
buckets = joins.group_by do |join|
|
94
|
+
case join
|
95
|
+
when String
|
96
|
+
'string_join'
|
97
|
+
when Hash, Symbol, Array, Nodes::Stub, Nodes::Join, Nodes::KeyPath
|
98
|
+
'association_join'
|
99
|
+
when JoinAssociation
|
100
|
+
'stashed_join'
|
101
|
+
when Arel::Nodes::Join
|
102
|
+
'join_node'
|
103
|
+
else
|
104
|
+
raise 'unknown class: %s' % join.class.name
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
association_joins = buckets['association_join'] || []
|
109
|
+
stashed_association_joins = buckets['stashed_join'] || []
|
110
|
+
join_nodes = buckets['join_node'] || []
|
111
|
+
string_joins = (buckets['string_join'] || []).map { |x|
|
112
|
+
x.strip
|
113
|
+
}.uniq
|
114
|
+
|
115
|
+
join_list = custom_join_ast(manager, string_joins)
|
116
|
+
|
117
|
+
# All of this duplication just to add
|
118
|
+
self.join_dependency = JoinDependency.new(
|
119
|
+
@klass,
|
120
|
+
association_joins,
|
121
|
+
join_list
|
122
|
+
)
|
123
|
+
|
124
|
+
join_nodes.each do |join|
|
125
|
+
join_dependency.alias_tracker.aliased_name_for(join.left.name.downcase)
|
126
|
+
end
|
127
|
+
|
128
|
+
join_dependency.graft(*stashed_association_joins)
|
129
|
+
|
130
|
+
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
|
131
|
+
|
132
|
+
join_dependency.join_associations.each do |association|
|
133
|
+
association.join_to(manager)
|
134
|
+
end
|
135
|
+
|
136
|
+
manager.join_sources.concat join_nodes.uniq
|
137
|
+
manager.join_sources.concat join_list
|
138
|
+
|
139
|
+
manager
|
140
|
+
end
|
141
|
+
|
142
|
+
def select(value = Proc.new)
|
143
|
+
if block_given? && Proc === value
|
144
|
+
if value.arity > 0
|
145
|
+
to_a.select {|*block_args| value.call(*block_args)}
|
146
|
+
else
|
147
|
+
relation = clone
|
148
|
+
relation.select_values += Array.wrap(DSL.evaluate &value)
|
149
|
+
relation
|
150
|
+
end
|
151
|
+
else
|
152
|
+
super
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def where(opts = Proc.new, *rest)
|
157
|
+
if block_given? && Proc === opts
|
158
|
+
super(DSL.evaluate &opts)
|
159
|
+
else
|
160
|
+
super
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def build_where(opts, other = [])
|
165
|
+
case opts
|
166
|
+
when String, Array
|
167
|
+
super
|
168
|
+
else # Let's prevent PredicateBuilder from doing its thing
|
169
|
+
[opts, *other].map do |arg|
|
170
|
+
case arg
|
171
|
+
when Array # Just in case there's an array in there somewhere
|
172
|
+
@klass.send(:sanitize_sql, arg)
|
173
|
+
when Hash
|
174
|
+
@klass.send(:expand_hash_conditions_for_aggregates, arg)
|
175
|
+
else
|
176
|
+
arg
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def order(*args)
|
183
|
+
if block_given? && args.empty?
|
184
|
+
super(DSL.evaluate &Proc.new)
|
185
|
+
else
|
186
|
+
super
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def joins(*args)
|
191
|
+
if block_given? && args.empty?
|
192
|
+
super(DSL.evaluate &Proc.new)
|
193
|
+
else
|
194
|
+
super
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def having(*args)
|
199
|
+
if block_given? && args.empty?
|
200
|
+
super(DSL.evaluate &Proc.new)
|
201
|
+
else
|
202
|
+
super
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def collapse_wheres(arel, wheres)
|
207
|
+
wheres = [wheres] unless Array === wheres
|
208
|
+
binaries = wheres.grep(Arel::Nodes::Binary)
|
209
|
+
|
210
|
+
groups = binaries.group_by {|b| [b.class, b.left]}
|
211
|
+
|
212
|
+
groups.each do |_, bins|
|
213
|
+
arel.where(Arel::Nodes::And.new(bins))
|
214
|
+
end
|
215
|
+
|
216
|
+
(wheres - binaries).each do |where|
|
217
|
+
where = Arel.sql(where) if String === where
|
218
|
+
arel.where(Arel::Nodes::Grouping.new(where))
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def find_equality_predicates(nodes)
|
223
|
+
nodes.map { |node|
|
224
|
+
case node
|
225
|
+
when Arel::Nodes::Equality
|
226
|
+
node
|
227
|
+
when Arel::Nodes::Grouping
|
228
|
+
find_equality_predicates([node.expr])
|
229
|
+
when Arel::Nodes::And
|
230
|
+
find_equality_predicates(node.children)
|
231
|
+
else
|
232
|
+
nil
|
233
|
+
end
|
234
|
+
}.compact.flatten
|
235
|
+
end
|
236
|
+
|
237
|
+
# Simulate the logic that occurs in #to_a
|
238
|
+
#
|
239
|
+
# This will let us get a dump of the SQL that will be run against the
|
240
|
+
# DB for debug purposes without actually running the query.
|
241
|
+
def debug_sql
|
242
|
+
if eager_loading?
|
243
|
+
including = (@eager_load_values + @includes_values).uniq
|
244
|
+
join_dependency = JoinDependency.new(@klass, including, [])
|
245
|
+
construct_relation_for_association_find(join_dependency).to_sql
|
246
|
+
else
|
247
|
+
arel.to_sql
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
### ZOMG ALIAS_METHOD_CHAIN IS BELOW. HIDE YOUR EYES!
|
252
|
+
# ...
|
253
|
+
# ...
|
254
|
+
# ...
|
255
|
+
# Since you're still looking, let me explain this horrible
|
256
|
+
# transgression you see before you.
|
257
|
+
# You see, Relation#where_values_hash is defined on the
|
258
|
+
# ActiveRecord::Relation class. Since it's defined there, but
|
259
|
+
# I would very much like to modify its behavior, I have three
|
260
|
+
# choices.
|
261
|
+
#
|
262
|
+
# 1. Inherit from ActiveRecord::Relation in a Squeel::Relation
|
263
|
+
# class, and make an attempt to usurp all of the various calls
|
264
|
+
# to methods on ActiveRecord::Relation by doing some really
|
265
|
+
# evil stuff with constant reassignment, all for the sake of
|
266
|
+
# being able to use super().
|
267
|
+
#
|
268
|
+
# 2. Submit a patch to Rails core, breaking this method off into
|
269
|
+
# another module, all for my own selfish desire to use super()
|
270
|
+
# while mucking about in Rails internals.
|
271
|
+
#
|
272
|
+
# 3. Use alias_method_chain, and say 10 hail Hanssons as penance.
|
273
|
+
#
|
274
|
+
# I opted to go with #3. Except for the hail Hansson thing.
|
275
|
+
# Unless you're DHH, in which case, I totally said them.
|
276
|
+
|
277
|
+
def self.included(base)
|
278
|
+
base.class_eval do
|
279
|
+
alias_method_chain :where_values_hash, :squeel
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def where_values_hash_with_squeel
|
284
|
+
equalities = find_equality_predicates(predicate_visitor.accept(@where_values))
|
285
|
+
|
286
|
+
Hash[equalities.map { |where| [where.left.name, where.right] }]
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|