sayso-meta_where 1.0.4.001
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/CHANGELOG +90 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.rdoc +346 -0
- data/Rakefile +11 -0
- data/lib/core_ext/hash.rb +5 -0
- data/lib/core_ext/symbol.rb +39 -0
- data/lib/core_ext/symbol_operators.rb +48 -0
- data/lib/meta_where/association_reflection.rb +51 -0
- data/lib/meta_where/column.rb +31 -0
- data/lib/meta_where/compound.rb +20 -0
- data/lib/meta_where/condition.rb +32 -0
- data/lib/meta_where/condition_operators.rb +19 -0
- data/lib/meta_where/function.rb +108 -0
- data/lib/meta_where/join_dependency.rb +101 -0
- data/lib/meta_where/join_type.rb +43 -0
- data/lib/meta_where/not.rb +13 -0
- data/lib/meta_where/relation.rb +296 -0
- data/lib/meta_where/utility.rb +51 -0
- data/lib/meta_where/version.rb +3 -0
- data/lib/meta_where/visitors/attribute.rb +58 -0
- data/lib/meta_where/visitors/predicate.rb +149 -0
- data/lib/meta_where/visitors/visitor.rb +47 -0
- data/lib/meta_where.rb +51 -0
- data/meta_where.gemspec +48 -0
- data/test/fixtures/companies.yml +17 -0
- data/test/fixtures/company.rb +7 -0
- data/test/fixtures/data_type.rb +3 -0
- data/test/fixtures/data_types.yml +15 -0
- data/test/fixtures/developer.rb +7 -0
- data/test/fixtures/developers.yml +55 -0
- data/test/fixtures/developers_projects.yml +25 -0
- data/test/fixtures/fixed_bid_project.rb +2 -0
- data/test/fixtures/invalid_company.rb +4 -0
- data/test/fixtures/invalid_developer.rb +4 -0
- data/test/fixtures/note.rb +3 -0
- data/test/fixtures/notes.yml +95 -0
- data/test/fixtures/people.yml +14 -0
- data/test/fixtures/person.rb +4 -0
- data/test/fixtures/project.rb +7 -0
- data/test/fixtures/projects.yml +29 -0
- data/test/fixtures/schema.rb +53 -0
- data/test/fixtures/time_and_materials_project.rb +2 -0
- data/test/helper.rb +33 -0
- data/test/test_base.rb +21 -0
- data/test/test_relations.rb +476 -0
- metadata +184 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
class Column
|
3
|
+
attr_reader :column, :method
|
4
|
+
|
5
|
+
def initialize(column, method)
|
6
|
+
@column = column
|
7
|
+
@method = method
|
8
|
+
end
|
9
|
+
|
10
|
+
def %(value)
|
11
|
+
MetaWhere::Condition.new(column, value, method)
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other_column)
|
15
|
+
other_column.is_a?(Column) &&
|
16
|
+
other_column.column == column &&
|
17
|
+
other_column.method == method
|
18
|
+
end
|
19
|
+
|
20
|
+
alias_method :eql?, :==
|
21
|
+
|
22
|
+
def hash
|
23
|
+
[column, method].hash
|
24
|
+
end
|
25
|
+
|
26
|
+
# Play nicely with expand_hash_conditions_for_aggregates
|
27
|
+
def to_sym
|
28
|
+
"#{column}.#{method}".to_sym
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'meta_where/condition_operators'
|
2
|
+
|
3
|
+
module MetaWhere
|
4
|
+
class Compound
|
5
|
+
include ConditionOperators
|
6
|
+
|
7
|
+
attr_reader :condition1, :condition2
|
8
|
+
|
9
|
+
def initialize(condition1, condition2)
|
10
|
+
@condition1 = condition1
|
11
|
+
@condition2 = condition2
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Or < Compound
|
16
|
+
end
|
17
|
+
|
18
|
+
class And < Compound
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'meta_where/utility'
|
2
|
+
require 'meta_where/condition_operators'
|
3
|
+
|
4
|
+
module MetaWhere
|
5
|
+
class Condition
|
6
|
+
include ConditionOperators
|
7
|
+
include Utility
|
8
|
+
|
9
|
+
attr_reader :column, :value, :method
|
10
|
+
|
11
|
+
def initialize(column, value, method)
|
12
|
+
@column = column
|
13
|
+
@value = value
|
14
|
+
@method = (MetaWhere::METHOD_ALIASES[method.to_s] || method).to_s
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other_condition)
|
18
|
+
other_condition.is_a?(Condition) &&
|
19
|
+
other_condition.column == column &&
|
20
|
+
other_condition.value == value &&
|
21
|
+
other_condition.method == method
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :eql?, :==
|
25
|
+
|
26
|
+
# Play "nicely" with expand_hash_conditions_for_aggregates
|
27
|
+
def to_sym
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
module ConditionOperators
|
3
|
+
def |(other)
|
4
|
+
Or.new(self, other)
|
5
|
+
end
|
6
|
+
|
7
|
+
def &(other)
|
8
|
+
And.new(self, other)
|
9
|
+
end
|
10
|
+
|
11
|
+
def -(other)
|
12
|
+
And.new(self, Not.new(other))
|
13
|
+
end
|
14
|
+
|
15
|
+
def -@
|
16
|
+
Not.new(self)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
class Function
|
3
|
+
attr_reader :name, :args
|
4
|
+
attr_accessor :table, :alias
|
5
|
+
|
6
|
+
def initialize(name, *args)
|
7
|
+
@name = name
|
8
|
+
@args = args
|
9
|
+
end
|
10
|
+
|
11
|
+
def as(val)
|
12
|
+
self.alias = val
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_sqlliteral
|
17
|
+
Arel.sql(
|
18
|
+
("#{name}(#{(['%s'] * args.size).join(',')})" % contextualize_args) +
|
19
|
+
(self.alias ? " AS #{self.alias}" : '')
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :to_sql, :to_sqlliteral
|
24
|
+
|
25
|
+
MetaWhere::PREDICATES.each do |predication|
|
26
|
+
define_method(predication) do
|
27
|
+
MetaWhere::Column.new(self, predication)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
MetaWhere::METHOD_ALIASES.each_pair do |aliased, predication|
|
32
|
+
define_method(aliased) do
|
33
|
+
MetaWhere::Column.new(self, predication)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def >>(value)
|
38
|
+
MetaWhere::Condition.new(self, value, :eq)
|
39
|
+
end
|
40
|
+
|
41
|
+
def ^(value)
|
42
|
+
MetaWhere::Condition.new(self, value, :not_eq)
|
43
|
+
end
|
44
|
+
|
45
|
+
def +(value)
|
46
|
+
MetaWhere::Condition.new(self, value, :in)
|
47
|
+
end
|
48
|
+
|
49
|
+
def -(value)
|
50
|
+
MetaWhere::Condition.new(self, value, :not_in)
|
51
|
+
end
|
52
|
+
|
53
|
+
def =~(value)
|
54
|
+
MetaWhere::Condition.new(self, value, :matches)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Won't work on Ruby 1.8.x so need to do this conditionally
|
58
|
+
if respond_to?('!~')
|
59
|
+
define_method('!~') do |value|
|
60
|
+
MetaWhere::Condition.new(self, value, :does_not_match)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def >(value)
|
65
|
+
MetaWhere::Condition.new(self, value, :gt)
|
66
|
+
end
|
67
|
+
|
68
|
+
def >=(value)
|
69
|
+
MetaWhere::Condition.new(self, value, :gteq)
|
70
|
+
end
|
71
|
+
|
72
|
+
def <(value)
|
73
|
+
MetaWhere::Condition.new(self, value, :lt)
|
74
|
+
end
|
75
|
+
|
76
|
+
def <=(value)
|
77
|
+
MetaWhere::Condition.new(self, value, :lteq)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Play "nicely" with expand_hash_conditions_for_aggregates
|
81
|
+
def to_sym
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def contextualize_args
|
88
|
+
args.map do |arg|
|
89
|
+
if arg.is_a? Symbol
|
90
|
+
self.table && self.table[arg] ? Arel::Visitors.for(ActiveRecord::Base).accept(table[arg]) : arg
|
91
|
+
else
|
92
|
+
arg.table = self.table if arg.is_a? MetaWhere::Function
|
93
|
+
Arel::Visitors.for(ActiveRecord::Base).accept(arg)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module Arel
|
101
|
+
module Visitors
|
102
|
+
class ToSql
|
103
|
+
def visit_MetaWhere_Function o
|
104
|
+
o.to_sqlliteral
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
module JoinDependency
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
alias_method_chain :build, :metawhere
|
7
|
+
alias_method_chain :find_join_association, :metawhere
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class BaseMismatchError < StandardError; end
|
12
|
+
class ConfigurationError < StandardError; end
|
13
|
+
class AssociationNotFoundError < StandardError; end
|
14
|
+
|
15
|
+
def build_with_metawhere(associations, parent = nil, join_type = Arel::Nodes::InnerJoin)
|
16
|
+
parent ||= @joins.last
|
17
|
+
if MetaWhere::JoinType === associations
|
18
|
+
klass = associations.klass
|
19
|
+
join_type = associations.join_type
|
20
|
+
associations = associations.name
|
21
|
+
end
|
22
|
+
|
23
|
+
case associations
|
24
|
+
when Symbol, String
|
25
|
+
reflection = parent.reflections[associations.to_s.intern] or
|
26
|
+
raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
|
27
|
+
unless (association = find_join_association(reflection, parent)) && (!klass || association.active_record == klass)
|
28
|
+
@reflections << reflection
|
29
|
+
if reflection.options[:polymorphic]
|
30
|
+
raise ArgumentError, "You can't create a polymorphic belongs_to join without specifying the polymorphic class!" unless klass
|
31
|
+
association = PolymorphicJoinAssociation.new(reflection, self, klass, parent)
|
32
|
+
else
|
33
|
+
association = build_join_association(reflection, parent)
|
34
|
+
end
|
35
|
+
association.join_type = join_type
|
36
|
+
@joins << association
|
37
|
+
cache_joined_association(association)
|
38
|
+
end
|
39
|
+
association
|
40
|
+
else
|
41
|
+
build_without_metawhere(associations, parent, join_type)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_join_association_with_metawhere(name_or_reflection, parent)
|
46
|
+
case name_or_reflection
|
47
|
+
when MetaWhere::JoinType
|
48
|
+
join_associations.detect do |j|
|
49
|
+
(j.reflection.name == name_or_reflection.name) &&
|
50
|
+
(j.reflection.klass == name_or_reflection.klass) &&
|
51
|
+
(j.parent == parent)
|
52
|
+
end
|
53
|
+
else
|
54
|
+
find_join_association_without_metawhere(name_or_reflection, parent)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class PolymorphicJoinAssociation < ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
|
60
|
+
|
61
|
+
def initialize(reflection, join_dependency, polymorphic_class, parent = nil)
|
62
|
+
reflection.check_validity!
|
63
|
+
@active_record = polymorphic_class
|
64
|
+
@cached_record = {}
|
65
|
+
@join_dependency = join_dependency
|
66
|
+
@parent = parent || join_dependency.join_base
|
67
|
+
@reflection = reflection.clone
|
68
|
+
@reflection.instance_variable_set :"@klass", polymorphic_class
|
69
|
+
@aliased_prefix = "t#{ join_dependency.joins.size }"
|
70
|
+
@parent_table_name = @parent.active_record.table_name
|
71
|
+
@aliased_table_name = aliased_table_name_for(table_name)
|
72
|
+
@join = nil
|
73
|
+
@join_type = Arel::Nodes::InnerJoin
|
74
|
+
end
|
75
|
+
|
76
|
+
def ==(other)
|
77
|
+
other.class == self.class &&
|
78
|
+
other.reflection == reflection &&
|
79
|
+
other.active_record == active_record &&
|
80
|
+
other.parent == parent
|
81
|
+
end
|
82
|
+
|
83
|
+
def association_join
|
84
|
+
return @join if @join
|
85
|
+
|
86
|
+
aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name, :engine => arel_engine)
|
87
|
+
parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name, :engine => arel_engine)
|
88
|
+
|
89
|
+
@join = [
|
90
|
+
aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name]),
|
91
|
+
parent_table[options[:foreign_type]].eq(active_record.name)
|
92
|
+
]
|
93
|
+
|
94
|
+
if options[:conditions]
|
95
|
+
@join << process_conditions(options[:conditions], aliased_table_name)
|
96
|
+
end
|
97
|
+
|
98
|
+
@join
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
class JoinType
|
3
|
+
attr_reader :name, :join_type, :klass
|
4
|
+
|
5
|
+
def initialize(name, join_type = Arel::Nodes::InnerJoin, klass = nil)
|
6
|
+
@name = name
|
7
|
+
@join_type = join_type
|
8
|
+
@klass = klass
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
self.class == other.class &&
|
13
|
+
name == other.name &&
|
14
|
+
join_type == other.join_type &&
|
15
|
+
klass == other.klass
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :eql?, :==
|
19
|
+
|
20
|
+
def hash
|
21
|
+
[name, join_type, klass].hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def outer
|
25
|
+
@join_type = Arel::Nodes::OuterJoin
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def inner
|
30
|
+
@join_type = Arel::Nodes::InnerJoin
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def type(klass)
|
35
|
+
@klass = klass
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_sym
|
40
|
+
self
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
module Relation
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
alias_method_chain :reset, :metawhere
|
7
|
+
alias_method_chain :where_values_hash, :metawhere
|
8
|
+
end
|
9
|
+
|
10
|
+
# We have to do this on the singleton to work with Ruby 1.8.7. Not sure why.
|
11
|
+
base.instance_eval do
|
12
|
+
alias_method :&, :merge
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def merge(r, association_name = nil)
|
17
|
+
if (r && (association_name || base_class.name != r.klass.base_class.name)) # Merging relations with different base.
|
18
|
+
association_name ||= (default_association = reflect_on_all_associations.detect {|a| a.class_name == r.klass.name}) ?
|
19
|
+
default_association.name : r.table_name.to_sym
|
20
|
+
r = r.clone
|
21
|
+
r.where_values.map! {|w| MetaWhere::Visitors::Predicate.visitables.include?(w.class) ? {association_name => w} : w}
|
22
|
+
r.joins_values.map! {|j| [Symbol, Hash, MetaWhere::JoinType].include?(j.class) ? {association_name => j} : j}
|
23
|
+
self.joins_values += [association_name] if reflect_on_association(association_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
super(r)
|
27
|
+
end
|
28
|
+
|
29
|
+
def reset_with_metawhere
|
30
|
+
@mw_unique_joins = @mw_association_joins = @mw_non_association_joins =
|
31
|
+
@mw_stashed_association_joins = @mw_custom_joins = nil
|
32
|
+
reset_without_metawhere
|
33
|
+
end
|
34
|
+
|
35
|
+
def where_values_hash_with_metawhere
|
36
|
+
Hash[flatten_nodes(flatten_predicates(@where_values, predicate_visitor)).find_all { |w|
|
37
|
+
w.respond_to?(:operator) && w.operator == :== && w.left.relation.name == table_name
|
38
|
+
}.map { |where|
|
39
|
+
[
|
40
|
+
where.left.name,
|
41
|
+
where.right.respond_to?(:value) ? where.right.value : where.right
|
42
|
+
]
|
43
|
+
}]
|
44
|
+
end
|
45
|
+
|
46
|
+
def build_where(opts, other = [])
|
47
|
+
if opts.is_a?(String)
|
48
|
+
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
|
49
|
+
else
|
50
|
+
predicates = []
|
51
|
+
[opts, *other].each do |arg|
|
52
|
+
predicates += Array.wrap(
|
53
|
+
case arg
|
54
|
+
when Array
|
55
|
+
@klass.send(:sanitize_sql, arg)
|
56
|
+
when Hash
|
57
|
+
@klass.send(:expand_hash_conditions_for_aggregates, arg)
|
58
|
+
else
|
59
|
+
arg
|
60
|
+
end
|
61
|
+
)
|
62
|
+
end
|
63
|
+
predicates
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_custom_joins(joins = [], arel = nil)
|
68
|
+
arel ||= table
|
69
|
+
joins.each do |join|
|
70
|
+
next if join.blank?
|
71
|
+
|
72
|
+
@implicit_readonly = true
|
73
|
+
|
74
|
+
case join
|
75
|
+
when ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
|
76
|
+
arel = arel.join(join.relation, Arel::Nodes::OuterJoin).on(*join.on)
|
77
|
+
when Hash, Array, Symbol
|
78
|
+
if array_of_strings?(join)
|
79
|
+
join_string = join.join(' ')
|
80
|
+
arel = arel.join(join_string)
|
81
|
+
end
|
82
|
+
else
|
83
|
+
arel = arel.join(join)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
arel
|
88
|
+
end
|
89
|
+
|
90
|
+
def custom_join_sql(*joins)
|
91
|
+
arel = table
|
92
|
+
joins.each do |join|
|
93
|
+
next if join.blank?
|
94
|
+
|
95
|
+
@implicit_readonly = true
|
96
|
+
|
97
|
+
case join
|
98
|
+
when Hash, Array, Symbol
|
99
|
+
if array_of_strings?(join)
|
100
|
+
join_string = join.join(' ')
|
101
|
+
arel = arel.join(join_string)
|
102
|
+
end
|
103
|
+
else
|
104
|
+
arel = arel.join(join)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
arel.joins(arel)
|
108
|
+
end unless defined?(:custom_join_sql)
|
109
|
+
|
110
|
+
def predicates_without_conflicting_equality
|
111
|
+
remove_conflicting_equality_predicates(flatten_predicates(@where_values, predicate_visitor))
|
112
|
+
end
|
113
|
+
|
114
|
+
# Very occasionally, we need to get a visitor for another relation, so it makes sense to factor
|
115
|
+
# these out into a public method despite only being two lines long.
|
116
|
+
def predicate_visitor
|
117
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
|
118
|
+
MetaWhere::Visitors::Predicate.new(join_dependency)
|
119
|
+
end
|
120
|
+
|
121
|
+
def attribute_visitor
|
122
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
|
123
|
+
MetaWhere::Visitors::Attribute.new(join_dependency)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Simulate the logic that occurs in ActiveRecord::Relation.to_a
|
127
|
+
#
|
128
|
+
# @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel.to_sql)
|
129
|
+
#
|
130
|
+
# This will let us get a dump of the SQL that will be run against the DB for debug
|
131
|
+
# purposes without actually running the query.
|
132
|
+
def debug_sql
|
133
|
+
if eager_loading?
|
134
|
+
including = (@eager_load_values + @includes_values).uniq
|
135
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
|
136
|
+
construct_relation_for_association_find(join_dependency).to_sql
|
137
|
+
else
|
138
|
+
arel.to_sql
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def construct_limited_ids_condition(relation)
|
143
|
+
visitor = relation.attribute_visitor
|
144
|
+
|
145
|
+
relation.order_values.map! {|o| visitor.can_accept?(o) ? visitor.accept(o).to_sql : o}
|
146
|
+
|
147
|
+
super
|
148
|
+
end
|
149
|
+
|
150
|
+
def build_arel
|
151
|
+
arel = table
|
152
|
+
|
153
|
+
visitor = predicate_visitor
|
154
|
+
|
155
|
+
arel = build_intelligent_joins(arel, visitor) if @joins_values.present?
|
156
|
+
|
157
|
+
predicate_wheres = flatten_predicates(@where_values.uniq, visitor)
|
158
|
+
|
159
|
+
arel = collapse_wheres(arel, (predicate_wheres - ['']).uniq)
|
160
|
+
|
161
|
+
arel = arel.having(*flatten_predicates(@having_values, visitor).reject {|h| h.blank?}) unless @having_values.empty?
|
162
|
+
|
163
|
+
arel = arel.take(@limit_value) if @limit_value
|
164
|
+
arel = arel.skip(@offset_value) if @offset_value
|
165
|
+
|
166
|
+
arel = arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
|
167
|
+
|
168
|
+
arel = build_order(arel, attribute_visitor, @order_values) unless @order_values.empty?
|
169
|
+
|
170
|
+
arel = build_select(arel, @select_values.uniq)
|
171
|
+
|
172
|
+
arel = arel.from(@from_value) if @from_value
|
173
|
+
arel = arel.lock(@lock_value) if @lock_value
|
174
|
+
|
175
|
+
arel
|
176
|
+
end
|
177
|
+
|
178
|
+
def select(value = Proc.new)
|
179
|
+
if MetaWhere::Function === value
|
180
|
+
value.table = self.arel_table
|
181
|
+
end
|
182
|
+
|
183
|
+
super
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
|
188
|
+
def is_equality_predicate?(predicate)
|
189
|
+
predicate.class == Arel::Nodes::Equality
|
190
|
+
end
|
191
|
+
|
192
|
+
def build_intelligent_joins(arel, visitor)
|
193
|
+
joined_associations = []
|
194
|
+
|
195
|
+
visitor.join_dependency.graft(*stashed_association_joins)
|
196
|
+
|
197
|
+
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
|
198
|
+
|
199
|
+
to_join = []
|
200
|
+
|
201
|
+
visitor.join_dependency.join_associations.each do |association|
|
202
|
+
if (association_relation = association.relation).is_a?(Array)
|
203
|
+
to_join << [association_relation.first, association.join_type, association.association_join.first]
|
204
|
+
to_join << [association_relation.last, association.join_type, association.association_join.last]
|
205
|
+
else
|
206
|
+
to_join << [association_relation, association.join_type, association.association_join]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
to_join.each do |tj|
|
211
|
+
unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] }
|
212
|
+
joined_associations << tj
|
213
|
+
arel = arel.join(tj[0], tj[1]).on(*tj[2])
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
arel = arel.join(custom_joins)
|
218
|
+
end
|
219
|
+
|
220
|
+
def build_order(arel, visitor, orders)
|
221
|
+
order_attributes = orders.map {|o|
|
222
|
+
visitor.can_accept?(o) ? visitor.accept(o, visitor.join_dependency.join_base) : o
|
223
|
+
}.flatten.uniq.reject {|o| o.blank?}
|
224
|
+
order_attributes.present? ? arel.order(*order_attributes) : arel
|
225
|
+
end
|
226
|
+
|
227
|
+
def remove_conflicting_equality_predicates(predicates)
|
228
|
+
predicates.reverse.inject([]) { |ary, w|
|
229
|
+
unless is_equality_predicate?(w) && ary.any? {|p| is_equality_predicate?(p) && p.operand1.name == w.operand1.name}
|
230
|
+
ary << w
|
231
|
+
end
|
232
|
+
ary
|
233
|
+
}.reverse
|
234
|
+
end
|
235
|
+
|
236
|
+
def collapse_wheres(arel, wheres)
|
237
|
+
binaries = wheres.grep(Arel::Nodes::Binary)
|
238
|
+
|
239
|
+
groups = binaries.group_by {|b| [b.class, b.left]}
|
240
|
+
|
241
|
+
groups.each do |_, bins|
|
242
|
+
test = bins.inject(bins.shift) do |memo, expr|
|
243
|
+
memo.and(expr)
|
244
|
+
end
|
245
|
+
arel = arel.where(test)
|
246
|
+
end
|
247
|
+
|
248
|
+
(wheres - binaries).each do |where|
|
249
|
+
where = Arel.sql(where) if String === where
|
250
|
+
arel = arel.where(Arel::Nodes::Grouping.new(where))
|
251
|
+
end
|
252
|
+
arel
|
253
|
+
end
|
254
|
+
|
255
|
+
def flatten_predicates(predicates, visitor)
|
256
|
+
predicates.map {|p|
|
257
|
+
visitor.can_accept?(p) ? visitor.accept(p) : p
|
258
|
+
}.flatten.uniq
|
259
|
+
end
|
260
|
+
|
261
|
+
def flatten_nodes(nodes)
|
262
|
+
nodes.map do |n|
|
263
|
+
case n
|
264
|
+
when Arel::Nodes::Grouping
|
265
|
+
flatten_nodes([n.expr])
|
266
|
+
when Arel::Nodes::And
|
267
|
+
flatten_nodes(n.respond_to?(:children) ? n.children : [n.left, n.right])
|
268
|
+
else
|
269
|
+
n
|
270
|
+
end
|
271
|
+
end.flatten
|
272
|
+
end
|
273
|
+
|
274
|
+
def unique_joins
|
275
|
+
@mw_unique_joins ||= @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq
|
276
|
+
end
|
277
|
+
|
278
|
+
def association_joins
|
279
|
+
@mw_association_joins ||= unique_joins.select{|j|
|
280
|
+
[Hash, Array, Symbol, MetaWhere::JoinType].include?(j.class) && !array_of_strings?(j)
|
281
|
+
}
|
282
|
+
end
|
283
|
+
|
284
|
+
def stashed_association_joins
|
285
|
+
@mw_stashed_association_joins ||= unique_joins.grep(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation)
|
286
|
+
end
|
287
|
+
|
288
|
+
def non_association_joins
|
289
|
+
@mw_non_association_joins ||= (unique_joins - association_joins - stashed_association_joins).reject {|j| j.blank?}
|
290
|
+
end
|
291
|
+
|
292
|
+
def custom_joins
|
293
|
+
@mw_custom_joins ||= custom_join_sql(*non_association_joins)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|