aub-record_filter 0.9.7 → 0.9.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +20 -0
- data/README.rdoc +36 -7
- data/TODO +3 -0
- data/VERSION.yml +1 -1
- data/lib/record_filter/conjunctions.rb +11 -5
- data/lib/record_filter/dsl/conjunction.rb +8 -4
- data/lib/record_filter/dsl/conjunction_dsl.rb +32 -20
- data/lib/record_filter/dsl/dsl.rb +22 -13
- data/lib/record_filter/dsl/join.rb +3 -3
- data/lib/record_filter/query.rb +5 -5
- data/lib/record_filter/table.rb +34 -30
- data/record_filter.gemspec +3 -2
- data/spec/exception_spec.rb +5 -5
- data/spec/explicit_join_spec.rb +8 -8
- data/spec/implicit_join_spec.rb +73 -2
- data/spec/limits_and_ordering_spec.rb +2 -2
- data/spec/select_spec.rb +18 -9
- data/spec/test.db +0 -0
- metadata +3 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,23 @@
|
|
1
|
+
= 0.9.8
|
2
|
+
|
3
|
+
* BREAKING CHANGE: changed the 'limit' method so that the limit is always the
|
4
|
+
first argument, with an optional offset as the second argument. This makes the
|
5
|
+
functionality much clearer by avoiding obnoxious argument swapping.
|
6
|
+
|
7
|
+
* BREAKING CHANGE: changed the arguments to the 'join' method so that it
|
8
|
+
takes a class and an options hash. The options hash can contain a :join_type
|
9
|
+
and an :alias, allowing only the arguments that the client wants to use to be
|
10
|
+
specified. This will break existing code where the join type was passed as
|
11
|
+
the second argument.
|
12
|
+
|
13
|
+
* BREAKING CHANGE: changed the arguments to the 'having' method so that it
|
14
|
+
takes an association name and an options hash. The options hash takes
|
15
|
+
:join_type and :alias. This will break existing code where the join_type was
|
16
|
+
passed as the first argument to the method. The alias is a new option that
|
17
|
+
allows you to alias joins, which lets you join in the same association twice.
|
18
|
+
|
19
|
+
* Added a distinct method to force queries to be distinct.
|
20
|
+
|
1
21
|
= 0.9.7
|
2
22
|
|
3
23
|
* Fix a bug where explicit joins would not honor different aliases if joining
|
data/README.rdoc
CHANGED
@@ -122,7 +122,7 @@ The following example shows the use of each of these techniques:
|
|
122
122
|
with(:offensive, true)
|
123
123
|
end
|
124
124
|
end
|
125
|
-
limit(
|
125
|
+
limit(100, 10)
|
126
126
|
order(:created_at, :desc)
|
127
127
|
group_by(:comments => :offensive)
|
128
128
|
end
|
@@ -234,7 +234,7 @@ In a filter for a Post model that has_many comments, the following two examples
|
|
234
234
|
|
235
235
|
having(:comments)
|
236
236
|
|
237
|
-
join(Comment, :inner) do
|
237
|
+
join(Comment, :join_type => :inner) do
|
238
238
|
on(:id => :post_id)
|
239
239
|
end
|
240
240
|
|
@@ -248,7 +248,7 @@ added. Explicit joins also allow conditions to be set on columns of the table be
|
|
248
248
|
with(:created_at).greater_than(2.days.ago)
|
249
249
|
end
|
250
250
|
|
251
|
-
join(Comment, :inner) do
|
251
|
+
join(Comment, :join_type => :inner) do
|
252
252
|
on(:id => :commentable_id)
|
253
253
|
on(:commentable_type).equal_to('Post')
|
254
254
|
with(:created_at).less_than(1.year.ago)
|
@@ -260,13 +260,29 @@ example will join both tables and add a condition on the author.
|
|
260
260
|
|
261
261
|
having(:comments => :author).with(:name, 'Bob')
|
262
262
|
|
263
|
+
For both join types, an options hash can be provided as the second argument for passing the join
|
264
|
+
type and/or an alias to use for the joined table. The join type defaults to :inner, and the alias
|
265
|
+
defaults to a unique name for identifying the table. Using aliases allows you to join to a given table
|
266
|
+
twice with two different names. How about a contrived example? Awesome.
|
267
|
+
|
268
|
+
Blog.filter do
|
269
|
+
having(:posts, :join_type => :left, :alias => 'posts_1').with(:title, 'a')
|
270
|
+
having(:posts, :alias => 'posts_2').with(:title, 'b')
|
271
|
+
end
|
272
|
+
|
273
|
+
# SELECT DISTINCT "blogs".* FROM "blogs"
|
274
|
+
# LEFT OUTER JOIN "posts" AS posts_1 ON "blogs".id = posts_1.blog_id
|
275
|
+
# INNER JOIN "posts" AS posts_2 ON "blogs".id = posts_2.blog_id
|
276
|
+
# WHERE ((posts_1.title = 'a') AND (posts_2.title = 'b'))
|
277
|
+
|
263
278
|
=== Limits and Offsets
|
264
279
|
|
265
|
-
These are specified using the 'limit' method, which takes two arguments, the
|
266
|
-
|
280
|
+
These are specified using the 'limit' method, which takes two arguments, the limit and the
|
281
|
+
offset. The offset is optional. For specifying only offsets, the 'offset' method is also available.
|
267
282
|
|
268
|
-
limit(
|
269
|
-
limit(100) # :
|
283
|
+
limit(100, 10) # :limit => 100, :offset => 100
|
284
|
+
limit(100) # :limit => 100
|
285
|
+
offset(10) # :offset => 10
|
270
286
|
|
271
287
|
=== Ordering
|
272
288
|
|
@@ -299,6 +315,19 @@ order in which they were given.
|
|
299
315
|
|
300
316
|
# :group => "'posts'.permalink, posts__comments.offensive'
|
301
317
|
|
318
|
+
=== Distinct
|
319
|
+
|
320
|
+
Filters that include outer joins are automatically made distinct by record_filter. For filters that
|
321
|
+
use inner joins, use the 'distinct' method in the DSL to force the select to be distinct.
|
322
|
+
|
323
|
+
Blog.filter do
|
324
|
+
with(:created_at).greater_than(1.day.ago)
|
325
|
+
having(:posts).with(:permalink, nil)
|
326
|
+
distinct
|
327
|
+
end
|
328
|
+
|
329
|
+
# :select => "DISTINCT 'blogs'.*"
|
330
|
+
|
302
331
|
== LICENSE:
|
303
332
|
|
304
333
|
(The MIT License)
|
data/TODO
ADDED
data/VERSION.yml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module RecordFilter
|
2
2
|
module Conjunctions # :nodoc: all
|
3
3
|
class Base
|
4
|
-
attr_reader :table_name, :limit, :offset
|
4
|
+
attr_reader :table_name, :limit, :offset, :distinct
|
5
5
|
|
6
6
|
def self.create_from(dsl_conjunction, table)
|
7
7
|
result = case dsl_conjunction.type
|
@@ -19,7 +19,7 @@ module RecordFilter
|
|
19
19
|
when DSL::Conjunction
|
20
20
|
result.add_conjunction(create_from(step, table))
|
21
21
|
when DSL::Join
|
22
|
-
join = result.add_join_on_association(step.association, step.join_type)
|
22
|
+
join = result.add_join_on_association(step.association, step.join_type, step.aliaz)
|
23
23
|
result.add_conjunction(create_from(step.conjunction, join.right_table))
|
24
24
|
when DSL::ClassJoin
|
25
25
|
join = result.add_join_on_class(
|
@@ -36,6 +36,7 @@ module RecordFilter
|
|
36
36
|
else raise InvalidFilterException.new('And invalid filter step was provided.')
|
37
37
|
end
|
38
38
|
end
|
39
|
+
result.set_distinct(dsl_conjunction.distinct)
|
39
40
|
result
|
40
41
|
end
|
41
42
|
|
@@ -44,6 +45,7 @@ module RecordFilter
|
|
44
45
|
@table_name = table.table_alias
|
45
46
|
@restrictions = restrictions || []
|
46
47
|
@joins = joins || []
|
48
|
+
@distinct = false
|
47
49
|
end
|
48
50
|
|
49
51
|
def add_restriction(column_name, operator, value, options={})
|
@@ -57,13 +59,13 @@ module RecordFilter
|
|
57
59
|
conjunction
|
58
60
|
end
|
59
61
|
|
60
|
-
def add_join_on_association(association_name, join_type)
|
62
|
+
def add_join_on_association(association_name, join_type, aliaz)
|
61
63
|
table = @table
|
62
64
|
while association_name.is_a?(Hash)
|
63
|
-
table = table.join_association(association_name.keys[0], join_type).right_table
|
65
|
+
table = table.join_association(association_name.keys[0], { :join_type => join_type }).right_table
|
64
66
|
association_name = association_name.values[0]
|
65
67
|
end
|
66
|
-
table.join_association(association_name, join_type)
|
68
|
+
table.join_association(association_name, :join_type => join_type, :alias => aliaz)
|
67
69
|
end
|
68
70
|
|
69
71
|
def add_join_on_class(join_class, join_type, table_alias, conditions)
|
@@ -82,6 +84,10 @@ module RecordFilter
|
|
82
84
|
@limit, @offset = limit, offset
|
83
85
|
end
|
84
86
|
|
87
|
+
def set_distinct(value)
|
88
|
+
@distinct = value
|
89
|
+
end
|
90
|
+
|
85
91
|
def add_named_filter(name, args)
|
86
92
|
unless @table.model_class.named_filters.include?(name.to_sym)
|
87
93
|
raise NamedFilterNotFoundException.new("The named filter #{name} was not found in #{@table.model_class}")
|
@@ -2,10 +2,10 @@ module RecordFilter
|
|
2
2
|
module DSL
|
3
3
|
class Conjunction # :nodoc: all
|
4
4
|
|
5
|
-
attr_reader :type, :steps
|
5
|
+
attr_reader :type, :steps, :distinct
|
6
6
|
|
7
7
|
def initialize(model_class, type=:all_of)
|
8
|
-
@model_class, @type, @steps = model_class, type, []
|
8
|
+
@model_class, @type, @steps, @distinct = model_class, type, [], false
|
9
9
|
end
|
10
10
|
|
11
11
|
def add_restriction(column, value)
|
@@ -19,10 +19,10 @@ module RecordFilter
|
|
19
19
|
@steps << dsl.conjunction
|
20
20
|
end
|
21
21
|
|
22
|
-
def add_join(association, join_type, &block)
|
22
|
+
def add_join(association, join_type, aliaz, &block)
|
23
23
|
dsl = ConjunctionDSL.new(@model_class, Conjunction.new(@model_class, :all_of))
|
24
24
|
dsl.instance_eval(&block) if block
|
25
|
-
@steps << Join.new(association, join_type, dsl.conjunction)
|
25
|
+
@steps << Join.new(association, join_type, dsl.conjunction, aliaz)
|
26
26
|
dsl
|
27
27
|
end
|
28
28
|
|
@@ -45,6 +45,10 @@ module RecordFilter
|
|
45
45
|
@steps << GroupBy.new(column)
|
46
46
|
end
|
47
47
|
|
48
|
+
def set_distinct
|
49
|
+
@distinct = true
|
50
|
+
end
|
51
|
+
|
48
52
|
def add_named_filter(method, *args)
|
49
53
|
@steps << NamedFilter.new(method, *args)
|
50
54
|
end
|
@@ -169,20 +169,28 @@ module RecordFilter
|
|
169
169
|
# with(:created_at).greater_than(3.days.ago)
|
170
170
|
# end
|
171
171
|
# end
|
172
|
-
#
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
172
|
+
# Options can be passed to provide a custom join type or table alias through the options
|
173
|
+
# hash. The :join_type option can be either :inner, :left or :right and will default to
|
174
|
+
# :inner if not provided. The :alias option allows you to provide an alias for use in joining
|
175
|
+
# the table. If the same association is joined twice with different aliases, it will be treated
|
176
|
+
# as two separate joins. By default an alias will automatically be created
|
176
177
|
# for the joined table named "#{left_table}__#{association_name}", so in the above example, the
|
177
178
|
# alias would be posts__comments. It is also possible to provide a hash as the association
|
178
179
|
# name, in which case a trail of associations can be joined in one statment.
|
179
180
|
#
|
180
181
|
# ==== Parameters
|
181
|
-
# join_type<Symbol>::
|
182
|
-
# Specifies the type of join to perform, and can be one of :inner, :left or :right. :left
|
183
|
-
# and :right will create left and right outer joins, respectively.
|
184
182
|
# association<Symbol>::
|
185
183
|
# The name of the association to use as a base for the join.
|
184
|
+
# options<Hash>::
|
185
|
+
# An options hash (see below)
|
186
|
+
#
|
187
|
+
# ==== Options (options)
|
188
|
+
# :join_type<Symbol>::
|
189
|
+
# The type of join to use. Available options are :inner, :left and :right. Defaults to :inner.
|
190
|
+
# :alias<String>::
|
191
|
+
# An alias to use for the table name in the join. If provided, will create a unique name for
|
192
|
+
# the join and allow the same association to be joined multiple times. By default, the alias
|
193
|
+
# will be "#{left_table}__#{association_name}".
|
186
194
|
#
|
187
195
|
# ==== Returns
|
188
196
|
# ConjunctionDSL::
|
@@ -193,30 +201,34 @@ module RecordFilter
|
|
193
201
|
# be used as the association name.
|
194
202
|
#
|
195
203
|
# @public
|
196
|
-
def having(
|
197
|
-
|
198
|
-
association, join_type = join_type, nil
|
199
|
-
end
|
200
|
-
@conjunction.add_join(association, join_type, &block)
|
204
|
+
def having(association, options={}, &block)
|
205
|
+
@conjunction.add_join(association, options[:join_type], options[:alias], &block)
|
201
206
|
end
|
202
207
|
|
203
208
|
# Create an explicit join on the table of the given class. This method allows more complex
|
204
209
|
# joins to be speficied than can be created using having, including jump joins and ones that
|
205
210
|
# include conditions on column values. The method accepts a block that can contain any sequence
|
206
211
|
# of conjunctions, restrictions, or other joins, but it must also contain at least one call to
|
207
|
-
# JoinDSL.on to specify the conditions for the join.
|
212
|
+
# JoinDSL.on to specify the conditions for the join. The options hash accepts :join_type and
|
213
|
+
# :alias parameters. The :join_type parameter can be either :inner, :left or :right and defaults
|
214
|
+
# to :inner. The :alias parameter allows you to specify an alias for the table in the join and
|
215
|
+
# defaults to "#{left_table}__#{clazz.name}".
|
208
216
|
#
|
209
217
|
# ==== Parameters
|
210
218
|
# clazz<Class>::
|
211
219
|
# The class that is being joined to.
|
212
|
-
#
|
220
|
+
# options<Hash>::
|
221
|
+
# An options hash (see below)
|
222
|
+
# block<Proc>
|
223
|
+
# The contents of the join block can contain any sequence of conjunctions, restrictions, or joins.
|
224
|
+
#
|
225
|
+
# ==== Options(options)
|
226
|
+
# :join_type<Symbol>::
|
213
227
|
# Indicates the type of join to use and must be one of :inner, :left or :right, where :left
|
214
228
|
# or :right will create a LEFT or RIGHT OUTER join respectively.
|
215
|
-
#
|
229
|
+
# :alias<String>::
|
216
230
|
# If provided, will specify an alias to use in the SQL when referring to the joined table.
|
217
231
|
# If the argument is not given, the alias will be "#{left_table}__#{clazz.name}"
|
218
|
-
# block<Proc>
|
219
|
-
# The contents of the join block can contain any sequence of conjunctions, restrictions, or joins.
|
220
232
|
#
|
221
233
|
# ==== Returns
|
222
234
|
# JoinDSL::
|
@@ -224,8 +236,8 @@ module RecordFilter
|
|
224
236
|
# for constructions like: join(Comment, :inner).on(:id => :post_id)
|
225
237
|
#
|
226
238
|
# @public
|
227
|
-
def join(clazz,
|
228
|
-
@conjunction.add_class_join(clazz, join_type,
|
239
|
+
def join(clazz, options={}, &block)
|
240
|
+
@conjunction.add_class_join(clazz, options[:join_type], options[:alias], &block)
|
229
241
|
end
|
230
242
|
|
231
243
|
# Access the class that the current filter is being applied to. This is necessary
|
@@ -285,7 +297,7 @@ module RecordFilter
|
|
285
297
|
# Define these_methods here just so that we can throw exceptions when they are called. They should not
|
286
298
|
# be callable in the scope of a conjunction_dsl.
|
287
299
|
#
|
288
|
-
def limit(
|
300
|
+
def limit(limit, offset=nil) # :nodoc:
|
289
301
|
raise InvalidFilterException.new('Calls to limit can only be made in the outer block of a filter.')
|
290
302
|
end
|
291
303
|
|
@@ -8,25 +8,18 @@ module RecordFilter
|
|
8
8
|
# last one will override any others.
|
9
9
|
#
|
10
10
|
# ==== Parameters
|
11
|
+
# limit<Integer>::
|
12
|
+
# Used for the limit of the query.
|
11
13
|
# offset<Integer>::
|
12
|
-
# Used
|
13
|
-
#
|
14
|
-
# Used as the limit for the query.
|
14
|
+
# Used as the offset for the query. This argument is optional, with the default
|
15
|
+
# being no offset.
|
15
16
|
#
|
16
17
|
# ==== Returns
|
17
18
|
# nil
|
18
19
|
#
|
19
|
-
# ==== Alternatives
|
20
|
-
# If called with a single argument, it is assumed to represent the limit, and
|
21
|
-
# no offset will be specified.
|
22
|
-
#
|
23
20
|
# @public
|
24
|
-
def limit(
|
25
|
-
|
26
|
-
@conjunction.add_limit(limit, offset)
|
27
|
-
else
|
28
|
-
@conjunction.add_limit(offset, nil)
|
29
|
-
end
|
21
|
+
def limit(limit, offset=nil)
|
22
|
+
@conjunction.add_limit(limit, offset)
|
30
23
|
nil
|
31
24
|
end
|
32
25
|
|
@@ -129,6 +122,22 @@ module RecordFilter
|
|
129
122
|
@conjunction.add_group_by(column)
|
130
123
|
nil
|
131
124
|
end
|
125
|
+
|
126
|
+
# Specify that the resulting query should select distinct results. This method
|
127
|
+
# can only be called in the outermost scope of a filter (i.e. not inside of a having
|
128
|
+
# block, etc.).
|
129
|
+
#
|
130
|
+
# ==== Parameters
|
131
|
+
# none
|
132
|
+
#
|
133
|
+
# ==== Returns
|
134
|
+
# nil
|
135
|
+
#
|
136
|
+
# @public
|
137
|
+
def distinct
|
138
|
+
@conjunction.set_distinct
|
139
|
+
nil
|
140
|
+
end
|
132
141
|
end
|
133
142
|
end
|
134
143
|
end
|
@@ -2,10 +2,10 @@ module RecordFilter
|
|
2
2
|
module DSL
|
3
3
|
class Join # :nodoc: all
|
4
4
|
|
5
|
-
attr_reader :association, :join_type, :conjunction
|
5
|
+
attr_reader :association, :join_type, :conjunction, :aliaz
|
6
6
|
|
7
|
-
def initialize(association, join_type, conjunction)
|
8
|
-
@association, @join_type, @conjunction = association, join_type, conjunction
|
7
|
+
def initialize(association, join_type, conjunction, aliaz)
|
8
|
+
@association, @join_type, @conjunction, @aliaz = association, join_type, conjunction, aliaz
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
data/lib/record_filter/query.rb
CHANGED
@@ -20,7 +20,9 @@ module RecordFilter
|
|
20
20
|
params = {}
|
21
21
|
conditions = @conjunction.to_conditions
|
22
22
|
params = { :conditions => conditions } if conditions
|
23
|
-
|
23
|
+
joins = @table.all_joins
|
24
|
+
params[:joins] = joins.map { |join| join.to_sql } unless joins.empty?
|
25
|
+
set_select(params, count_query)
|
24
26
|
orders = @table.orders
|
25
27
|
params[:order] = orders.map { |order| order.to_sql } * ', ' unless orders.empty?
|
26
28
|
group_bys = @table.group_bys
|
@@ -34,10 +36,8 @@ module RecordFilter
|
|
34
36
|
|
35
37
|
protected
|
36
38
|
|
37
|
-
def
|
38
|
-
|
39
|
-
params[:joins] = joins.map { |join| join.to_sql } unless joins.empty?
|
40
|
-
if (joins.any? { |j| j.requires_distinct_select? })
|
39
|
+
def set_select(params, count_query)
|
40
|
+
if @conjunction.distinct || (@table.all_joins.any? { |j| j.requires_distinct_select? })
|
41
41
|
if count_query
|
42
42
|
params[:select] = "DISTINCT #{@table.table_name}.#{@table.model_class.primary_key}"
|
43
43
|
else
|
data/lib/record_filter/table.rb
CHANGED
@@ -16,35 +16,39 @@ module RecordFilter
|
|
16
16
|
@table_name ||= @model_class.quoted_table_name
|
17
17
|
end
|
18
18
|
|
19
|
-
def join_association(association_name,
|
19
|
+
def join_association(association_name, options={})
|
20
20
|
association_name = association_name.to_sym
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
21
|
+
join_type = options[:join_type] || :inner
|
22
|
+
cache_key = options[:alias] || association_name
|
23
|
+
@joins_cache[cache_key] ||= begin
|
24
|
+
association = @model_class.reflect_on_association(association_name)
|
25
|
+
if association.nil?
|
26
|
+
raise AssociationNotFoundException.new("The association #{association_name} was not found on #{@model_class.name}.")
|
27
|
+
end
|
28
|
+
if (association.options[:through])
|
29
|
+
through_association = @model_class.reflect_on_association(association.options[:through])
|
30
|
+
|
31
|
+
through_join = join_association(
|
32
|
+
association.options[:through],
|
33
|
+
:join_type => join_type,
|
34
|
+
:type_restriction => association.options[:source_type],
|
35
|
+
:source => association.options[:source])
|
36
|
+
|
37
|
+
through_join.right_table.join_association(
|
38
|
+
association.options[:source] || association_name,
|
39
|
+
:join_type => join_type,
|
40
|
+
:alias => options[:alias],
|
41
|
+
:join_class => association.options[:source_type])
|
42
|
+
else
|
43
|
+
case association.macro
|
44
|
+
when :belongs_to, :has_many, :has_one
|
45
|
+
simple_join(association, join_type, options)
|
46
|
+
when :has_and_belongs_to_many
|
47
|
+
compound_join(association, join_type, options)
|
48
|
+
else raise InvalidJoinException.new("I don't know how to join on an association of type #{association.macro}.")
|
46
49
|
end
|
47
50
|
end
|
51
|
+
end
|
48
52
|
end
|
49
53
|
|
50
54
|
def join_class(clazz, join_type, table_alias, conditions)
|
@@ -109,7 +113,7 @@ module RecordFilter
|
|
109
113
|
|
110
114
|
clazz = options[:join_class].nil? ? association.klass : options[:join_class].constantize
|
111
115
|
|
112
|
-
join_table = Table.new(clazz, alias_for_association(association))
|
116
|
+
join_table = Table.new(clazz, options[:alias] || alias_for_association(association))
|
113
117
|
@joins << join = Join.new(self, join_table, join_predicate, join_type)
|
114
118
|
join
|
115
119
|
end
|
@@ -125,13 +129,13 @@ module RecordFilter
|
|
125
129
|
end
|
126
130
|
end
|
127
131
|
|
128
|
-
def compound_join(association, join_type)
|
132
|
+
def compound_join(association, join_type, options)
|
129
133
|
pivot_join_predicate = [{ @model_class.primary_key => association.primary_key_name.to_sym }]
|
130
134
|
table_name = @model_class.connection.quote_table_name(association.options[:join_table])
|
131
|
-
pivot_table = PivotTable.new(table_name, association, "__#{alias_for_association(association)}")
|
135
|
+
pivot_table = PivotTable.new(table_name, association, "__#{options[:alias] || alias_for_association(association)}")
|
132
136
|
pivot_join = Join.new(self, pivot_table, pivot_join_predicate, join_type)
|
133
137
|
join_predicate = [{ association.association_foreign_key.to_sym => @model_class.primary_key }]
|
134
|
-
join_table = Table.new(association.klass, alias_for_association(association))
|
138
|
+
join_table = Table.new(association.klass, options[:alias] || alias_for_association(association))
|
135
139
|
pivot_table.joins << join = Join.new(pivot_table, join_table, join_predicate, join_type)
|
136
140
|
@joins << pivot_join
|
137
141
|
join
|
data/record_filter.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{record_filter}
|
5
|
-
s.version = "0.9.
|
5
|
+
s.version = "0.9.8"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Aubrey Holland", "Mat Brown"]
|
9
|
-
s.date = %q{2009-06-
|
9
|
+
s.date = %q{2009-06-04}
|
10
10
|
s.description = %q{RecordFilter is a Pure-ruby criteria API for building complex queries in ActiveRecord. It supports queries that are built on the fly as well as named filters that can be added to objects and chained to create complex queries. It also gets rid of the nasty hard-coded SQL that shows up in most ActiveRecord code with a clean API that makes queries simple and intuitive to build.}
|
11
11
|
s.email = %q{aubreyholland@gmail.com}
|
12
12
|
s.extra_rdoc_files = [
|
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
|
|
17
17
|
"CHANGELOG",
|
18
18
|
"README.rdoc",
|
19
19
|
"Rakefile",
|
20
|
+
"TODO",
|
20
21
|
"VERSION.yml",
|
21
22
|
"config/roodi.yml",
|
22
23
|
"lib/record_filter.rb",
|
data/spec/exception_spec.rb
CHANGED
@@ -61,7 +61,7 @@ describe 'raising exceptions' do
|
|
61
61
|
it 'should raise ColumnNotFoundException for explicit joins on bad column names for the right table' do
|
62
62
|
lambda {
|
63
63
|
Review.filter do
|
64
|
-
join(Feature, :left) do
|
64
|
+
join(Feature, :join_type => :left) do
|
65
65
|
on(:reviewable_id => :ftrable_id)
|
66
66
|
on(:reviewable_type => :ftrable_type)
|
67
67
|
with(:priority, 5)
|
@@ -73,7 +73,7 @@ describe 'raising exceptions' do
|
|
73
73
|
it 'should raise ColumnNotFoundException for explicit joins on bad column names for the left table' do
|
74
74
|
lambda {
|
75
75
|
Review.filter do
|
76
|
-
join(Feature, :inner) do
|
76
|
+
join(Feature, :join_type => :inner) do
|
77
77
|
on(:rvwable_id => :featurable_id)
|
78
78
|
on(:rvwable_type => :featurable_type)
|
79
79
|
with(:priority, 5)
|
@@ -85,7 +85,7 @@ describe 'raising exceptions' do
|
|
85
85
|
it 'should raise ColumnNotFoundException for explicit joins on bad column names in conditions' do
|
86
86
|
lambda {
|
87
87
|
Review.filter do
|
88
|
-
join(Feature, :inner) do
|
88
|
+
join(Feature, :join_type => :inner) do
|
89
89
|
on(:reviewable_id).gt(12)
|
90
90
|
end
|
91
91
|
end.inspect
|
@@ -95,7 +95,7 @@ describe 'raising exceptions' do
|
|
95
95
|
it 'should raise an ArgumentError if an invalid join type is specified' do
|
96
96
|
lambda {
|
97
97
|
Review.filter do
|
98
|
-
join(Feature, :crazy) do
|
98
|
+
join(Feature, :join_type => :crazy) do
|
99
99
|
on(:reviewable_type => :featurable_type)
|
100
100
|
end
|
101
101
|
end.inspect
|
@@ -105,7 +105,7 @@ describe 'raising exceptions' do
|
|
105
105
|
it 'should raise an InvalidJoinException if no columns are specified for the join' do
|
106
106
|
lambda {
|
107
107
|
Review.filter do
|
108
|
-
join(Feature, :inner)
|
108
|
+
join(Feature, :join_type => :inner)
|
109
109
|
end.inspect
|
110
110
|
}.should raise_error(RecordFilter::InvalidJoinException)
|
111
111
|
end
|
data/spec/explicit_join_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe 'explicit joins' do
|
|
8
8
|
describe 'specifying a simple join' do
|
9
9
|
before do
|
10
10
|
Post.filter do
|
11
|
-
join(Blog, :left, :posts_blogs) do
|
11
|
+
join(Blog, :join_type => :left, :alias => :posts_blogs) do
|
12
12
|
on(:blog_id => :id)
|
13
13
|
with(:name, 'Test Name')
|
14
14
|
end
|
@@ -27,7 +27,7 @@ describe 'explicit joins' do
|
|
27
27
|
describe 'specifying a complex join through polymorphic associations' do
|
28
28
|
before do
|
29
29
|
Review.filter do
|
30
|
-
join(Feature, :left, :reviews_features) do
|
30
|
+
join(Feature, :join_type => :left, :alias => :reviews_features) do
|
31
31
|
on(:reviewable_id => :featurable_id)
|
32
32
|
on(:reviewable_type => :featurable_type)
|
33
33
|
with(:priority, 5)
|
@@ -47,7 +47,7 @@ describe 'explicit joins' do
|
|
47
47
|
describe 'should use values as join parameters instead of columns if given' do
|
48
48
|
before do
|
49
49
|
Review.filter do
|
50
|
-
join(Feature, :left) do
|
50
|
+
join(Feature, :join_type => :left) do
|
51
51
|
on(:reviewable_id => :featurable_id)
|
52
52
|
on(:reviewable_type => :featurable_type)
|
53
53
|
on(:featurable_type, 'SomeType')
|
@@ -64,7 +64,7 @@ describe 'explicit joins' do
|
|
64
64
|
describe 'using restrictions on join conditions' do
|
65
65
|
before do
|
66
66
|
Review.filter do
|
67
|
-
join(Feature, :left) do
|
67
|
+
join(Feature, :join_type => :left) do
|
68
68
|
on(:featurable_type, nil)
|
69
69
|
on(:featurable_id).gte(12)
|
70
70
|
on(:priority).not(6)
|
@@ -84,9 +84,9 @@ describe 'explicit joins' do
|
|
84
84
|
having(:ads) do
|
85
85
|
with(:content, nil)
|
86
86
|
end
|
87
|
-
join(Post, :left) do
|
87
|
+
join(Post, :join_type => :left) do
|
88
88
|
on(:id => :blog_id)
|
89
|
-
join(Comment, :inner) do
|
89
|
+
join(Comment, :join_type => :inner) do
|
90
90
|
on(:id => :post_id)
|
91
91
|
on(:offensive, true)
|
92
92
|
end
|
@@ -109,11 +109,11 @@ describe 'explicit joins' do
|
|
109
109
|
before do
|
110
110
|
@blog = Class.new(Blog)
|
111
111
|
@blog.named_filter(:things) do
|
112
|
-
join(Post, :inner, 'blogs_posts_1') do
|
112
|
+
join(Post, :join_type => :inner, :alias => 'blogs_posts_1') do
|
113
113
|
on(:id => :blog_id)
|
114
114
|
with(:title, 'ack')
|
115
115
|
end
|
116
|
-
join(Post, :inner, 'blogs_posts_2') do
|
116
|
+
join(Post, :join_type => :inner, :alias => 'blogs_posts_2') do
|
117
117
|
on(:id => :blog_id)
|
118
118
|
with(:title, 'hmm')
|
119
119
|
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(:posts, :join_type => :left) 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({ :posts => :comments }, :join_type => :left) do
|
246
246
|
with(:offensive, true)
|
247
247
|
end
|
248
248
|
end.inspect
|
@@ -329,4 +329,75 @@ describe 'implicit joins' do
|
|
329
329
|
Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)]
|
330
330
|
end
|
331
331
|
end
|
332
|
+
|
333
|
+
describe 'using a table alias' do
|
334
|
+
before do
|
335
|
+
Post.filter do
|
336
|
+
having(:comments, :alias => 'arghs') do
|
337
|
+
with(:offensive, true)
|
338
|
+
end
|
339
|
+
end.inspect
|
340
|
+
end
|
341
|
+
|
342
|
+
it 'should create the correct condition' do
|
343
|
+
Post.last_find[:conditions].should == [%q(arghs.offensive = ?), true]
|
344
|
+
end
|
345
|
+
|
346
|
+
it 'should create the correct join' do
|
347
|
+
Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS arghs ON "posts".id = arghs.post_id)]
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
describe 'using a table alias to do multiple joins on the same association' do
|
352
|
+
before do
|
353
|
+
Post.filter do
|
354
|
+
having(:comments, :alias => 'ooohs').with(:offensive, true)
|
355
|
+
having(:comments, :alias => 'aaahs').with(:offensive, false)
|
356
|
+
end.inspect
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'should create the correct condition' do
|
360
|
+
Post.last_find[:conditions].should == [%q((ooohs.offensive = ?) AND (aaahs.offensive = ?)), true, false]
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'should create the correct join' do
|
364
|
+
Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS ooohs ON "posts".id = ooohs.post_id), %q(INNER JOIN "comments" AS aaahs ON "posts".id = aaahs.post_id)]
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
describe 'using a table alias with has_many :through associations' do
|
369
|
+
before do
|
370
|
+
Blog.filter do
|
371
|
+
having(:comments, :alias => 'arghs') do
|
372
|
+
with(:offensive, true)
|
373
|
+
end
|
374
|
+
end.inspect
|
375
|
+
end
|
376
|
+
|
377
|
+
it 'should create the correct condition' do
|
378
|
+
Blog.last_find[:conditions].should == [%q(arghs.offensive = ?), true]
|
379
|
+
end
|
380
|
+
|
381
|
+
it 'should create the correct join' do
|
382
|
+
Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id), %q(INNER JOIN "comments" AS arghs ON blogs__posts.id = arghs.post_id)]
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
describe 'using a table alias' do
|
387
|
+
before do
|
388
|
+
Post.filter do
|
389
|
+
having(:comments, :alias => 'arghs') do
|
390
|
+
with(:offensive, true)
|
391
|
+
end
|
392
|
+
end.inspect
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'should create the correct condition' do
|
396
|
+
Post.last_find[:conditions].should == [%q(arghs.offensive = ?), true]
|
397
|
+
end
|
398
|
+
|
399
|
+
it 'should create the correct join' do
|
400
|
+
Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS arghs ON "posts".id = arghs.post_id)]
|
401
|
+
end
|
402
|
+
end
|
332
403
|
end
|
@@ -52,7 +52,7 @@ describe 'filter qualifiers' do
|
|
52
52
|
before do
|
53
53
|
Post.filter do
|
54
54
|
with :published, true
|
55
|
-
limit(
|
55
|
+
limit(10, 20)
|
56
56
|
end.inspect
|
57
57
|
end
|
58
58
|
|
@@ -168,7 +168,7 @@ describe 'filter qualifiers' do
|
|
168
168
|
before do
|
169
169
|
Post.filter do
|
170
170
|
with(:published, false)
|
171
|
-
join(Comment, :inner) do
|
171
|
+
join(Comment, :join_type => :inner) do
|
172
172
|
on(:id => :post_id)
|
173
173
|
end
|
174
174
|
order(Comment => :id)
|
data/spec/select_spec.rb
CHANGED
@@ -18,7 +18,7 @@ describe 'with custom selects for cases where DISTINCT is required' do
|
|
18
18
|
it 'should put the distinct clause in the select' do
|
19
19
|
[:left, :right].each do |join_type|
|
20
20
|
Post.filter do
|
21
|
-
having(
|
21
|
+
having(:comments, :join_type => join_type).with(:offensive, true)
|
22
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
|
@@ -27,12 +27,10 @@ describe 'with custom selects for cases where DISTINCT is required' do
|
|
27
27
|
|
28
28
|
describe 'with join types that do not require distinct' do
|
29
29
|
it 'should not put the distinct clause in the select' do
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
Post.last_find[:select].should be_nil
|
35
|
-
end
|
30
|
+
Post.filter do
|
31
|
+
having(:comments, :join_type => :inner).with(:offensive, true)
|
32
|
+
end.inspect
|
33
|
+
Post.last_find[:select].should be_nil
|
36
34
|
end
|
37
35
|
end
|
38
36
|
|
@@ -40,7 +38,7 @@ describe 'with custom selects for cases where DISTINCT is required' do
|
|
40
38
|
it 'should put the distinct clause in the select' do
|
41
39
|
Blog.filter do
|
42
40
|
having(:posts) do
|
43
|
-
having(:
|
41
|
+
having(:comments, :join_type => :left).with(:offensive, true)
|
44
42
|
end
|
45
43
|
end.inspect
|
46
44
|
Blog.last_find[:select].should == %q(DISTINCT "blogs".*)
|
@@ -50,9 +48,20 @@ describe 'with custom selects for cases where DISTINCT is required' do
|
|
50
48
|
describe 'on a filter that requires distinct with a count call' do
|
51
49
|
it 'should put the distinct clause in the select' do
|
52
50
|
Post.filter do
|
53
|
-
having(:
|
51
|
+
having(:comments, :join_type => :left).with(:offensive, true)
|
54
52
|
end.count
|
55
53
|
Post.last_find[:select].should == %q(DISTINCT "posts".id)
|
56
54
|
end
|
57
55
|
end
|
56
|
+
|
57
|
+
describe 'using the distinct method' do
|
58
|
+
it 'should always create a distinct query' do
|
59
|
+
Blog.filter do
|
60
|
+
with(:created_at).gt(1.day.ago)
|
61
|
+
having(:posts).with(:permalink, nil)
|
62
|
+
distinct
|
63
|
+
end.inspect
|
64
|
+
Blog.last_find[:select].should == %q(DISTINCT "blogs".*)
|
65
|
+
end
|
66
|
+
end
|
58
67
|
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.9.
|
4
|
+
version: 0.9.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aubrey Holland
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2009-06-
|
13
|
+
date: 2009-06-04 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -27,6 +27,7 @@ files:
|
|
27
27
|
- CHANGELOG
|
28
28
|
- README.rdoc
|
29
29
|
- Rakefile
|
30
|
+
- TODO
|
30
31
|
- VERSION.yml
|
31
32
|
- config/roodi.yml
|
32
33
|
- lib/record_filter.rb
|