meta_where 0.9.6 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +12 -0
- data/README.rdoc +30 -2
- data/Rakefile +3 -3
- data/VERSION +1 -1
- data/lib/core_ext/hash.rb +2 -9
- data/lib/core_ext/symbol.rb +16 -12
- data/lib/core_ext/symbol_operators.rb +6 -2
- data/lib/meta_where/association_reflection.rb +1 -1
- data/lib/meta_where/column.rb +2 -18
- data/lib/meta_where/compound.rb +5 -11
- data/lib/meta_where/condition.rb +10 -23
- data/lib/meta_where/function.rb +108 -0
- data/lib/meta_where/join_dependency.rb +9 -26
- data/lib/meta_where/join_type.rb +22 -0
- data/lib/meta_where/relation.rb +53 -47
- data/lib/meta_where/utility.rb +22 -6
- data/lib/meta_where/visitors/attribute.rb +57 -0
- data/lib/meta_where/visitors/predicate.rb +88 -0
- data/lib/meta_where/visitors/visitor.rb +46 -0
- data/lib/meta_where.rb +19 -2
- data/meta_where.gemspec +16 -12
- data/test/test_relations.rb +63 -2
- metadata +15 -11
- data/lib/meta_where/builder.rb +0 -84
data/CHANGELOG
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
Changes since 0.9.6 (2010-09-30):
|
2
|
+
* ARel 2.x and Rails 3.0.2 compatibility
|
3
|
+
* Allow MetaWhere::And and MetaWhere::Or on the value side of a condition hash
|
4
|
+
to trigger treatment of key as an association.
|
5
|
+
ex: :comments => (:created_at.lt % 1.day.ago | :body.matches % '%hey%')
|
6
|
+
* Support for selecting join type: :association.outer / :association.inner results
|
7
|
+
in an outer or inner join.
|
8
|
+
* Having clauses are now fully MetaWhere-enabled.
|
9
|
+
* SQL functions are now supported. :concat.func(val1, val2) will result in an
|
10
|
+
SQL concat() call, with two parameters. If you've enabled Symbol operators, then
|
11
|
+
you can also just do :concat[val1, val2].
|
12
|
+
|
1
13
|
Changes since 0.9.5 (2010-09-27):
|
2
14
|
* Don't skip adding condition if an empty array is supplied
|
3
15
|
|
data/README.rdoc
CHANGED
@@ -68,7 +68,7 @@ to enclose other conditions in {}, you should place operator conditions before a
|
|
68
68
|
|
69
69
|
Operators are:
|
70
70
|
|
71
|
-
*
|
71
|
+
* >> (equal)
|
72
72
|
* ^ (not equal)
|
73
73
|
* + (in array/range)
|
74
74
|
* - (not in array/range)
|
@@ -78,6 +78,7 @@ Operators are:
|
|
78
78
|
* >= (greater than or equal to)
|
79
79
|
* < (less than)
|
80
80
|
* <= (less than or equal to)
|
81
|
+
* [] (SQL functions -- more on those below)
|
81
82
|
|
82
83
|
=== Compounds
|
83
84
|
You can use the & and | operators to perform ands and ors within your queries.
|
@@ -148,6 +149,33 @@ parentheses around everything. So MetaWhere also supports a substitution-inspire
|
|
148
149
|
|
149
150
|
(*) Formatting added for clarity. I said you could do this, not that you should. :)
|
150
151
|
|
152
|
+
== Join type specification
|
153
|
+
You can choose whether to use an inner join (the default) or a left outer join by tacking
|
154
|
+
<tt>.outer</tt> or <tt>.inner</tt> to the symbols specified in your joins() call:
|
155
|
+
|
156
|
+
Article.joins(:comments => :moderations.outer).to_sql
|
157
|
+
=> SELECT "articles".* FROM "articles"
|
158
|
+
INNER JOIN "comments" ON "comments"."article_id" = "articles"."id"
|
159
|
+
LEFT OUTER JOIN "moderations" ON "moderations"."comment_id" = "comments"."id"
|
160
|
+
|
161
|
+
== SQL Functions
|
162
|
+
You can use SQL functions in your queries:
|
163
|
+
|
164
|
+
Manager.joins(:employees.outer).group('managers.id').
|
165
|
+
having(:employees => (:count.func(:id) < 3))
|
166
|
+
=> SELECT "managers".* FROM "managers"
|
167
|
+
LEFT OUTER JOIN "employees" ON "employees"."manager_id" = "managers"."id"
|
168
|
+
GROUP BY managers.id HAVING count("employees"."id") < 3
|
169
|
+
|
170
|
+
If you enable Symbol operators, you can just use <tt>:count[:id]</tt>, instead of calling
|
171
|
+
<tt>func</tt> as shown above. SQL functions work in the SELECT, WHERE, and HAVING clauses,
|
172
|
+
and can be aliased with <tt>as</tt>:
|
173
|
+
|
174
|
+
Manager.select('managers.*').
|
175
|
+
select(:find_in_set[:id, '3,2,1'].as('position'))
|
176
|
+
=> SELECT managers.*, find_in_set("managers"."id",'3,2,1') AS position
|
177
|
+
FROM "managers"
|
178
|
+
|
151
179
|
=== But wait, there's more!
|
152
180
|
|
153
181
|
== Intelligent hash condition mapping
|
@@ -182,7 +210,7 @@ With MetaWhere:
|
|
182
210
|
=> SELECT "articles".* FROM "articles" INNER JOIN "comments"
|
183
211
|
ON "comments"."article_id" = "articles"."id" WHERE (("comments"."body" = 'hey'))
|
184
212
|
|
185
|
-
The general idea is that if an association with the name provided exists, MetaWhere
|
213
|
+
The general idea is that if an association with the name provided exists, MetaWhere
|
186
214
|
will build the conditions against that association's table as it's been aliased, before falling
|
187
215
|
back to assuming you're specifying a table by name. It also handles nested associations:
|
188
216
|
|
data/Rakefile
CHANGED
@@ -19,9 +19,9 @@ begin
|
|
19
19
|
gem.homepage = "http://metautonomo.us/projects/metawhere/"
|
20
20
|
gem.authors = ["Ernie Miller"]
|
21
21
|
gem.add_development_dependency "shoulda"
|
22
|
-
gem.add_dependency "activerecord", "~> 3.0.
|
23
|
-
gem.add_dependency "activesupport", "~> 3.0.
|
24
|
-
gem.add_dependency "arel", "~>
|
22
|
+
gem.add_dependency "activerecord", "~> 3.0.2"
|
23
|
+
gem.add_dependency "activesupport", "~> 3.0.2"
|
24
|
+
gem.add_dependency "arel", "~> 2.0.2"
|
25
25
|
gem.post_install_message = <<END
|
26
26
|
|
27
27
|
*** Thanks for installing MetaWhere! ***
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.9
|
data/lib/core_ext/hash.rb
CHANGED
@@ -1,16 +1,9 @@
|
|
1
1
|
class Hash
|
2
|
-
|
3
|
-
Arel::Predicates::All.new(*builder.build_predicates_from_hash(self, parent || builder.join_dependency.join_base))
|
4
|
-
end
|
5
|
-
|
6
|
-
def to_attribute(builder, parent = nil)
|
7
|
-
builder.build_attributes_from_hash(self, parent)
|
8
|
-
end
|
9
|
-
|
2
|
+
|
10
3
|
def |(other)
|
11
4
|
MetaWhere::Or.new(self, other)
|
12
5
|
end
|
13
|
-
|
6
|
+
|
14
7
|
def &(other)
|
15
8
|
MetaWhere::And.new(self, other)
|
16
9
|
end
|
data/lib/core_ext/symbol.rb
CHANGED
@@ -1,30 +1,34 @@
|
|
1
1
|
class Symbol
|
2
|
-
|
2
|
+
MetaWhere::PREDICATES.each do |predication|
|
3
3
|
define_method(predication) do
|
4
4
|
MetaWhere::Column.new(self, predication)
|
5
5
|
end
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
MetaWhere::METHOD_ALIASES.each_pair do |aliased, predication|
|
9
9
|
define_method(aliased) do
|
10
10
|
MetaWhere::Column.new(self, predication)
|
11
11
|
end
|
12
12
|
end
|
13
|
-
|
14
|
-
def to_attribute(builder, parent = nil)
|
15
|
-
table = builder.build_table(parent)
|
16
|
-
|
17
|
-
unless attribute = table[self]
|
18
|
-
raise ::ActiveRecord::StatementInvalid, "No attribute named `#{self}` exists for table `#{table.name}`"
|
19
|
-
end
|
20
13
|
|
21
|
-
|
14
|
+
def mw_func(*args)
|
15
|
+
MetaWhere::Function.new(self, *args)
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :func, :mw_func unless method_defined?(:func)
|
19
|
+
|
20
|
+
def inner
|
21
|
+
MetaWhere::JoinType.new(self, Arel::Nodes::InnerJoin)
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
|
+
def outer
|
25
|
+
MetaWhere::JoinType.new(self, Arel::Nodes::OuterJoin)
|
26
|
+
end
|
27
|
+
|
24
28
|
def asc
|
25
29
|
MetaWhere::Column.new(self, :asc)
|
26
30
|
end
|
27
|
-
|
31
|
+
|
28
32
|
def desc
|
29
33
|
MetaWhere::Column.new(self, :desc)
|
30
34
|
end
|
@@ -1,5 +1,9 @@
|
|
1
1
|
class Symbol
|
2
|
-
def [](
|
2
|
+
def [](*values)
|
3
|
+
MetaWhere::Function.new(self, *values)
|
4
|
+
end
|
5
|
+
|
6
|
+
def >>(value)
|
3
7
|
MetaWhere::Condition.new(self, value, :eq)
|
4
8
|
end
|
5
9
|
|
@@ -22,7 +26,7 @@ class Symbol
|
|
22
26
|
# Won't work on Ruby 1.8.x so need to do this conditionally
|
23
27
|
if respond_to?('!~')
|
24
28
|
define_method('!~') do |value|
|
25
|
-
MetaWhere::Condition.new(self, value, :
|
29
|
+
MetaWhere::Condition.new(self, value, :does_not_match)
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
@@ -18,7 +18,7 @@ module MetaWhere
|
|
18
18
|
when Hash
|
19
19
|
if obj.keys.grep(MetaWhere::Column).any?
|
20
20
|
raise MetaWhereInAssociationError, <<END
|
21
|
-
The :#{name} association
|
21
|
+
The :#{name} association has a MetaWhere::Column in its :conditions. \
|
22
22
|
If you actually needed to access conditions other than equality, then you most \
|
23
23
|
likely meant to set up a scope or method, instead. Associations only work with \
|
24
24
|
standard equality conditions, since they can be used to create records as well.
|
data/lib/meta_where/column.rb
CHANGED
@@ -3,8 +3,8 @@ module MetaWhere
|
|
3
3
|
attr_reader :column, :method
|
4
4
|
|
5
5
|
def initialize(column, method)
|
6
|
-
@column = column
|
7
|
-
@method = method
|
6
|
+
@column = column
|
7
|
+
@method = method
|
8
8
|
end
|
9
9
|
|
10
10
|
def %(value)
|
@@ -19,22 +19,6 @@ module MetaWhere
|
|
19
19
|
|
20
20
|
alias_method :eql?, :==
|
21
21
|
|
22
|
-
def to_attribute(builder, parent = nil)
|
23
|
-
column_name = column
|
24
|
-
if column_name.include?('.')
|
25
|
-
table_name, column_name = column_name.split('.', 2)
|
26
|
-
table = Arel::Table.new(table_name, :engine => parent.arel_engine)
|
27
|
-
else
|
28
|
-
table = builder.build_table(parent)
|
29
|
-
end
|
30
|
-
|
31
|
-
unless attribute = table[column_name]
|
32
|
-
raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column_name}` exists for table `#{table.name}`"
|
33
|
-
end
|
34
|
-
|
35
|
-
attribute.send(method)
|
36
|
-
end
|
37
|
-
|
38
22
|
def hash
|
39
23
|
[column, method].hash
|
40
24
|
end
|
data/lib/meta_where/compound.rb
CHANGED
@@ -1,30 +1,24 @@
|
|
1
1
|
module MetaWhere
|
2
2
|
class Compound
|
3
3
|
attr_reader :condition1, :condition2
|
4
|
-
|
4
|
+
|
5
5
|
def initialize(condition1, condition2)
|
6
6
|
@condition1 = condition1
|
7
7
|
@condition2 = condition2
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
def |(other)
|
11
11
|
Or.new(self, other)
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def &(other)
|
15
15
|
And.new(self, other)
|
16
16
|
end
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
class Or < Compound
|
20
|
-
def to_predicate(builder, parent = nil)
|
21
|
-
condition1.to_predicate(builder, parent).or(condition2.to_predicate(builder, parent))
|
22
|
-
end
|
23
20
|
end
|
24
|
-
|
21
|
+
|
25
22
|
class And < Compound
|
26
|
-
def to_predicate(builder, parent = nil)
|
27
|
-
condition1.to_predicate(builder, parent).and(condition2.to_predicate(builder, parent))
|
28
|
-
end
|
29
23
|
end
|
30
24
|
end
|
data/lib/meta_where/condition.rb
CHANGED
@@ -3,49 +3,36 @@ require 'meta_where/utility'
|
|
3
3
|
module MetaWhere
|
4
4
|
class Condition
|
5
5
|
include MetaWhere::Utility
|
6
|
-
|
6
|
+
|
7
7
|
attr_reader :column, :value, :method
|
8
|
-
|
8
|
+
|
9
9
|
def initialize(column, value, method)
|
10
|
-
@column = column
|
10
|
+
@column = column
|
11
11
|
@value = value
|
12
12
|
@method = (MetaWhere::METHOD_ALIASES[method.to_s] || method).to_s
|
13
13
|
end
|
14
|
-
|
15
|
-
def to_predicate(builder, parent = nil)
|
16
|
-
table = builder.build_table(parent)
|
17
|
-
|
18
|
-
unless attribute = table[column]
|
19
|
-
raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
|
20
|
-
end
|
21
|
-
|
22
|
-
unless valid_comparison_method?(method)
|
23
|
-
raise ::ActiveRecord::StatementInvalid, "No comparison method named `#{method}` exists for column `#{column}`"
|
24
|
-
end
|
25
|
-
attribute.send(method, *args_for_predicate(method.to_s, value))
|
26
|
-
end
|
27
|
-
|
14
|
+
|
28
15
|
def ==(other_condition)
|
29
16
|
other_condition.is_a?(Condition) &&
|
30
17
|
other_condition.column == column &&
|
31
|
-
other_condition.value
|
18
|
+
other_condition.value == value &&
|
32
19
|
other_condition.method == method
|
33
20
|
end
|
34
|
-
|
21
|
+
|
35
22
|
alias_method :eql?, :==
|
36
|
-
|
23
|
+
|
37
24
|
def |(other)
|
38
25
|
Or.new(self, other)
|
39
26
|
end
|
40
|
-
|
27
|
+
|
41
28
|
def &(other)
|
42
29
|
And.new(self, other)
|
43
30
|
end
|
44
|
-
|
31
|
+
|
45
32
|
# Play "nicely" with expand_hash_conditions_for_aggregates
|
46
33
|
def to_sym
|
47
34
|
self
|
48
35
|
end
|
49
36
|
end
|
50
|
-
|
37
|
+
|
51
38
|
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
|
@@ -11,37 +11,20 @@ module MetaWhere
|
|
11
11
|
class ConfigurationError < StandardError; end
|
12
12
|
class AssociationNotFoundError < StandardError; end
|
13
13
|
|
14
|
-
def build_with_metawhere(associations, parent = nil,
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
raise AssociationNotFoundError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
|
14
|
+
def build_with_metawhere(associations, parent = nil, join_type = Arel::Nodes::InnerJoin)
|
15
|
+
if MetaWhere::JoinType === associations
|
16
|
+
parent||= @joins.last
|
17
|
+
reflection = parent.reflections[associations.name] or
|
18
|
+
raise AssociationNotFoundError, "Association named '#{ associations.name }' was not found; perhaps you misspelled it?"
|
20
19
|
unless association = find_join_association(reflection, parent)
|
21
20
|
@reflections << reflection
|
22
|
-
association =
|
21
|
+
association = build_join_association(reflection, parent)
|
22
|
+
association.join_type = associations.join_type
|
23
|
+
@joins << association
|
23
24
|
end
|
24
25
|
association
|
25
|
-
when Array
|
26
|
-
associations.each do |association|
|
27
|
-
build(association, parent, join_class)
|
28
|
-
end
|
29
|
-
when Hash
|
30
|
-
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
|
31
|
-
association = build(name, parent, join_class)
|
32
|
-
build(associations[name], association, join_class)
|
33
|
-
end
|
34
|
-
else
|
35
|
-
raise ConfigurationError, associations.inspect
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def find_join_association(name_or_reflection, parent)
|
40
|
-
case name_or_reflection
|
41
|
-
when Symbol, String
|
42
|
-
join_associations.detect {|j| (j.reflection.name == name_or_reflection.to_s.intern) && (j.parent == parent)}
|
43
26
|
else
|
44
|
-
|
27
|
+
build_without_metawhere(associations, parent, join_type)
|
45
28
|
end
|
46
29
|
end
|
47
30
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module MetaWhere
|
2
|
+
class JoinType
|
3
|
+
attr_reader :name, :join_type
|
4
|
+
|
5
|
+
def initialize(name, join_type = Arel::Nodes::InnerJoin)
|
6
|
+
@name = name
|
7
|
+
@join_type = join_type
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
self.class == other.class &&
|
12
|
+
name == other.name &&
|
13
|
+
join_type == other.join_type
|
14
|
+
end
|
15
|
+
|
16
|
+
alias_method :eql?, :==
|
17
|
+
|
18
|
+
def hash
|
19
|
+
[name, join_type].hash
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/meta_where/relation.rb
CHANGED
@@ -15,12 +15,12 @@ module MetaWhere
|
|
15
15
|
|
16
16
|
def merge(r, association_name = nil)
|
17
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.
|
18
|
+
association_name ||= (default_association = reflect_on_all_associations.detect {|a| a.class_name == r.klass.name}) ?
|
19
19
|
default_association.name : r.table_name.to_sym
|
20
20
|
r = r.clone
|
21
|
-
r.where_values.map! {|w|
|
22
|
-
r.joins_values.map! {|j| [Symbol, Hash].include?(j.class) ? {association_name => j} : j}
|
23
|
-
self.joins_values += [association_name]
|
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
24
|
end
|
25
25
|
|
26
26
|
super(r)
|
@@ -36,7 +36,7 @@ module MetaWhere
|
|
36
36
|
@scope_for_create ||= begin
|
37
37
|
@create_with_value || predicate_wheres.inject({}) do |hash, where|
|
38
38
|
if is_equality_predicate?(where)
|
39
|
-
hash[where.
|
39
|
+
hash[where.left.name] = where.right.respond_to?(:value) ? where.right.value : where.right
|
40
40
|
end
|
41
41
|
|
42
42
|
hash
|
@@ -74,7 +74,7 @@ module MetaWhere
|
|
74
74
|
|
75
75
|
case join
|
76
76
|
when ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation
|
77
|
-
arel = arel.join(join.relation, Arel::OuterJoin).on(*join.on)
|
77
|
+
arel = arel.join(join.relation, Arel::Nodes::OuterJoin).on(*join.on)
|
78
78
|
when Hash, Array, Symbol
|
79
79
|
if array_of_strings?(join)
|
80
80
|
join_string = join.join(' ')
|
@@ -109,14 +109,19 @@ module MetaWhere
|
|
109
109
|
end unless defined?(:custom_join_sql)
|
110
110
|
|
111
111
|
def predicate_wheres
|
112
|
-
remove_conflicting_equality_predicates(flatten_predicates(@where_values,
|
112
|
+
remove_conflicting_equality_predicates(flatten_predicates(@where_values, predicate_visitor))
|
113
113
|
end
|
114
114
|
|
115
|
-
# Very occasionally, we need to get a
|
116
|
-
#
|
117
|
-
def
|
115
|
+
# Very occasionally, we need to get a visitor for another relation, so it makes sense to factor
|
116
|
+
# these out into a public method despite only being two lines long.
|
117
|
+
def predicate_visitor
|
118
118
|
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
|
119
|
-
MetaWhere::
|
119
|
+
MetaWhere::Visitors::Predicate.new(join_dependency)
|
120
|
+
end
|
121
|
+
|
122
|
+
def attribute_visitor
|
123
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins)
|
124
|
+
MetaWhere::Visitors::Attribute.new(join_dependency)
|
120
125
|
end
|
121
126
|
|
122
127
|
# Simulate the logic that occurs in ActiveRecord::Relation.to_a
|
@@ -136,9 +141,9 @@ module MetaWhere
|
|
136
141
|
end
|
137
142
|
|
138
143
|
def construct_limited_ids_condition(relation)
|
139
|
-
|
144
|
+
visitor = relation.attribute_visitor
|
140
145
|
|
141
|
-
relation.order_values.map! {|o|
|
146
|
+
relation.order_values.map! {|o| visitor.can_accept?(o) ? visitor.accept(o).to_sql : o}
|
142
147
|
|
143
148
|
super
|
144
149
|
end
|
@@ -146,68 +151,70 @@ module MetaWhere
|
|
146
151
|
def build_arel
|
147
152
|
arel = table
|
148
153
|
|
149
|
-
|
154
|
+
visitor = predicate_visitor
|
150
155
|
|
151
|
-
arel = build_intelligent_joins(arel,
|
156
|
+
arel = build_intelligent_joins(arel, visitor) if @joins_values.present?
|
152
157
|
|
153
|
-
predicate_wheres = remove_conflicting_equality_predicates(flatten_predicates(@where_values,
|
158
|
+
predicate_wheres = remove_conflicting_equality_predicates(flatten_predicates(@where_values, visitor))
|
154
159
|
|
155
160
|
predicate_wheres.each do |where|
|
156
161
|
next if where.blank?
|
157
162
|
|
158
163
|
case where
|
159
|
-
when Arel::SqlLiteral
|
164
|
+
when Arel::Nodes::SqlLiteral
|
160
165
|
arel = arel.where(where)
|
161
166
|
else
|
162
167
|
sql = where.is_a?(String) ? where : where.to_sql
|
163
|
-
arel = arel.where(Arel::SqlLiteral.new("(#{sql})"))
|
168
|
+
arel = arel.where(Arel::Nodes::SqlLiteral.new("(#{sql})"))
|
164
169
|
end
|
165
170
|
end
|
166
171
|
|
167
|
-
arel = arel.having(
|
172
|
+
arel = arel.having(*flatten_predicates(@having_values, visitor).reject {|h| h.blank?}) unless @having_values.empty?
|
168
173
|
|
169
|
-
arel = arel.take(@limit_value) if @limit_value
|
170
|
-
arel = arel.skip(@offset_value) if @offset_value
|
174
|
+
arel = arel.take(@limit_value) if @limit_value
|
175
|
+
arel = arel.skip(@offset_value) if @offset_value
|
171
176
|
|
172
|
-
arel = arel.group(*@group_values.uniq.
|
177
|
+
arel = arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
|
173
178
|
|
174
|
-
arel = build_order(arel,
|
179
|
+
arel = build_order(arel, attribute_visitor, @order_values) unless @order_values.empty?
|
175
180
|
|
176
181
|
arel = build_select(arel, @select_values.uniq)
|
177
182
|
|
178
|
-
arel = arel.from(@from_value) if @from_value
|
179
|
-
|
180
|
-
case @lock_value
|
181
|
-
when TrueClass
|
182
|
-
arel = arel.lock
|
183
|
-
when String
|
184
|
-
arel = arel.lock(@lock_value)
|
185
|
-
end if @lock_value.present?
|
183
|
+
arel = arel.from(@from_value) if @from_value
|
184
|
+
arel = arel.lock(@lock_value) if @lock_value
|
186
185
|
|
187
186
|
arel
|
188
187
|
end
|
189
188
|
|
189
|
+
def select(value = Proc.new)
|
190
|
+
if MetaWhere::Function === value
|
191
|
+
value.table = self.arel_table
|
192
|
+
end
|
193
|
+
|
194
|
+
super
|
195
|
+
end
|
196
|
+
|
190
197
|
private
|
191
198
|
|
192
199
|
def is_equality_predicate?(predicate)
|
193
|
-
predicate.
|
200
|
+
predicate.class == Arel::Nodes::Equality
|
194
201
|
end
|
195
202
|
|
196
|
-
def build_intelligent_joins(arel,
|
203
|
+
def build_intelligent_joins(arel, visitor)
|
197
204
|
joined_associations = []
|
198
205
|
|
199
|
-
|
206
|
+
visitor.join_dependency.graft(*stashed_association_joins)
|
200
207
|
|
201
208
|
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
|
202
209
|
|
203
210
|
to_join = []
|
204
211
|
|
205
|
-
|
212
|
+
visitor.join_dependency.join_associations.each do |association|
|
206
213
|
if (association_relation = association.relation).is_a?(Array)
|
207
|
-
to_join << [association_relation.first, association.
|
208
|
-
to_join << [association_relation.last, association.
|
214
|
+
to_join << [association_relation.first, association.join_type, association.association_join.first]
|
215
|
+
to_join << [association_relation.last, association.join_type, association.association_join.last]
|
209
216
|
else
|
210
|
-
to_join << [association_relation, association.
|
217
|
+
to_join << [association_relation, association.join_type, association.association_join]
|
211
218
|
end
|
212
219
|
end
|
213
220
|
|
@@ -221,11 +228,10 @@ module MetaWhere
|
|
221
228
|
arel = arel.join(custom_joins)
|
222
229
|
end
|
223
230
|
|
224
|
-
def build_order(arel,
|
231
|
+
def build_order(arel, visitor, orders)
|
225
232
|
order_attributes = orders.map {|o|
|
226
|
-
|
227
|
-
}.flatten.uniq.
|
228
|
-
order_attributes.map! {|a| Arel::SqlLiteral.new(a.is_a?(String) ? a : a.to_sql)}
|
233
|
+
visitor.can_accept?(o) ? visitor.accept(o, visitor.join_dependency.join_base) : o
|
234
|
+
}.flatten.uniq.reject {|o| o.blank?}
|
229
235
|
order_attributes.present? ? arel.order(*order_attributes) : arel
|
230
236
|
end
|
231
237
|
|
@@ -238,11 +244,11 @@ module MetaWhere
|
|
238
244
|
}.reverse
|
239
245
|
end
|
240
246
|
|
241
|
-
def flatten_predicates(predicates,
|
247
|
+
def flatten_predicates(predicates, visitor)
|
242
248
|
predicates.map {|p|
|
243
|
-
predicate =
|
244
|
-
if predicate.is_a?(Arel::
|
245
|
-
flatten_predicates(predicate.
|
249
|
+
predicate = visitor.can_accept?(p) ? visitor.accept(p) : p
|
250
|
+
if predicate.is_a?(Arel::Nodes::Grouping) && predicate.expr.is_a?(Arel::Nodes::And)
|
251
|
+
flatten_predicates([predicate.expr.left, predicate.expr.right], visitor)
|
246
252
|
else
|
247
253
|
predicate
|
248
254
|
end
|
@@ -255,7 +261,7 @@ module MetaWhere
|
|
255
261
|
|
256
262
|
def association_joins
|
257
263
|
@mw_association_joins ||= unique_joins.select{|j|
|
258
|
-
[Hash, Array, Symbol].include?(j.class) && !array_of_strings?(j)
|
264
|
+
[Hash, Array, Symbol, MetaWhere::JoinType].include?(j.class) && !array_of_strings?(j)
|
259
265
|
}
|
260
266
|
end
|
261
267
|
|
data/lib/meta_where/utility.rb
CHANGED
@@ -2,12 +2,28 @@ module MetaWhere
|
|
2
2
|
module Utility
|
3
3
|
private
|
4
4
|
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
def association_from_parent_and_column(parent, column)
|
6
|
+
parent.is_a?(Symbol) ? nil : @join_dependency.send(:find_join_association, column, parent)
|
7
|
+
end
|
8
|
+
|
9
|
+
def attribute_from_column_and_table(column, table)
|
10
|
+
case column
|
11
|
+
when String, Symbol
|
12
|
+
table[column]
|
13
|
+
when MetaWhere::Function
|
14
|
+
column.table = table
|
15
|
+
column.to_sqlliteral
|
9
16
|
else
|
10
|
-
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def args_for_predicate(value)
|
22
|
+
case value
|
23
|
+
when ActiveRecord::Associations::AssociationCollection, ActiveRecord::Relation
|
24
|
+
value.to_a
|
25
|
+
else
|
26
|
+
value
|
11
27
|
end
|
12
28
|
end
|
13
29
|
|
@@ -21,7 +37,7 @@ module MetaWhere
|
|
21
37
|
end
|
22
38
|
|
23
39
|
def valid_comparison_method?(method)
|
24
|
-
|
40
|
+
MetaWhere::PREDICATES.map(&:to_s).include?(method.to_s)
|
25
41
|
end
|
26
42
|
end
|
27
43
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'meta_where/visitors/visitor'
|
2
|
+
|
3
|
+
module MetaWhere
|
4
|
+
module Visitors
|
5
|
+
class Attribute < Visitor
|
6
|
+
|
7
|
+
def self.visitables
|
8
|
+
[Hash, Symbol, MetaWhere::Column]
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit_Hash(o, parent)
|
12
|
+
table = build_table(parent)
|
13
|
+
built_attributes = o.map do |column, value|
|
14
|
+
if value.is_a?(Hash)
|
15
|
+
association = association_from_parent_and_column(parent, column)
|
16
|
+
accept(value, association || column)
|
17
|
+
elsif value.is_a?(Array) && value.all? {|v| can_accept?(v)}
|
18
|
+
association = association_from_parent_and_column(parent, column)
|
19
|
+
value.map {|val| self.accept(val, association || column)}
|
20
|
+
else
|
21
|
+
association = association_from_parent_and_column(parent, column)
|
22
|
+
can_accept?(value) ? self.accept(value, association || column) : value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
built_attributes.flatten
|
27
|
+
end
|
28
|
+
|
29
|
+
def visit_Symbol(o, parent)
|
30
|
+
table = self.build_table(parent)
|
31
|
+
|
32
|
+
unless attribute = table[o]
|
33
|
+
raise ::ActiveRecord::StatementInvalid, "No attribute named `#{o}` exists for table `#{table.name}`"
|
34
|
+
end
|
35
|
+
|
36
|
+
attribute
|
37
|
+
end
|
38
|
+
|
39
|
+
def visit_MetaWhere_Column(o, parent)
|
40
|
+
column_name = o.column.to_s
|
41
|
+
if column_name.include?('.')
|
42
|
+
table_name, column_name = column_name.split('.', 2)
|
43
|
+
table = Arel::Table.new(table_name, :engine => parent.arel_engine)
|
44
|
+
else
|
45
|
+
table = self.build_table(parent)
|
46
|
+
end
|
47
|
+
|
48
|
+
unless attribute = table[column_name]
|
49
|
+
raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column_name}` exists for table `#{table.name}`"
|
50
|
+
end
|
51
|
+
|
52
|
+
attribute.send(o.method)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'meta_where/visitors/visitor'
|
2
|
+
|
3
|
+
module MetaWhere
|
4
|
+
module Visitors
|
5
|
+
class Predicate < Visitor
|
6
|
+
|
7
|
+
def self.visitables
|
8
|
+
[Hash, MetaWhere::Or, MetaWhere::And, MetaWhere::Condition, MetaWhere::Function]
|
9
|
+
end
|
10
|
+
|
11
|
+
def visit_Hash(o, parent)
|
12
|
+
parent ||= join_dependency.join_base
|
13
|
+
table = build_table(parent)
|
14
|
+
predicates = o.map do |column, value|
|
15
|
+
if value.is_a?(Hash)
|
16
|
+
association = association_from_parent_and_column(parent, column)
|
17
|
+
accept(value, association || column)
|
18
|
+
elsif [MetaWhere::Condition, MetaWhere::And, MetaWhere::Or].include?(value.class)
|
19
|
+
association = association_from_parent_and_column(parent, column)
|
20
|
+
accept(value, association || column)
|
21
|
+
elsif value.is_a?(Array) && !value.empty? && value.all? {|v| can_accept?(v)}
|
22
|
+
association = association_from_parent_and_column(parent, column)
|
23
|
+
value.map {|val| accept(val, association || column)}
|
24
|
+
else
|
25
|
+
if column.is_a?(MetaWhere::Column)
|
26
|
+
method = column.method
|
27
|
+
column = column.column
|
28
|
+
else
|
29
|
+
method = method_from_value(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
if [String, Symbol].include?(column.class) && column.to_s.include?('.')
|
33
|
+
table_name, column = column.to_s.split('.', 2)
|
34
|
+
table = Arel::Table.new(table_name, :engine => parent.arel_engine)
|
35
|
+
end
|
36
|
+
|
37
|
+
unless attribute = attribute_from_column_and_table(column, table)
|
38
|
+
raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
|
39
|
+
end
|
40
|
+
|
41
|
+
unless valid_comparison_method?(method)
|
42
|
+
raise ::ActiveRecord::StatementInvalid, "No comparison method named `#{method}` exists for column `#{column}`"
|
43
|
+
end
|
44
|
+
|
45
|
+
attribute.send(method, args_for_predicate(value))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
predicates.flatten!
|
50
|
+
|
51
|
+
if predicates.size > 1
|
52
|
+
first = predicates.shift
|
53
|
+
Arel::Nodes::Grouping.new(predicates.inject(first) {|memo, expr| Arel::Nodes::And.new(memo, expr)})
|
54
|
+
else
|
55
|
+
predicates.first
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def visit_MetaWhere_Or(o, parent)
|
60
|
+
accept(o.condition1, parent).or(accept(o.condition2, parent))
|
61
|
+
end
|
62
|
+
|
63
|
+
def visit_MetaWhere_And(o, parent)
|
64
|
+
accept(o.condition1, parent).and(accept(o.condition2, parent))
|
65
|
+
end
|
66
|
+
|
67
|
+
def visit_MetaWhere_Condition(o, parent)
|
68
|
+
table = self.build_table(parent)
|
69
|
+
|
70
|
+
unless attribute = attribute_from_column_and_table(o.column, table)
|
71
|
+
raise ::ActiveRecord::StatementInvalid, "No attribute named `#{o.column}` exists for table `#{table.name}`"
|
72
|
+
end
|
73
|
+
|
74
|
+
unless valid_comparison_method?(o.method)
|
75
|
+
raise ::ActiveRecord::StatementInvalid, "No comparison method named `#{o.method}` exists for column `#{o.column}`"
|
76
|
+
end
|
77
|
+
attribute.send(o.method, args_for_predicate(o.value))
|
78
|
+
end
|
79
|
+
|
80
|
+
def visit_MetaWhere_Function(o, parent)
|
81
|
+
self.table = self.build_table(parent)
|
82
|
+
|
83
|
+
o.to_sqlliteral
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'meta_where/utility'
|
2
|
+
|
3
|
+
module MetaWhere
|
4
|
+
module Visitors
|
5
|
+
class Visitor
|
6
|
+
include MetaWhere::Utility
|
7
|
+
|
8
|
+
attr_reader :join_dependency
|
9
|
+
|
10
|
+
def initialize(join_dependency)
|
11
|
+
@join_dependency = join_dependency
|
12
|
+
jb = join_dependency.join_base
|
13
|
+
@engine = jb.arel_engine
|
14
|
+
@default_table = Arel::Table.new(jb.table_name, :as => jb.aliased_table_name, :engine => @engine)
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_table(parent_or_table_name = nil)
|
18
|
+
if parent_or_table_name.is_a?(Symbol)
|
19
|
+
Arel::Table.new(parent_or_table_name, :engine => @engine)
|
20
|
+
elsif parent_or_table_name.respond_to?(:aliased_table_name)
|
21
|
+
Arel::Table.new(parent_or_table_name.table_name, :as => parent_or_table_name.aliased_table_name, :engine => @engine)
|
22
|
+
else
|
23
|
+
@default_table
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def accept(object, parent = nil)
|
28
|
+
visit(object, parent)
|
29
|
+
end
|
30
|
+
|
31
|
+
def can_accept?(object)
|
32
|
+
respond_to? DISPATCH[object.class]
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
DISPATCH = Hash.new do |hash, klass|
|
38
|
+
hash[klass] = "visit_#{klass.name.gsub('::', '_')}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def visit(object, parent)
|
42
|
+
send(DISPATCH[object.class], object, parent)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/meta_where.rb
CHANGED
@@ -2,12 +2,26 @@ module MetaWhere
|
|
2
2
|
METHOD_ALIASES = {
|
3
3
|
'ne' => :not_eq,
|
4
4
|
'like' => :matches,
|
5
|
-
'
|
5
|
+
'not_matches' => :does_not_match,
|
6
|
+
'nlike' => :does_not_match,
|
6
7
|
'lte' => :lteq,
|
7
8
|
'gte' => :gteq,
|
8
9
|
'nin' => :not_in
|
9
10
|
}
|
10
11
|
|
12
|
+
PREDICATES = [
|
13
|
+
:eq, :eq_any, :eq_all,
|
14
|
+
:not_eq, :not_eq_any, :not_eq_all,
|
15
|
+
:matches, :matches_any, :matches_all,
|
16
|
+
:does_not_match, :does_not_match_any, :does_not_match_all,
|
17
|
+
:lt, :lt_any, :lt_all,
|
18
|
+
:lteq, :lteq_any, :lteq_all,
|
19
|
+
:gt, :gt_any, :gt_all,
|
20
|
+
:gteq, :gteq_any, :gteq_all,
|
21
|
+
:in, :in_any, :in_all,
|
22
|
+
:not_in, :not_in_any, :not_in_all
|
23
|
+
]
|
24
|
+
|
11
25
|
def self.operator_overload!
|
12
26
|
require 'core_ext/symbol_operators'
|
13
27
|
end
|
@@ -18,9 +32,12 @@ require 'active_support'
|
|
18
32
|
require 'meta_where/column'
|
19
33
|
require 'meta_where/condition'
|
20
34
|
require 'meta_where/compound'
|
35
|
+
require 'meta_where/function'
|
36
|
+
require 'meta_where/join_type'
|
21
37
|
require 'core_ext/symbol'
|
22
38
|
require 'core_ext/hash'
|
23
|
-
require 'meta_where/
|
39
|
+
require 'meta_where/visitors/attribute'
|
40
|
+
require 'meta_where/visitors/predicate'
|
24
41
|
require 'meta_where/association_reflection'
|
25
42
|
require 'meta_where/relation'
|
26
43
|
require 'meta_where/join_dependency'
|
data/meta_where.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{meta_where}
|
8
|
-
s.version = "0.9.
|
8
|
+
s.version = "0.9.9"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Ernie Miller"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-11-15}
|
13
13
|
s.description = %q{
|
14
14
|
MetaWhere offers the ability to call any Arel predicate methods
|
15
15
|
(with a few convenient aliases) on your Model's attributes instead
|
@@ -39,13 +39,17 @@ Gem::Specification.new do |s|
|
|
39
39
|
"lib/core_ext/symbol_operators.rb",
|
40
40
|
"lib/meta_where.rb",
|
41
41
|
"lib/meta_where/association_reflection.rb",
|
42
|
-
"lib/meta_where/builder.rb",
|
43
42
|
"lib/meta_where/column.rb",
|
44
43
|
"lib/meta_where/compound.rb",
|
45
44
|
"lib/meta_where/condition.rb",
|
45
|
+
"lib/meta_where/function.rb",
|
46
46
|
"lib/meta_where/join_dependency.rb",
|
47
|
+
"lib/meta_where/join_type.rb",
|
47
48
|
"lib/meta_where/relation.rb",
|
48
49
|
"lib/meta_where/utility.rb",
|
50
|
+
"lib/meta_where/visitors/attribute.rb",
|
51
|
+
"lib/meta_where/visitors/predicate.rb",
|
52
|
+
"lib/meta_where/visitors/visitor.rb",
|
49
53
|
"meta_where.gemspec",
|
50
54
|
"test/fixtures/companies.yml",
|
51
55
|
"test/fixtures/company.rb",
|
@@ -105,20 +109,20 @@ you're feeling especially appreciative. It'd help me justify this
|
|
105
109
|
|
106
110
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
107
111
|
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
108
|
-
s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.
|
109
|
-
s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.
|
110
|
-
s.add_runtime_dependency(%q<arel>, ["~>
|
112
|
+
s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.2"])
|
113
|
+
s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.2"])
|
114
|
+
s.add_runtime_dependency(%q<arel>, ["~> 2.0.2"])
|
111
115
|
else
|
112
116
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
113
|
-
s.add_dependency(%q<activerecord>, ["~> 3.0.
|
114
|
-
s.add_dependency(%q<activesupport>, ["~> 3.0.
|
115
|
-
s.add_dependency(%q<arel>, ["~>
|
117
|
+
s.add_dependency(%q<activerecord>, ["~> 3.0.2"])
|
118
|
+
s.add_dependency(%q<activesupport>, ["~> 3.0.2"])
|
119
|
+
s.add_dependency(%q<arel>, ["~> 2.0.2"])
|
116
120
|
end
|
117
121
|
else
|
118
122
|
s.add_dependency(%q<shoulda>, [">= 0"])
|
119
|
-
s.add_dependency(%q<activerecord>, ["~> 3.0.
|
120
|
-
s.add_dependency(%q<activesupport>, ["~> 3.0.
|
121
|
-
s.add_dependency(%q<arel>, ["~>
|
123
|
+
s.add_dependency(%q<activerecord>, ["~> 3.0.2"])
|
124
|
+
s.add_dependency(%q<activesupport>, ["~> 3.0.2"])
|
125
|
+
s.add_dependency(%q<arel>, ["~> 2.0.2"])
|
122
126
|
end
|
123
127
|
end
|
124
128
|
|
data/test/test_relations.rb
CHANGED
@@ -29,6 +29,35 @@ class TestRelations < Test::Unit::TestCase
|
|
29
29
|
assert_equal results.first, Company.find_by_name('Initech')
|
30
30
|
end
|
31
31
|
|
32
|
+
should "allow selection of join type in association joins" do
|
33
|
+
assert_match /INNER JOIN/, @r.joins(:developers.inner).to_sql
|
34
|
+
assert_match /LEFT OUTER JOIN/, @r.joins(:developers.outer).to_sql
|
35
|
+
end
|
36
|
+
|
37
|
+
should "only join once even if two join types are used" do
|
38
|
+
assert_equal 1, @r.joins(:developers.inner, :developers.outer).to_sql.scan("JOIN").size
|
39
|
+
end
|
40
|
+
|
41
|
+
should "allow SQL functions via Symbol#func" do
|
42
|
+
assert_equal @r.where(:name.in => ['Initech', 'Mission Data']), @r.joins(:developers).group('companies.id').having(:developers => {:count.func(:id).gt => 2}).all
|
43
|
+
end
|
44
|
+
|
45
|
+
should "allow SQL functions via Symbol#[]" do
|
46
|
+
assert_equal @r.where(:name.in => ['Initech', 'Mission Data']), @r.joins(:developers).group('companies.id').having(:developers => {:count[:id].gt => 2}).all
|
47
|
+
end
|
48
|
+
|
49
|
+
should "allow SQL functions in select clause" do
|
50
|
+
assert_equal [3,2,3], @r.joins(:developers).group('companies.id').select(:count[Developer.arel_table[:id]].as(:developers_count)).map {|c| c.developers_count}
|
51
|
+
end
|
52
|
+
|
53
|
+
should "allow operators on MetaWhere::Function objects" do
|
54
|
+
assert_equal @r.where(:name.in => ['Initech', 'Mission Data']), @r.joins(:developers).group('companies.id').having(:developers => [:count[:id] > 2]).all
|
55
|
+
end
|
56
|
+
|
57
|
+
should "join multiple parameters to an SQL function with commas" do
|
58
|
+
assert_match /concat\("companies"."id","companies"."name"\) LIKE '%blah%'/, @r.where(:concat[:id,:name].matches => '%blah%').to_sql
|
59
|
+
end
|
60
|
+
|
32
61
|
should "create new records with values from equality predicates" do
|
33
62
|
assert_equal "New Company",
|
34
63
|
@r.where(:name => 'New Company').new.name
|
@@ -59,6 +88,12 @@ class TestRelations < Test::Unit::TestCase
|
|
59
88
|
assert_equal results.first, Company.find_by_name('Initech')
|
60
89
|
end
|
61
90
|
|
91
|
+
should "behave as expected with empty arrays" do
|
92
|
+
none = @r.where("3 = 1").all
|
93
|
+
assert_equal none, @r.where(:name => []).all
|
94
|
+
assert_equal none, @r.where(:name.in => []).all
|
95
|
+
end
|
96
|
+
|
62
97
|
should "allow multiple condition params in a single where" do
|
63
98
|
results = @r.where(['name like ?', '%tech'], :created_at => 100.years.ago..Time.now)
|
64
99
|
assert_equal 1, results.size
|
@@ -92,6 +127,21 @@ class TestRelations < Test::Unit::TestCase
|
|
92
127
|
@r.joins(:data_types).where(:data_types => [:dec >= 2, :dec <= 5]).all
|
93
128
|
end
|
94
129
|
|
130
|
+
should "allow nested conditions hashes to have MetaWhere::Condition values" do
|
131
|
+
assert_equal @r.joins(:data_types).where(:data_types => {:dec.gt => 2}).all,
|
132
|
+
@r.joins(:data_types).where(:data_types => :dec > 2).all
|
133
|
+
end
|
134
|
+
|
135
|
+
should "allow nested conditions hashes to have MetaWhere::And values" do
|
136
|
+
assert_equal @r.joins(:data_types).where(:data_types => {:dec => 2..5}).all,
|
137
|
+
@r.joins(:data_types).where(:data_types => ((:dec >= 2) & (:dec <= 5))).all
|
138
|
+
end
|
139
|
+
|
140
|
+
should "allow nested conditions hashes to have MetaWhere::Or values" do
|
141
|
+
assert_equal @r.joins(:data_types).where(:data_types => [:dec.gteq % 2 | :bln.eq % true]).all,
|
142
|
+
@r.joins(:data_types).where(:data_types => ((:dec >= 2) | (:bln >> true))).all
|
143
|
+
end
|
144
|
+
|
95
145
|
should "allow combinations of options that no sane developer would ever try to use" do
|
96
146
|
assert_equal @r.find_all_by_name('Initech'),
|
97
147
|
@r.joins(:data_types, :developers => [:projects, :notes]).
|
@@ -165,7 +215,7 @@ class TestRelations < Test::Unit::TestCase
|
|
165
215
|
end
|
166
216
|
end
|
167
217
|
|
168
|
-
context "A merged relation" do
|
218
|
+
context "A merged relation with a different base class" do
|
169
219
|
setup do
|
170
220
|
@r = Developer.where(:salary.gteq % 70000) & Company.where(:name.matches % 'Initech')
|
171
221
|
end
|
@@ -179,7 +229,18 @@ class TestRelations < Test::Unit::TestCase
|
|
179
229
|
end
|
180
230
|
end
|
181
231
|
|
182
|
-
context "A merged relation with
|
232
|
+
context "A merged relation with a different base class and a MetaWhere::JoinType in joins" do
|
233
|
+
setup do
|
234
|
+
@r = Developer.where(:salary.gteq % 70000) & Company.where(:name.matches % 'Initech').joins(:data_types.outer)
|
235
|
+
end
|
236
|
+
|
237
|
+
should "merge the JoinType under the association for the merged relation" do
|
238
|
+
assert_match /LEFT OUTER JOIN #{DataType.quoted_table_name} ON #{DataType.quoted_table_name}."company_id" = #{Company.quoted_table_name}."id"/,
|
239
|
+
@r.to_sql
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
context "A merged relation with with a different base class and an alternate association" do
|
183
244
|
setup do
|
184
245
|
@r = Company.scoped.merge(Developer.where(:salary.gt => 70000), :slackers)
|
185
246
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 9
|
8
|
-
-
|
9
|
-
version: 0.9.
|
8
|
+
- 9
|
9
|
+
version: 0.9.9
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ernie Miller
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-11-15 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -41,8 +41,8 @@ dependencies:
|
|
41
41
|
segments:
|
42
42
|
- 3
|
43
43
|
- 0
|
44
|
-
-
|
45
|
-
version: 3.0.
|
44
|
+
- 2
|
45
|
+
version: 3.0.2
|
46
46
|
type: :runtime
|
47
47
|
version_requirements: *id002
|
48
48
|
- !ruby/object:Gem::Dependency
|
@@ -56,8 +56,8 @@ dependencies:
|
|
56
56
|
segments:
|
57
57
|
- 3
|
58
58
|
- 0
|
59
|
-
-
|
60
|
-
version: 3.0.
|
59
|
+
- 2
|
60
|
+
version: 3.0.2
|
61
61
|
type: :runtime
|
62
62
|
version_requirements: *id003
|
63
63
|
- !ruby/object:Gem::Dependency
|
@@ -69,10 +69,10 @@ dependencies:
|
|
69
69
|
- - ~>
|
70
70
|
- !ruby/object:Gem::Version
|
71
71
|
segments:
|
72
|
-
-
|
72
|
+
- 2
|
73
73
|
- 0
|
74
|
-
-
|
75
|
-
version:
|
74
|
+
- 2
|
75
|
+
version: 2.0.2
|
76
76
|
type: :runtime
|
77
77
|
version_requirements: *id004
|
78
78
|
description: "\n MetaWhere offers the ability to call any Arel predicate methods\n (with a few convenient aliases) on your Model's attributes instead\n of the ones normally offered by ActiveRecord's hash parameters. It also\n adds convenient syntax for order clauses, smarter mapping of nested hash\n conditions, and a debug_sql method to see the real SQL your code is\n generating without running it against the database. If you like the new\n AR 3.0 query interface, you'll love it with MetaWhere.\n "
|
@@ -99,13 +99,17 @@ files:
|
|
99
99
|
- lib/core_ext/symbol_operators.rb
|
100
100
|
- lib/meta_where.rb
|
101
101
|
- lib/meta_where/association_reflection.rb
|
102
|
-
- lib/meta_where/builder.rb
|
103
102
|
- lib/meta_where/column.rb
|
104
103
|
- lib/meta_where/compound.rb
|
105
104
|
- lib/meta_where/condition.rb
|
105
|
+
- lib/meta_where/function.rb
|
106
106
|
- lib/meta_where/join_dependency.rb
|
107
|
+
- lib/meta_where/join_type.rb
|
107
108
|
- lib/meta_where/relation.rb
|
108
109
|
- lib/meta_where/utility.rb
|
110
|
+
- lib/meta_where/visitors/attribute.rb
|
111
|
+
- lib/meta_where/visitors/predicate.rb
|
112
|
+
- lib/meta_where/visitors/visitor.rb
|
109
113
|
- meta_where.gemspec
|
110
114
|
- test/fixtures/companies.yml
|
111
115
|
- test/fixtures/company.rb
|
data/lib/meta_where/builder.rb
DELETED
@@ -1,84 +0,0 @@
|
|
1
|
-
require 'meta_where/utility'
|
2
|
-
|
3
|
-
module MetaWhere
|
4
|
-
class Builder
|
5
|
-
include MetaWhere::Utility
|
6
|
-
attr_reader :join_dependency
|
7
|
-
|
8
|
-
def initialize(join_dependency)
|
9
|
-
@join_dependency = join_dependency
|
10
|
-
@engine = join_dependency.join_base.arel_engine
|
11
|
-
@default_table = Arel::Table.new(join_dependency.join_base.table_name, :engine => @engine)
|
12
|
-
end
|
13
|
-
|
14
|
-
def build_table(parent_or_table_name = nil)
|
15
|
-
if parent_or_table_name.is_a?(Symbol)
|
16
|
-
Arel::Table.new(parent_or_table_name, :engine => @engine)
|
17
|
-
elsif parent_or_table_name.respond_to?(:aliased_table_name)
|
18
|
-
Arel::Table.new(parent_or_table_name.table_name, :as => parent_or_table_name.aliased_table_name, :engine => @engine)
|
19
|
-
else
|
20
|
-
@default_table
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def build_predicates_from_hash(attributes, parent = nil)
|
25
|
-
table = build_table(parent)
|
26
|
-
predicates = attributes.map do |column, value|
|
27
|
-
if value.is_a?(Hash)
|
28
|
-
association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
|
29
|
-
build_predicates_from_hash(value, association || column)
|
30
|
-
elsif value.is_a?(MetaWhere::Condition)
|
31
|
-
association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
|
32
|
-
value.to_predicate(self, association || column)
|
33
|
-
elsif value.is_a?(Array) && !value.empty? && value.all? {|v| v.respond_to?(:to_predicate)}
|
34
|
-
association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
|
35
|
-
value.map {|val| val.to_predicate(self, association || column)}
|
36
|
-
else
|
37
|
-
if column.is_a?(MetaWhere::Column)
|
38
|
-
method = column.method
|
39
|
-
column = column.column
|
40
|
-
else
|
41
|
-
column = column.to_s
|
42
|
-
method = method_from_value(value)
|
43
|
-
end
|
44
|
-
|
45
|
-
if column.include?('.')
|
46
|
-
table_name, column = column.split('.', 2)
|
47
|
-
table = Arel::Table.new(table_name, :engine => parent.arel_engine)
|
48
|
-
end
|
49
|
-
|
50
|
-
unless attribute = table[column]
|
51
|
-
raise ::ActiveRecord::StatementInvalid, "No attribute named `#{column}` exists for table `#{table.name}`"
|
52
|
-
end
|
53
|
-
|
54
|
-
unless valid_comparison_method?(method)
|
55
|
-
raise ::ActiveRecord::StatementInvalid, "No comparison method named `#{method}` exists for column `#{column}`"
|
56
|
-
end
|
57
|
-
|
58
|
-
attribute.send(method, *args_for_predicate(method.to_s, value))
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
predicates.flatten
|
63
|
-
end
|
64
|
-
|
65
|
-
def build_attributes_from_hash(attributes, parent = nil)
|
66
|
-
table = build_table(parent)
|
67
|
-
built_attributes = attributes.map do |column, value|
|
68
|
-
if value.is_a?(Hash)
|
69
|
-
association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
|
70
|
-
build_attributes_from_hash(value, association || column)
|
71
|
-
elsif value.is_a?(Array) && value.all? {|v| v.respond_to?(:to_attribute)}
|
72
|
-
association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
|
73
|
-
value.map {|val| val.to_attribute(self, association || column)}
|
74
|
-
else
|
75
|
-
association = parent.is_a?(Symbol) ? nil : @join_dependency.find_join_association(column, parent)
|
76
|
-
value.respond_to?(:to_attribute) ? value.to_attribute(self, association || column) : value
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
built_attributes.flatten
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|