aub-record_filter 0.2.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/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
|