aub-record_filter 0.2.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +205 -0
- data/VERSION.yml +1 -1
- data/lib/record_filter.rb +5 -3
- data/lib/record_filter/active_record.rb +30 -2
- data/lib/record_filter/conjunctions.rb +10 -0
- data/lib/record_filter/dsl.rb +1 -1
- data/lib/record_filter/dsl/conjunction.rb +4 -0
- data/lib/record_filter/dsl/conjunction_dsl.rb +20 -0
- data/lib/record_filter/dsl/dsl.rb +1 -1
- data/lib/record_filter/dsl/named_filter.rb +12 -0
- data/lib/record_filter/dsl/restriction.rb +5 -0
- data/lib/record_filter/filter.rb +1 -14
- data/lib/record_filter/join.rb +3 -4
- data/lib/record_filter/query.rb +42 -19
- data/lib/record_filter/restrictions.rb +5 -0
- data/spec/exception_spec.rb +62 -0
- data/spec/explicit_join_spec.rb +5 -5
- data/spec/implicit_join_spec.rb +2 -2
- data/spec/limits_and_ordering_spec.rb +0 -42
- data/spec/named_filter_spec.rb +78 -56
- data/spec/restrictions_spec.rb +21 -0
- data/spec/select_spec.rb +4 -4
- data/spec/test.db +0 -0
- metadata +6 -4
data/README.rdoc
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
= record_filter
|
2
|
+
|
3
|
+
record_filter is a DSL for specifying criteria for ActiveRecord queries in pure Ruby.
|
4
|
+
It has support for filters created on the fly and for named filters that are associated with object types.
|
5
|
+
record_filter has the following top-level features:
|
6
|
+
|
7
|
+
* Pure ruby API eliminates the need for hard-coded SQL in most cases.
|
8
|
+
* Works seamlessly with existing ActiveRecord APIs, including named scopes.
|
9
|
+
* Supports creation of ad-hoc filters as well as named filters that can be associated with object types.
|
10
|
+
* Allows chaining of filters with each other and with named scopes to create complex queries.
|
11
|
+
* Takes advantage of the associations in your ActiveRecord objects for a clean implicit join API.
|
12
|
+
|
13
|
+
== Installation
|
14
|
+
|
15
|
+
gem install outoftime-record_filter --source=http://gems.github.com
|
16
|
+
|
17
|
+
== Usage
|
18
|
+
|
19
|
+
=== Ad-hoc filters
|
20
|
+
|
21
|
+
Post.filter do
|
22
|
+
with(:permalink, 'blog-post')
|
23
|
+
having(:blog).with(:name, 'Blog')
|
24
|
+
end
|
25
|
+
|
26
|
+
This could be expressed in ActiveRecord as:
|
27
|
+
|
28
|
+
Post.find(:all, :joins => :blog, :conditions => ['posts.permalink = ? AND blogs.name = ?', 'blog-post', 'Blog')
|
29
|
+
|
30
|
+
and it returns the same result, a list of Post objects that are returned from the query.
|
31
|
+
|
32
|
+
=== Named filters
|
33
|
+
|
34
|
+
class Post < ActiveRecord::Base
|
35
|
+
named_filter(:with_title) do |title|
|
36
|
+
with(:title, title)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Post.with_title('posted')
|
41
|
+
|
42
|
+
This is the same as the following code using named scopes, and returns the same result:
|
43
|
+
|
44
|
+
class Post < ActiveRecord::Base
|
45
|
+
named_scope :with_title, lambda { |title| { :conditions => ['title = ?', title] }}
|
46
|
+
end
|
47
|
+
|
48
|
+
Post.with_title('scoped')
|
49
|
+
|
50
|
+
=== Restrictions
|
51
|
+
|
52
|
+
Restrictions are specified through the API using the 'with' function. The first argument to 'with' should be the
|
53
|
+
name of the field that the restriction applies to. All restriction types can be negated by chaining the 'with'
|
54
|
+
method with a call to 'not', as seen in some examples below.
|
55
|
+
|
56
|
+
==== Equality
|
57
|
+
|
58
|
+
If a second argument is supplied, it is assumed that you are
|
59
|
+
expressing an equality condition and that argument is used as the value.
|
60
|
+
|
61
|
+
with(:title, 'abc') # :conditions => ['title = ?', 'abc']
|
62
|
+
|
63
|
+
Which can be negated with:
|
64
|
+
|
65
|
+
with(:title, 'abc').not # :conditions => ['title <> ?', 'abc']
|
66
|
+
|
67
|
+
For the more verbose among us, this can also be specified as:
|
68
|
+
|
69
|
+
with(:title).equal_to('abc') # :conditions => ['title = ?', 'abc']
|
70
|
+
|
71
|
+
Other types of restrictions are specified by omitting the second argument to 'with' and chaining it with one of the
|
72
|
+
restriction methods.
|
73
|
+
|
74
|
+
==== Comparison operators
|
75
|
+
|
76
|
+
with(:price).greater_than(10) # :conditions => ['price > ?', 10]
|
77
|
+
|
78
|
+
with(:created_at).less_than(2.days.ago) # :conditions => ['created_at < ?', 2.days.ago]
|
79
|
+
|
80
|
+
These methods can also have _or_equal_to tagged onto the end, to obvious affect, and all of the comparison operators are aliased to
|
81
|
+
their standard single-character variants:
|
82
|
+
|
83
|
+
gt, gte, lt and lte
|
84
|
+
|
85
|
+
==== IS NULL
|
86
|
+
|
87
|
+
with(:price, nil) # :conditions => ['price IS NULL']
|
88
|
+
|
89
|
+
This short form can also be made explicit by using the is_null, null, or nil functions on with:
|
90
|
+
|
91
|
+
with(:price).is_null # :conditions => ['price IS NULL']
|
92
|
+
|
93
|
+
It can be negated either by chaining with the 'not' function or by using is_not_null:
|
94
|
+
|
95
|
+
with(:price).is_not_null # :conditions => ['price IS NOT NULL']
|
96
|
+
with(:price).is_null.not # "
|
97
|
+
|
98
|
+
==== IN
|
99
|
+
|
100
|
+
with(:id).in([1, 2, 3]) # :conditions => ['id IN (?)', [1, 2, 3]]
|
101
|
+
|
102
|
+
==== BETWEEN
|
103
|
+
|
104
|
+
with(:id).between(1, 5) # :conditions => ['id BETWEEN ? AND ?', 1, 5]
|
105
|
+
|
106
|
+
The argument to between can also be either a tuple or a range
|
107
|
+
|
108
|
+
with(:created_at).between([Time.now, 3.days.ago]) # :conditions => ['created_at BETWEEN ? AND ?', Time.now, 3.days.ago]
|
109
|
+
|
110
|
+
with(:price).between(1..5) # :conditions => ['price BETWEEN ? AND ?', 1, 5]
|
111
|
+
|
112
|
+
==== LIKE
|
113
|
+
|
114
|
+
with(:title).like('%help%') # :conditions => ['title LIKE ?', '%help%']
|
115
|
+
|
116
|
+
|
117
|
+
=== Implicit Joins
|
118
|
+
|
119
|
+
Implicit joins are specified using the 'having' function, which takes as its argument the name of an association to join on.
|
120
|
+
|
121
|
+
having(:comments) # :joins => :comments
|
122
|
+
|
123
|
+
This function can be chained with calls to 'with' in order to specify conditions on the joined table:
|
124
|
+
|
125
|
+
having(:comments).with(:created_at).gt(2.days.ago) # :joins => :comments, :conditions => ['comments.created_at > ?', 2.days.ago]
|
126
|
+
|
127
|
+
It can also take a block that can have any number of conditions or other clauses (including other joins) in it:
|
128
|
+
|
129
|
+
having(:comments) do
|
130
|
+
with(:created_at).gt(2.days.ago)
|
131
|
+
having(:author).with(:name, 'Bubba')
|
132
|
+
end
|
133
|
+
|
134
|
+
The 'having' function can also take :inner, :left or :right as its second argument in order to specify that a particular join type
|
135
|
+
should be used.
|
136
|
+
|
137
|
+
=== Explicit joins
|
138
|
+
|
139
|
+
In cases where there is no ActiveRecord association that can be used to specify an implicit join, explicit joins are also
|
140
|
+
supported, using the 'join' function. Its arguments are the class to be joined against, the join type (:inner, left or :right) and
|
141
|
+
an optional alias for the join table. A block should also be supplied in order to specify the columns to use for the join using the
|
142
|
+
'on' method.
|
143
|
+
|
144
|
+
Post.filter do
|
145
|
+
join(Comment, :inner, :posts__comments_alias) do
|
146
|
+
on(:id => :commentable_id)
|
147
|
+
on(:commentable_type, 'Post')
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
=== Conjunctions
|
152
|
+
|
153
|
+
The following conjunction types are supported:
|
154
|
+
|
155
|
+
* any_of
|
156
|
+
* all_of
|
157
|
+
* none_of
|
158
|
+
* not_all_of
|
159
|
+
|
160
|
+
Each takes a block that can contain conditions or joins and will apply the expected boolean logic to the combination.
|
161
|
+
|
162
|
+
any_of
|
163
|
+
with(:price, 2)
|
164
|
+
with(:price).gt(100)
|
165
|
+
end
|
166
|
+
|
167
|
+
# :conditions => ['price = ? OR price > ?', 2, 100]
|
168
|
+
|
169
|
+
=== Limits and ordering
|
170
|
+
|
171
|
+
limit(20)
|
172
|
+
|
173
|
+
order(:id, :desc)
|
174
|
+
|
175
|
+
Multiple order clauses can be specified, and they will be applied in the order in which they are specified.
|
176
|
+
When joins are used, order can also take a hash as the first argument that leads to the column to order on through the joins:
|
177
|
+
|
178
|
+
having(:comments).with(:offensive, true)
|
179
|
+
order(:comments => :id, :desc)
|
180
|
+
|
181
|
+
|
182
|
+
== LICENSE:
|
183
|
+
|
184
|
+
(The MIT License)
|
185
|
+
|
186
|
+
Copyright (c) 2008 Mat Brown
|
187
|
+
|
188
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
189
|
+
a copy of this software and associated documentation files (the
|
190
|
+
'Software'), to deal in the Software without restriction, including
|
191
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
192
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
193
|
+
permit persons to whom the Software is furnished to do so, subject to
|
194
|
+
the following conditions:
|
195
|
+
|
196
|
+
The above copyright notice and this permission notice shall be
|
197
|
+
included in all copies or substantial portions of the Software.
|
198
|
+
|
199
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
200
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
201
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
202
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
203
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
204
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
205
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/VERSION.yml
CHANGED
data/lib/record_filter.rb
CHANGED
@@ -7,7 +7,9 @@ require 'active_record'
|
|
7
7
|
end
|
8
8
|
|
9
9
|
module RecordFilter
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
AssociationNotFoundException = Class.new(StandardError)
|
11
|
+
ColumnNotFoundException = Class.new(StandardError)
|
12
|
+
InvalidFilterException = Class.new(StandardError)
|
13
|
+
InvalidJoinException = Class.new(StandardError)
|
14
|
+
NamedFilterNotFoundException = Class.new(StandardError)
|
13
15
|
end
|
@@ -1,14 +1,33 @@
|
|
1
1
|
module RecordFilter
|
2
|
+
# The ActiveRecordExtension module is mixed in to ActiveRecord::Base to form the
|
3
|
+
# top-level API for interacting with record_filter. It adds public methods for
|
4
|
+
# executing ad-hoc filters as well as for creating and querying named filters.
|
2
5
|
module ActiveRecordExtension
|
3
6
|
module ClassMethods
|
4
7
|
|
8
|
+
# Execute an ad-hoc filter
|
9
|
+
#
|
10
|
+
# ==== Parameters
|
11
|
+
# block::
|
12
|
+
# A block that specifies the contents of the filter.
|
13
|
+
#
|
14
|
+
# ==== Returns
|
15
|
+
# Filter:: The Filter object resulting from the query, which can be
|
16
|
+
# treated as an array of the results.
|
17
|
+
#
|
18
|
+
# ==== Example
|
19
|
+
# Blog.filter do
|
20
|
+
# having(:posts).with(:name, nil)
|
21
|
+
# end
|
22
|
+
#—
|
23
|
+
# @public
|
5
24
|
def filter(&block)
|
6
25
|
Filter.new(self, nil, &block)
|
7
26
|
end
|
8
27
|
|
9
28
|
def named_filter(name, &block)
|
10
29
|
return if named_filters.include?(name.to_sym)
|
11
|
-
|
30
|
+
local_named_filters << name.to_sym
|
12
31
|
DSL::DSL::subclass(self).module_eval do
|
13
32
|
define_method(name, &block)
|
14
33
|
end
|
@@ -21,10 +40,19 @@ module RecordFilter
|
|
21
40
|
end
|
22
41
|
|
23
42
|
def named_filters
|
24
|
-
|
43
|
+
result = local_named_filters.dup
|
44
|
+
result.concat(superclass.named_filters) if (superclass && superclass.respond_to?(:named_filters))
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def local_named_filters
|
51
|
+
@local_named_filters ||= []
|
25
52
|
end
|
26
53
|
end
|
27
54
|
end
|
28
55
|
end
|
29
56
|
|
30
57
|
ActiveRecord::Base.send(:extend, RecordFilter::ActiveRecordExtension::ClassMethods)
|
58
|
+
|
@@ -30,6 +30,8 @@ module RecordFilter
|
|
30
30
|
result.add_order(step.column, step.direction)
|
31
31
|
when DSL::GroupBy
|
32
32
|
result.add_group_by(step.column)
|
33
|
+
when DSL::NamedFilter
|
34
|
+
result.add_named_filter(step.name, step.args)
|
33
35
|
end
|
34
36
|
end
|
35
37
|
result
|
@@ -81,6 +83,14 @@ module RecordFilter
|
|
81
83
|
@limit, @offset = limit, offset
|
82
84
|
end
|
83
85
|
|
86
|
+
def add_named_filter(name, args)
|
87
|
+
unless @table.model_class.named_filters.include?(name.to_sym)
|
88
|
+
raise NamedFilterNotFoundException.new("The named filter #{name} was not found in #{@table.model_class}")
|
89
|
+
end
|
90
|
+
query = Query.new(@table.model_class, name, *args)
|
91
|
+
self << self.class.create_from(query.dsl_conjunction, @table)
|
92
|
+
end
|
93
|
+
|
84
94
|
def <<(restriction)
|
85
95
|
@restrictions << restriction
|
86
96
|
end
|
data/lib/record_filter/dsl.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
%w(class_join conjunction conjunction_dsl dsl group_by join join_dsl join_condition limit order restriction).each { |file| require File.join(File.dirname(__FILE__), 'dsl', file) }
|
1
|
+
%w(class_join conjunction conjunction_dsl dsl group_by join join_dsl join_condition limit named_filter order restriction).each { |file| require File.join(File.dirname(__FILE__), 'dsl', file) }
|
2
2
|
|
3
3
|
module RecordFilter
|
4
4
|
module DSL
|
@@ -53,6 +53,26 @@ module RecordFilter
|
|
53
53
|
def filter_class
|
54
54
|
@model_class
|
55
55
|
end
|
56
|
+
|
57
|
+
def limit(offset_or_limit, limit=nil)
|
58
|
+
raise InvalidFilterException.new('Calls to limit can only be made in the outer block of a filter.')
|
59
|
+
end
|
60
|
+
|
61
|
+
def order(column, direction=:asc)
|
62
|
+
raise InvalidFilterException.new('Calls to order can only be made in the outer block of a filter.')
|
63
|
+
end
|
64
|
+
|
65
|
+
def group_by(column)
|
66
|
+
raise InvalidFilterException.new('Calls to group_by can only be made in the outer block of a filter.')
|
67
|
+
end
|
68
|
+
|
69
|
+
def on(column, value=Restriction::DEFAULT_VALUE)
|
70
|
+
raise InvalidFilterException.new('Calls to on can only be made in the block of a call to join.')
|
71
|
+
end
|
72
|
+
|
73
|
+
def method_missing(method, *args)
|
74
|
+
@conjunction.add_named_filter(method, *args)
|
75
|
+
end
|
56
76
|
end
|
57
77
|
end
|
58
78
|
end
|
data/lib/record_filter/filter.rb
CHANGED
@@ -12,10 +12,7 @@ module RecordFilter
|
|
12
12
|
@current_scoped_methods = clazz.send(:current_scoped_methods)
|
13
13
|
@clazz = clazz
|
14
14
|
|
15
|
-
@
|
16
|
-
@dsl.instance_eval(&block) if block
|
17
|
-
@dsl.send(named_filter, *args) if named_filter && @dsl.respond_to?(named_filter)
|
18
|
-
@query = Query.new(@clazz, @dsl.conjunction)
|
15
|
+
@query = Query.new(@clazz, named_filter, *args, &block)
|
19
16
|
end
|
20
17
|
|
21
18
|
def first(*args)
|
@@ -90,16 +87,6 @@ module RecordFilter
|
|
90
87
|
end
|
91
88
|
end
|
92
89
|
|
93
|
-
def dsl_for_named_filter(clazz, named_filter)
|
94
|
-
return DSL::DSL.create(clazz) if named_filter.blank?
|
95
|
-
while (clazz)
|
96
|
-
dsl = DSL::DSL::SUBCLASSES.has_key?(clazz.name.to_sym) ? DSL::DSL::SUBCLASSES[clazz.name.to_sym] : nil
|
97
|
-
return DSL::DSL.create(clazz) if dsl && dsl.instance_methods(false).include?(named_filter.to_s)
|
98
|
-
clazz = clazz.superclass
|
99
|
-
end
|
100
|
-
nil
|
101
|
-
end
|
102
|
-
|
103
90
|
def loaded_data
|
104
91
|
@loaded_data ||= do_with_scope do
|
105
92
|
@clazz.find(:all)
|
data/lib/record_filter/join.rb
CHANGED
@@ -22,7 +22,7 @@ module RecordFilter
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def requires_distinct_select?
|
25
|
-
[:
|
25
|
+
[:right, :left].include?(@join_type)
|
26
26
|
end
|
27
27
|
|
28
28
|
protected
|
@@ -55,9 +55,8 @@ module RecordFilter
|
|
55
55
|
def join_type_string
|
56
56
|
@join_type_string ||= case(@join_type)
|
57
57
|
when :inner then 'INNER'
|
58
|
-
when :left then 'LEFT'
|
59
|
-
when :
|
60
|
-
when :outer then 'OUTER'
|
58
|
+
when :left then 'LEFT OUTER'
|
59
|
+
when :right then 'RIGHT OUTER'
|
61
60
|
else nil
|
62
61
|
end
|
63
62
|
end
|
data/lib/record_filter/query.rb
CHANGED
@@ -1,31 +1,54 @@
|
|
1
1
|
module RecordFilter
|
2
2
|
class Query
|
3
3
|
|
4
|
-
|
4
|
+
attr_reader :dsl_conjunction
|
5
|
+
attr_reader :conjunction
|
6
|
+
|
7
|
+
def initialize(clazz, named_filter, *args, &block)
|
8
|
+
dsl = dsl_for_named_filter(clazz, named_filter)
|
9
|
+
dsl.instance_eval(&block) if block
|
10
|
+
dsl.send(named_filter, *args) if named_filter && dsl.respond_to?(named_filter)
|
11
|
+
|
5
12
|
@table = RecordFilter::Table.new(clazz)
|
6
|
-
@
|
13
|
+
@dsl_conjunction = dsl.conjunction
|
14
|
+
@conjunction = RecordFilter::Conjunctions::Base.create_from(@dsl_conjunction, @table)
|
7
15
|
end
|
8
16
|
|
9
17
|
def to_find_params(count_query=false)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
18
|
+
@params_cache ||= {}
|
19
|
+
@params_cache[count_query] ||= begin
|
20
|
+
params = {}
|
21
|
+
conditions = @conjunction.to_conditions
|
22
|
+
params = { :conditions => conditions } if conditions
|
23
|
+
joins = @table.all_joins
|
24
|
+
params[:joins] = joins.map { |join| join.to_sql } * ' ' unless joins.empty?
|
25
|
+
if (joins.any? { |j| j.requires_distinct_select? })
|
26
|
+
if count_query
|
27
|
+
params[:select] = "DISTINCT #{@table.model_class.quoted_table_name}.#{@table.model_class.primary_key}"
|
28
|
+
else
|
29
|
+
params[:select] = "DISTINCT #{@table.model_class.quoted_table_name}.*"
|
30
|
+
end
|
20
31
|
end
|
32
|
+
orders = @table.orders
|
33
|
+
params[:order] = orders.map { |order| order.to_sql } * ', ' unless orders.empty?
|
34
|
+
group_bys = @table.group_bys
|
35
|
+
params[:group] = group_bys.map { |group_by| group_by.to_sql } * ', ' unless group_bys.empty?
|
36
|
+
params[:limit] = @conjunction.limit if @conjunction.limit
|
37
|
+
params[:offset] = @conjunction.offset if @conjunction.offset
|
38
|
+
params
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
def dsl_for_named_filter(clazz, named_filter)
|
45
|
+
return DSL::DSL.create(clazz) if named_filter.blank?
|
46
|
+
while (clazz)
|
47
|
+
dsl = DSL::DSL.subclass(clazz)
|
48
|
+
return DSL::DSL.create(clazz) if dsl && dsl.instance_methods(false).include?(named_filter.to_s)
|
49
|
+
clazz = clazz.superclass
|
21
50
|
end
|
22
|
-
|
23
|
-
params[:order] = orders.map { |order| order.to_sql } * ', ' unless orders.empty?
|
24
|
-
group_bys = @table.group_bys
|
25
|
-
params[:group] = group_bys.map { |group_by| group_by.to_sql } * ', ' unless group_bys.empty?
|
26
|
-
params[:limit] = @conjunction.limit if @conjunction.limit
|
27
|
-
params[:offset] = @conjunction.offset if @conjunction.offset
|
28
|
-
params
|
51
|
+
nil
|
29
52
|
end
|
30
53
|
end
|
31
54
|
end
|
data/spec/exception_spec.rb
CHANGED
@@ -102,4 +102,66 @@ describe 'raising exceptions' do
|
|
102
102
|
}.should raise_error(RecordFilter::InvalidJoinException)
|
103
103
|
end
|
104
104
|
end
|
105
|
+
|
106
|
+
describe 'limiting methods within joins and conjunctions' do
|
107
|
+
it 'should not allow calls to limit within joins' do
|
108
|
+
lambda {
|
109
|
+
Post.filter do
|
110
|
+
having(:photo) do
|
111
|
+
limit 2
|
112
|
+
end
|
113
|
+
end
|
114
|
+
}.should raise_error(RecordFilter::InvalidFilterException)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should not allow calls to order within joins' do
|
118
|
+
lambda {
|
119
|
+
Post.filter do
|
120
|
+
having(:photo) do
|
121
|
+
order :id
|
122
|
+
end
|
123
|
+
end
|
124
|
+
}.should raise_error(RecordFilter::InvalidFilterException)
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'should not allow calls to limit within conjunctions' do
|
128
|
+
lambda {
|
129
|
+
Post.filter do
|
130
|
+
all_of do
|
131
|
+
limit 2
|
132
|
+
end
|
133
|
+
end
|
134
|
+
}.should raise_error(RecordFilter::InvalidFilterException)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should not allow calls to order within joins' do
|
138
|
+
lambda {
|
139
|
+
Post.filter do
|
140
|
+
all_of do
|
141
|
+
order :id
|
142
|
+
end
|
143
|
+
end
|
144
|
+
}.should raise_error(RecordFilter::InvalidFilterException)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'limiting calls to on' do
|
149
|
+
it 'should not allow calls to on in the outer scope' do
|
150
|
+
lambda {
|
151
|
+
Post.filter do
|
152
|
+
on(:a => :b)
|
153
|
+
end
|
154
|
+
}.should raise_error(RecordFilter::InvalidFilterException)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
describe 'calling named filters within filters' do
|
159
|
+
it 'should raise an excpetion if the named filter does not exist' do
|
160
|
+
lambda {
|
161
|
+
Post.filter do
|
162
|
+
having(:comments).does_not_exist
|
163
|
+
end
|
164
|
+
}.should raise_error(RecordFilter::NamedFilterNotFoundException)
|
165
|
+
end
|
166
|
+
end
|
105
167
|
end
|
data/spec/explicit_join_spec.rb
CHANGED
@@ -16,7 +16,7 @@ describe 'explicit joins' do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'should add correct join' do
|
19
|
-
Post.last_find[:joins].should == %q(LEFT JOIN "blogs" AS posts_blogs ON "posts".blog_id = posts_blogs.id)
|
19
|
+
Post.last_find[:joins].should == %q(LEFT OUTER JOIN "blogs" AS posts_blogs ON "posts".blog_id = posts_blogs.id)
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'should query against condition on join table' do
|
@@ -36,7 +36,7 @@ describe 'explicit joins' do
|
|
36
36
|
end
|
37
37
|
|
38
38
|
it 'should add correct join' do
|
39
|
-
Review.last_find[:joins].should == %q(LEFT JOIN "features" AS reviews_features ON "reviews".reviewable_id = reviews_features.featurable_id AND "reviews".reviewable_type = reviews_features.featurable_type)
|
39
|
+
Review.last_find[:joins].should == %q(LEFT OUTER JOIN "features" AS reviews_features ON "reviews".reviewable_id = reviews_features.featurable_id AND "reviews".reviewable_type = reviews_features.featurable_type)
|
40
40
|
end
|
41
41
|
|
42
42
|
it 'should query against condition on join table' do
|
@@ -57,7 +57,7 @@ describe 'explicit joins' do
|
|
57
57
|
end
|
58
58
|
|
59
59
|
it 'should add correct join' do
|
60
|
-
Review.last_find[:joins].should == %q(LEFT JOIN "features" AS reviews__Feature ON "reviews".reviewable_id = reviews__Feature.featurable_id AND "reviews".reviewable_type = reviews__Feature.featurable_type AND (reviews__Feature.featurable_type = 'SomeType'))
|
60
|
+
Review.last_find[:joins].should == %q(LEFT OUTER JOIN "features" AS reviews__Feature ON "reviews".reviewable_id = reviews__Feature.featurable_id AND "reviews".reviewable_type = reviews__Feature.featurable_type AND (reviews__Feature.featurable_type = 'SomeType'))
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
@@ -73,7 +73,7 @@ describe 'explicit joins' do
|
|
73
73
|
end
|
74
74
|
|
75
75
|
it 'should add the correct join' do
|
76
|
-
Review.last_find[:joins].should == %q(LEFT JOIN "features" AS reviews__Feature ON (reviews__Feature.featurable_type IS NULL) AND (reviews__Feature.featurable_id >= 12) AND (reviews__Feature.priority <> 6))
|
76
|
+
Review.last_find[:joins].should == %q(LEFT OUTER JOIN "features" AS reviews__Feature ON (reviews__Feature.featurable_type IS NULL) AND (reviews__Feature.featurable_id >= 12) AND (reviews__Feature.priority <> 6))
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
@@ -100,7 +100,7 @@ describe 'explicit joins' do
|
|
100
100
|
end
|
101
101
|
|
102
102
|
it 'should produce the correct join' do
|
103
|
-
Blog.last_find[:joins].should == %q(INNER JOIN "ads" AS blogs__ads ON "blogs".id = blogs__ads.blog_id LEFT JOIN "posts" AS blogs__Post ON "blogs".id = blogs__Post.blog_id INNER JOIN "comments" AS blogs__Post__Comment ON blogs__Post.id = blogs__Post__Comment.post_id AND (blogs__Post__Comment.offensive = 't'))
|
103
|
+
Blog.last_find[:joins].should == %q(INNER JOIN "ads" AS blogs__ads ON "blogs".id = blogs__ads.blog_id LEFT OUTER JOIN "posts" AS blogs__Post ON "blogs".id = blogs__Post.blog_id INNER JOIN "comments" AS blogs__Post__Comment ON blogs__Post.id = blogs__Post__Comment.post_id AND (blogs__Post__Comment.offensive = 't'))
|
104
104
|
end
|
105
105
|
end
|
106
106
|
end
|
data/spec/implicit_join_spec.rb
CHANGED
@@ -224,7 +224,7 @@ describe 'implicit joins' do
|
|
224
224
|
describe 'passing the join type to having' do
|
225
225
|
before do
|
226
226
|
Blog.filter do
|
227
|
-
having(:
|
227
|
+
having(:left, :posts) do
|
228
228
|
with(:permalink, 'ack')
|
229
229
|
end
|
230
230
|
end.inspect
|
@@ -242,7 +242,7 @@ describe 'implicit joins' do
|
|
242
242
|
describe 'passing the join type to having with multiple joins' do
|
243
243
|
before do
|
244
244
|
Blog.filter do
|
245
|
-
having(:
|
245
|
+
having(:left, :posts => :comments) do
|
246
246
|
with(:offensive, true)
|
247
247
|
end
|
248
248
|
end.inspect
|
@@ -118,48 +118,6 @@ describe 'filter qualifiers' do
|
|
118
118
|
Post.last_find[:order].should == %q(posts__photo.path DESC, "posts".permalink ASC)
|
119
119
|
end
|
120
120
|
end
|
121
|
-
|
122
|
-
describe 'limiting methods within joins and conjunctions' do
|
123
|
-
it 'should not allow calls to limit within joins' do
|
124
|
-
lambda {
|
125
|
-
Post.filter do
|
126
|
-
having(:photo) do
|
127
|
-
limit 2
|
128
|
-
end
|
129
|
-
end
|
130
|
-
}.should raise_error(NoMethodError)
|
131
|
-
end
|
132
|
-
|
133
|
-
it 'should not allow calls to order within joins' do
|
134
|
-
lambda {
|
135
|
-
Post.filter do
|
136
|
-
having(:photo) do
|
137
|
-
order :id
|
138
|
-
end
|
139
|
-
end
|
140
|
-
}.should raise_error(NoMethodError)
|
141
|
-
end
|
142
|
-
|
143
|
-
it 'should not allow calls to limit within conjunctions' do
|
144
|
-
lambda {
|
145
|
-
Post.filter do
|
146
|
-
all_of do
|
147
|
-
limit 2
|
148
|
-
end
|
149
|
-
end
|
150
|
-
}.should raise_error(NoMethodError)
|
151
|
-
end
|
152
|
-
|
153
|
-
it 'should not allow calls to order within joins' do
|
154
|
-
lambda {
|
155
|
-
Post.filter do
|
156
|
-
all_of do
|
157
|
-
order :id
|
158
|
-
end
|
159
|
-
end
|
160
|
-
}.should raise_error(NoMethodError)
|
161
|
-
end
|
162
|
-
end
|
163
121
|
end
|
164
122
|
|
165
123
|
describe 'group_by' do
|
data/spec/named_filter_spec.rb
CHANGED
@@ -7,40 +7,43 @@ describe 'named filters' do
|
|
7
7
|
|
8
8
|
describe 'defining a simple filter' do
|
9
9
|
before do
|
10
|
-
|
10
|
+
@blog = Class.new(Blog)
|
11
|
+
@blog.named_filter(:with_test_name) do
|
11
12
|
with :name, 'Test Name'
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
15
16
|
it 'should call the filter through the filter method' do
|
16
|
-
|
17
|
-
|
17
|
+
@blog.with_test_name.inspect
|
18
|
+
@blog.last_find[:conditions].should == [%q("blogs".name = ?), 'Test Name']
|
18
19
|
end
|
19
20
|
|
20
21
|
it 'should call the filter within the block' do
|
21
|
-
|
22
|
+
@blog.filter do
|
22
23
|
with_test_name
|
23
24
|
end.inspect
|
24
|
-
|
25
|
+
@blog.last_find[:conditions].should == [%q("blogs".name = ?), 'Test Name']
|
25
26
|
end
|
26
27
|
end
|
27
28
|
|
28
29
|
describe 'defining a filter with arguments' do
|
29
30
|
before do
|
30
|
-
|
31
|
+
@blog = Class.new(Blog)
|
32
|
+
@blog.named_filter(:with_name) do |name|
|
31
33
|
with :name, name
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
35
37
|
it 'should call the filter with the passed argument' do
|
36
|
-
|
37
|
-
|
38
|
+
@blog.with_name('nice name').inspect
|
39
|
+
@blog.last_find[:conditions].should == [%q("blogs".name = ?), 'nice name']
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
41
43
|
describe 'defining a filter that passes arguments down several levels' do
|
42
44
|
before do
|
43
|
-
|
45
|
+
@blog = Class.new(Blog)
|
46
|
+
@blog.named_filter(:with_name_and_post_with_permalink) do |name, permalink|
|
44
47
|
with :name, name
|
45
48
|
having :posts do
|
46
49
|
with :permalink, permalink
|
@@ -49,29 +52,31 @@ describe 'named filters' do
|
|
49
52
|
end
|
50
53
|
|
51
54
|
it 'should call the filter passing all of the arguments' do
|
52
|
-
|
53
|
-
|
55
|
+
@blog.with_name_and_post_with_permalink('booya', 'ftw').inspect
|
56
|
+
@blog.last_find[:conditions].should ==
|
54
57
|
[%q(("blogs".name = ?) AND (blogs__posts.permalink = ?)), 'booya', 'ftw']
|
55
58
|
end
|
56
59
|
end
|
57
60
|
|
58
61
|
describe 'taking active_record objects as arguments' do
|
59
62
|
it 'should use the id of the object as the actual parameter' do
|
60
|
-
|
63
|
+
post = Class.new(Post)
|
64
|
+
post.named_filter(:with_ar_arg) do |blog|
|
61
65
|
with(:blog_id, blog)
|
62
66
|
end
|
63
67
|
blog = Blog.create
|
64
|
-
|
65
|
-
|
68
|
+
post.with_ar_arg(blog).inspect
|
69
|
+
post.last_find[:conditions].should == [%q("posts".blog_id = ?), blog.id]
|
66
70
|
end
|
67
71
|
end
|
68
72
|
|
69
73
|
describe 'using filters in subclasses' do
|
70
74
|
before do
|
71
|
-
|
75
|
+
@comment = Class.new(Comment)
|
76
|
+
@comment.named_filter(:with_contents) do |*args|
|
72
77
|
with :contents, args[0]
|
73
78
|
end
|
74
|
-
|
79
|
+
@nice_comment = Class.new(@comment) do
|
75
80
|
extend TestModel
|
76
81
|
|
77
82
|
named_filter(:offensive) do
|
@@ -81,95 +86,110 @@ describe 'named filters' do
|
|
81
86
|
end
|
82
87
|
|
83
88
|
it 'should execute the parent class filters correctly' do
|
84
|
-
|
85
|
-
|
89
|
+
@nice_comment.with_contents('test contents').inspect
|
90
|
+
@nice_comment.last_find[:conditions].should ==
|
86
91
|
[%q("comments".contents = ?), 'test contents']
|
87
92
|
end
|
88
93
|
|
89
94
|
it 'should not have the subclass filters in the parent class' do
|
90
|
-
|
95
|
+
@comment.respond_to?(:offensive).should == false
|
91
96
|
end
|
92
97
|
|
93
98
|
it 'should have parent class filters in the subclass' do
|
94
|
-
|
95
|
-
|
99
|
+
@nice_comment.offensive.with_contents('something').inspect
|
100
|
+
@nice_comment.last_find[:conditions].should ==
|
96
101
|
%q(("comments".contents = 'something') AND ("comments".offensive = 't'))
|
97
102
|
end
|
98
103
|
|
99
104
|
it 'should provide access to the named filters' do
|
100
|
-
|
101
|
-
|
105
|
+
@comment.named_filters.should == [:with_contents]
|
106
|
+
@nice_comment.named_filters.sort_by { |i| i.to_s }.should == [:offensive, :with_contents]
|
102
107
|
end
|
103
108
|
end
|
104
109
|
|
105
110
|
describe 'using compound filters' do
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
having(:comments).offensive(true)
|
111
|
+
before do
|
112
|
+
Comment.named_filter(:offensive_or_not) do |state|
|
113
|
+
with(:offensive, state)
|
110
114
|
end
|
111
|
-
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should concatenate the filters correctly' do
|
118
|
+
Post.filter do
|
119
|
+
having(:comments).offensive_or_not(true)
|
120
|
+
end.inspect
|
112
121
|
Post.last_find[:conditions].should == [%q(posts__comments.offensive = ?), true]
|
113
|
-
Post.last_find[:joins].should == %q(INNER JOIN "comments" AS posts__comments ON "
|
122
|
+
Post.last_find[:joins].should == %q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'should work correctly with the named filter called within the having block' do
|
126
|
+
Post.filter do
|
127
|
+
having(:comments) do
|
128
|
+
offensive_or_not(false)
|
129
|
+
end
|
130
|
+
end.inspect
|
131
|
+
Post.last_find[:conditions].should == [%q(posts__comments.offensive = ?), false]
|
132
|
+
Post.last_find[:joins].should == %q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)
|
114
133
|
end
|
115
134
|
end
|
116
135
|
|
117
136
|
describe 'chaining filters' do
|
118
137
|
before do
|
119
|
-
|
138
|
+
@post = Class.new(Post)
|
139
|
+
@post.named_filter(:for_blog) do |*args|
|
120
140
|
having(:blog).with :id, args[0]
|
121
141
|
end
|
122
|
-
|
142
|
+
@post.named_filter(:with_offensive_comments) do
|
123
143
|
having(:comments).with :offensive, true
|
124
144
|
end
|
125
|
-
|
145
|
+
@post.named_filter(:with_interesting_comments) do
|
126
146
|
having(:comments).with :offensive, false
|
127
147
|
end
|
128
148
|
end
|
129
149
|
|
130
150
|
it 'should chain the filters into a single query' do
|
131
|
-
|
132
|
-
|
133
|
-
|
151
|
+
@post.for_blog(1).with_offensive_comments.inspect
|
152
|
+
@post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1))
|
153
|
+
@post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
|
134
154
|
end
|
135
155
|
|
136
156
|
it 'should remove duplicate joins' do
|
137
|
-
|
138
|
-
|
157
|
+
@post.for_blog(1).with_offensive_comments.with_interesting_comments.inspect
|
158
|
+
@post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
|
139
159
|
end
|
140
160
|
|
141
161
|
it 'should allow for filtering a named_filter' do
|
142
|
-
|
143
|
-
|
144
|
-
|
162
|
+
@post.for_blog(1).filter { having(:comments).with :offensive, true }.inspect
|
163
|
+
@post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1))
|
164
|
+
@post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
|
145
165
|
end
|
146
166
|
|
147
167
|
it 'should allow for applying a named filter to a filter' do
|
148
|
-
|
149
|
-
|
150
|
-
|
168
|
+
@post.filter { having(:comments).with :offensive, false }.for_blog(1).inspect
|
169
|
+
@post.last_find[:conditions].should == %q((posts__blog.id = 1) AND (posts__comments.offensive = 'f'))
|
170
|
+
@post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id), %q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)]
|
151
171
|
end
|
152
172
|
|
153
173
|
it 'should not change the inner filter conditions when chaining filters' do
|
154
|
-
base =
|
174
|
+
base = @post.for_blog(1)
|
155
175
|
base.with_offensive_comments
|
156
176
|
base.inspect
|
157
|
-
|
177
|
+
@post.last_find[:conditions].should == [%q(posts__blog.id = ?), 1]
|
158
178
|
end
|
159
179
|
|
160
180
|
it 'should not change the inner filter joins when chaining filters' do
|
161
|
-
base =
|
181
|
+
base = @post.for_blog(1)
|
162
182
|
base.with_offensive_comments
|
163
183
|
base.inspect
|
164
|
-
|
184
|
+
@post.last_find[:joins].should == %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)
|
165
185
|
end
|
166
186
|
|
167
187
|
it 'should not change an original filter when reusing it' do
|
168
|
-
base =
|
188
|
+
base = @post.for_blog(1)
|
169
189
|
level1 = base.with_offensive_comments.inspect
|
170
190
|
level2 = base.with_interesting_comments
|
171
|
-
|
172
|
-
|
191
|
+
@post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1))
|
192
|
+
@post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
|
173
193
|
end
|
174
194
|
end
|
175
195
|
|
@@ -220,20 +240,22 @@ describe 'named filters' do
|
|
220
240
|
|
221
241
|
describe 'chaining multiple named filters with different joins' do
|
222
242
|
before do
|
223
|
-
|
224
|
-
|
243
|
+
@blog = Class.new(Blog)
|
244
|
+
@blog.named_filter(:with_offensive_comments) { having(:comments).with(:offensive, true) }
|
245
|
+
@blog.named_filter(:with_ads_with_content) { |content| having(:ads).with(:content, content) }
|
225
246
|
end
|
226
247
|
|
227
248
|
it 'compile the joins correctly' do
|
228
|
-
|
229
|
-
|
249
|
+
@blog.with_offensive_comments.with_ads_with_content('ack').inspect
|
250
|
+
@blog.last_find[:joins].should == [%q(INNER JOIN "ads" AS blogs__ads ON "blogs".id = blogs__ads.blog_id), %q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)]
|
230
251
|
end
|
231
252
|
end
|
232
253
|
|
233
254
|
describe 'with named filters that only include orders' do
|
234
255
|
it 'should have an empty conditions hash' do
|
235
|
-
|
236
|
-
|
256
|
+
blog = Class.new(Blog)
|
257
|
+
blog.named_filter(:ordered_by_id) { order(:id, :desc) }
|
258
|
+
blog.ordered_by_id.proxy_options.should == { :order => %q("blogs".id DESC) }
|
237
259
|
end
|
238
260
|
end
|
239
261
|
end
|
data/spec/restrictions_spec.rb
CHANGED
@@ -42,6 +42,20 @@ describe 'RecordFilter restrictions' do
|
|
42
42
|
Post.last_find.should == { :conditions => [%q{"posts".blog_id IN (?)}, [1, 3, 5]] }
|
43
43
|
end
|
44
44
|
|
45
|
+
it 'should do the right thing for IN filters with empty arrays' do
|
46
|
+
Post.filter do
|
47
|
+
with(:blog_id).in([])
|
48
|
+
end.inspect
|
49
|
+
Post.last_find.should == { :conditions => [%q("posts".blog_id IN (?)), []] }
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should do the right thing for IN filters with nil' do
|
53
|
+
Post.filter do
|
54
|
+
with(:blog_id).in(nil)
|
55
|
+
end.inspect
|
56
|
+
Post.last_find.should == { :conditions => [%q("posts".blog_id IN (?)), nil] }
|
57
|
+
end
|
58
|
+
|
45
59
|
it 'should filter for between' do
|
46
60
|
time1 = Time.parse('2008-01-03 23:23:00')
|
47
61
|
time2 = Time.parse('2008-01-03 23:36:00')
|
@@ -119,6 +133,13 @@ describe 'RecordFilter restrictions' do
|
|
119
133
|
end
|
120
134
|
end
|
121
135
|
|
136
|
+
it 'should filter for is_not_null' do
|
137
|
+
Post.filter do
|
138
|
+
with(:permalink).is_not_null
|
139
|
+
end.inspect
|
140
|
+
Post.last_find.should == { :conditions => [%q("posts".permalink IS NOT NULL)] }
|
141
|
+
end
|
142
|
+
|
122
143
|
it 'should support like' do
|
123
144
|
Post.filter do
|
124
145
|
with(:permalink).like('%piglets%')
|
data/spec/select_spec.rb
CHANGED
@@ -16,10 +16,10 @@ describe 'with custom selects for cases where DISTINCT is required' do
|
|
16
16
|
|
17
17
|
describe 'with join types that require distinct' do
|
18
18
|
it 'should put the distinct clause in the select' do
|
19
|
-
[:left, :
|
19
|
+
[:left, :right].each do |join_type|
|
20
20
|
Post.filter do
|
21
21
|
having(join_type, :comments).with(:offensive, true)
|
22
|
-
end.inspect
|
22
|
+
end.inspect rescue nil # required because sqlite doesn't support right joins
|
23
23
|
Post.last_find[:select].should == %q(DISTINCT "posts".*)
|
24
24
|
end
|
25
25
|
end
|
@@ -40,7 +40,7 @@ describe 'with custom selects for cases where DISTINCT is required' do
|
|
40
40
|
it 'should put the distinct clause in the select' do
|
41
41
|
Blog.filter do
|
42
42
|
having(:posts) do
|
43
|
-
having(:
|
43
|
+
having(:left, :comments).with(:offensive, true)
|
44
44
|
end
|
45
45
|
end.inspect
|
46
46
|
Blog.last_find[:select].should == %q(DISTINCT "blogs".*)
|
@@ -50,7 +50,7 @@ describe 'with custom selects for cases where DISTINCT is required' do
|
|
50
50
|
describe 'on a filter that requires distinct with a count call' do
|
51
51
|
it 'should put the distinct clause in the select' do
|
52
52
|
Post.filter do
|
53
|
-
having(:
|
53
|
+
having(:left, :comments).with(:offensive, true)
|
54
54
|
end.count
|
55
55
|
Post.last_find[:select].should == %q(DISTINCT "posts".id)
|
56
56
|
end
|
data/spec/test.db
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aub-record_filter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mat Brown
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2009-
|
13
|
+
date: 2009-05-01 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -20,9 +20,10 @@ executables: []
|
|
20
20
|
|
21
21
|
extensions: []
|
22
22
|
|
23
|
-
extra_rdoc_files:
|
24
|
-
|
23
|
+
extra_rdoc_files:
|
24
|
+
- README.rdoc
|
25
25
|
files:
|
26
|
+
- README.rdoc
|
26
27
|
- Rakefile
|
27
28
|
- VERSION.yml
|
28
29
|
- lib/record_filter.rb
|
@@ -38,6 +39,7 @@ files:
|
|
38
39
|
- lib/record_filter/dsl/join_condition.rb
|
39
40
|
- lib/record_filter/dsl/join_dsl.rb
|
40
41
|
- lib/record_filter/dsl/limit.rb
|
42
|
+
- lib/record_filter/dsl/named_filter.rb
|
41
43
|
- lib/record_filter/dsl/order.rb
|
42
44
|
- lib/record_filter/dsl/restriction.rb
|
43
45
|
- lib/record_filter/filter.rb
|