aub-record_filter 0.9.3 → 0.9.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +53 -20
- data/Rakefile +31 -3
- data/VERSION.yml +1 -1
- data/lib/record_filter/active_record.rb +4 -3
- data/lib/record_filter/column_parser.rb +14 -0
- data/lib/record_filter/conjunctions.rb +4 -4
- data/lib/record_filter/dsl/dsl.rb +23 -3
- data/lib/record_filter/dsl/restriction.rb +1 -1
- data/lib/record_filter/group_by.rb +3 -5
- data/lib/record_filter/join.rb +5 -3
- data/lib/record_filter/order.rb +9 -10
- data/lib/record_filter/query.rb +13 -9
- data/lib/record_filter/restriction_factory.rb +21 -0
- data/lib/record_filter/restrictions.rb +0 -4
- data/lib/record_filter/table.rb +18 -10
- data/lib/record_filter.rb +2 -2
- data/spec/exception_spec.rb +3 -3
- data/spec/limits_and_ordering_spec.rb +59 -1
- data/spec/restrictions_spec.rb +7 -0
- data/spec/test.db +0 -0
- data/test/performance_test.rb +3 -0
- metadata +5 -3
data/README.rdoc
CHANGED
@@ -19,6 +19,10 @@ intended to be a getting started guide that should cover the most common uses.
|
|
19
19
|
|
20
20
|
gem install aub-record_filter --source=http://gems.github.com
|
21
21
|
|
22
|
+
In Rails, you'll need to add this to your config/environment.rb file:
|
23
|
+
|
24
|
+
config.gem 'aub-record_filter', :lib => 'record_filter', :source => 'http://gems.github.com'
|
25
|
+
|
22
26
|
== Using Filters
|
23
27
|
|
24
28
|
Given a Blog model having a has_many relationship with a Post model, a simple
|
@@ -31,24 +35,27 @@ filter with conditions and joins might look like this.
|
|
31
35
|
|
32
36
|
This could be expressed in ActiveRecord as:
|
33
37
|
|
34
|
-
Blog.
|
35
|
-
:all,
|
38
|
+
Blog.all(
|
36
39
|
:joins => :posts,
|
37
|
-
:conditions =>
|
40
|
+
:conditions => {
|
41
|
+
:posts => {:permalink => nil},
|
42
|
+
:created_at => 1.day.ago..Time.now
|
43
|
+
}
|
44
|
+
)
|
38
45
|
|
39
46
|
and it returns the same result, a list of Blog objects that are the result of the query. This
|
40
47
|
type of filter is designed to be created on the fly, but if you have a filter that you would like
|
41
48
|
to use in more than one place, it can be added to a class as a named filter. The following example
|
42
49
|
creates the same filter as above and executes it:
|
43
50
|
|
44
|
-
class
|
45
|
-
named_filter(:
|
51
|
+
class Blog < ActiveRecord::Base
|
52
|
+
named_filter(:new_with_unlinked_posts) do
|
46
53
|
with(:created_at).greater_than(1.day.ago)
|
47
54
|
having(:posts).with(:permalink, nil)
|
48
55
|
end
|
49
56
|
end
|
50
57
|
|
51
|
-
|
58
|
+
Blog.new_with_unlinked_posts
|
52
59
|
|
53
60
|
This returns the same result as the example above but with the advantages that it is
|
54
61
|
easily reusable and that it can be combined with other named filters to produce a more
|
@@ -85,13 +92,15 @@ model even if called from a join.
|
|
85
92
|
end
|
86
93
|
|
87
94
|
class Post < ActiveRecord::Base
|
88
|
-
named_filter(:
|
95
|
+
named_filter(:using_other_filter) do
|
89
96
|
having(:comments) do
|
90
97
|
offensive
|
91
98
|
end
|
92
99
|
end
|
93
100
|
end
|
94
101
|
|
102
|
+
Post.using_other_filter
|
103
|
+
|
95
104
|
== Specifying Filters
|
96
105
|
|
97
106
|
record_filter supports all of the SQL query abstractions provided by ActiveRecord, specifically:
|
@@ -125,13 +134,13 @@ the name of the column to restrict. If a second argument is given, it will autom
|
|
125
134
|
be used as the value in an equality condition. The 'with' function will return a Restriction
|
126
135
|
object that has methods to specify a number of different conditions and to negate them:
|
127
136
|
|
128
|
-
with(:permalink, 'aardvarks') #
|
129
|
-
with(:permalink).equal_to('sheep') #
|
130
|
-
with(:permalink).not.equal_to('cats') #
|
137
|
+
with(:permalink, 'aardvarks') # ['permalink = ?', 'aardvarks']
|
138
|
+
with(:permalink).equal_to('sheep') # ['permalink = ?', 'sheep']
|
139
|
+
with(:permalink).not.equal_to('cats') # ['permailnk <> ?', 'cats']
|
131
140
|
|
132
|
-
with(:permalink, nil) #
|
133
|
-
with(:permalink).is_null #
|
134
|
-
with(:permalink, nil).not #
|
141
|
+
with(:permalink, nil) # ['permalink IS NULL']
|
142
|
+
with(:permalink).is_null # ['permalink IS NULL']
|
143
|
+
with(:permalink, nil).not # ['permalink IS NOT NULL']
|
135
144
|
|
136
145
|
The following condition types are supported through the Restriction API:
|
137
146
|
|
@@ -141,8 +150,23 @@ The following condition types are supported through the Restriction API:
|
|
141
150
|
* In
|
142
151
|
* Is null
|
143
152
|
* Like
|
153
|
+
* Negation of all of the above
|
154
|
+
|
155
|
+
And here are some examples. See the RDoc page for
|
156
|
+
{DSL::Restriction}[http://aub.github.com/record_filter/rdoc/classes/RecordFilter/DSL/Restriction.html]
|
157
|
+
for more details on how to use them.
|
158
|
+
|
159
|
+
with(:featured_at).greater_than(Time.now) # ['featured_at > ?', Time.now]
|
160
|
+
|
161
|
+
with(:price).lte(1000) # ['price <= ?', 1000]
|
162
|
+
|
163
|
+
with(:created_at).between(time_a..time_b) # ['created_at BETWEEN ? AND ?', time_a, time_b]
|
144
164
|
|
145
|
-
|
165
|
+
with(:id).in([1, 2, 3]) # ['id in (?)', [1, 2, 3]]
|
166
|
+
|
167
|
+
with(:content).like('%easy%') # ['content LIKE ?', '%easy%']
|
168
|
+
|
169
|
+
with(:content).not.like('%hard%') # ['content NOT LIKE ?', '%hard%']
|
146
170
|
|
147
171
|
=== Boolean Operations
|
148
172
|
|
@@ -156,25 +180,34 @@ joins or other boolean operations. The default operator is all_of.
|
|
156
180
|
with(:permalink, 'ack')
|
157
181
|
end
|
158
182
|
|
159
|
-
|
183
|
+
# ['id = ? AND permalink = ?', 4, 'ack']
|
160
184
|
|
161
185
|
Post.filter do
|
162
|
-
any_of
|
186
|
+
any_of do
|
163
187
|
with(:id, 3)
|
164
188
|
with(:permalink, 'booya')
|
165
189
|
end
|
166
190
|
end
|
167
191
|
|
168
|
-
|
192
|
+
# ['id = ? OR permalink = ?', 3, 'booya']
|
169
193
|
|
170
194
|
Post.filter do
|
171
|
-
none_of
|
195
|
+
none_of do
|
172
196
|
with(:id, 2)
|
173
197
|
with(:permalink, 'ouch')
|
174
198
|
end
|
175
199
|
end
|
176
200
|
|
177
|
-
|
201
|
+
# ['NOT (id = ? OR permalink = ?)', 2, 'ouch']
|
202
|
+
|
203
|
+
Post.filter do
|
204
|
+
not_all_of do
|
205
|
+
with(:id, 1)
|
206
|
+
with(:permalink, 'bonobo')
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# ['NOT (id = ? AND permalink = ?)', 1, 'bonobo']
|
178
211
|
|
179
212
|
=== Joins
|
180
213
|
|
@@ -257,7 +290,7 @@ order in which they were given.
|
|
257
290
|
|
258
291
|
(The MIT License)
|
259
292
|
|
260
|
-
Copyright (c) 2008 Aubrey Holland, Mat Brown
|
293
|
+
Copyright (c) 2008-2009 Aubrey Holland, Mat Brown
|
261
294
|
|
262
295
|
Permission is hereby granted, free of charge, to any person obtaining
|
263
296
|
a copy of this software and associated documentation files (the
|
data/Rakefile
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
-
# ENV['RUBYOPT'] = '-W1'
|
2
|
-
|
3
1
|
require 'rubygems'
|
4
2
|
require 'rake'
|
5
3
|
require 'rake/testtask'
|
6
4
|
|
7
5
|
FileList['tasks/**/*.rake'].each { |file| load file }
|
8
6
|
|
9
|
-
task :default => :spec
|
7
|
+
task :default => ["db:spec:prepare", :spec]
|
10
8
|
|
11
9
|
begin
|
12
10
|
require 'jeweler'
|
@@ -41,6 +39,7 @@ Rake::RDocTask.new do |rdoc|
|
|
41
39
|
rdoc.title = "record_filter #{version}"
|
42
40
|
rdoc.rdoc_files.include('README*')
|
43
41
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
42
|
+
rdoc.options << '--webcvs=http://github.com/aub/record_filter/tree/master/'
|
44
43
|
end
|
45
44
|
|
46
45
|
begin
|
@@ -56,3 +55,32 @@ rescue LoadError
|
|
56
55
|
puts 'Ruby-prof not available. Profiling tests are disabled.'
|
57
56
|
end
|
58
57
|
|
58
|
+
begin
|
59
|
+
require 'metric_fu'
|
60
|
+
MetricFu::Configuration.run do |config|
|
61
|
+
#define which metrics you want to use
|
62
|
+
config.metrics = [:churn, :flog, :flay, :reek, :roodi, :rcov] # :saikuro, :stats
|
63
|
+
config.flay = { :dirs_to_flay => ['lib'] }
|
64
|
+
config.flog = { :dirs_to_flog => ['lib'] }
|
65
|
+
config.reek = { :dirs_to_reek => ['lib'] }
|
66
|
+
config.roodi = { :dirs_to_roodi => ['lib'] }
|
67
|
+
config.saikuro = { :output_directory => 'scratch_directory/saikuro',
|
68
|
+
:input_directory => ['lib'],
|
69
|
+
:cyclo => "",
|
70
|
+
:filter_cyclo => "0",
|
71
|
+
:warn_cyclo => "5",
|
72
|
+
:error_cyclo => "7",
|
73
|
+
:formater => "text"} #this needs to be set to "text"
|
74
|
+
config.churn = { :start_date => "1 year ago", :minimum_churn_count => 10}
|
75
|
+
config.rcov = { :test_files => ['spec/**/*_spec.rb'],
|
76
|
+
:rcov_opts => ["--sort coverage",
|
77
|
+
"--no-html",
|
78
|
+
"--text-coverage",
|
79
|
+
"--no-color",
|
80
|
+
"--profile",
|
81
|
+
"--exclude spec"]}
|
82
|
+
end
|
83
|
+
rescue LoadError
|
84
|
+
puts 'Install metric_fu for code quality metric tests.'
|
85
|
+
end
|
86
|
+
|
data/VERSION.yml
CHANGED
@@ -59,16 +59,17 @@ module RecordFilter
|
|
59
59
|
#
|
60
60
|
# @public
|
61
61
|
def named_filter(name, &block)
|
62
|
-
|
62
|
+
name = name.to_sym
|
63
|
+
if named_filters.include?(name)
|
63
64
|
raise InvalidFilterNameException.new("A named filter with the name #{name} already exists on the class #{self.name}.")
|
64
65
|
end
|
65
|
-
local_named_filters << name
|
66
|
+
local_named_filters << name
|
66
67
|
DSL::DSLFactory::get_subclass(self).module_eval do
|
67
68
|
define_method(name, &block)
|
68
69
|
end
|
69
70
|
|
70
71
|
(class << self; self; end).instance_eval do
|
71
|
-
define_method(name
|
72
|
+
define_method(name) do |*args|
|
72
73
|
Filter.new(self, name, *args)
|
73
74
|
end
|
74
75
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RecordFilter
|
2
|
+
module ColumnParser # :nodoc: all
|
3
|
+
|
4
|
+
protected
|
5
|
+
|
6
|
+
def parse_column_in_table(column, table)
|
7
|
+
while column.is_a?(Hash)
|
8
|
+
table = table.join_association(column.keys[0]).right_table
|
9
|
+
column = column.values[0]
|
10
|
+
end
|
11
|
+
[column, table]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -9,6 +9,7 @@ module RecordFilter
|
|
9
9
|
when :all_of then AllOf.new(table)
|
10
10
|
when :none_of then NoneOf.new(table)
|
11
11
|
when :not_all_of then NotAllOf.new(table)
|
12
|
+
else raise InvalidFilterException.new("An invalid conjunction type of #{dsl_conjunction.type} was used.")
|
12
13
|
end
|
13
14
|
|
14
15
|
dsl_conjunction.steps.each do |step|
|
@@ -32,6 +33,7 @@ module RecordFilter
|
|
32
33
|
result.add_group_by(step.column)
|
33
34
|
when DSL::NamedFilter
|
34
35
|
result.add_named_filter(step.name, step.args)
|
36
|
+
else raise InvalidFilterException.new('And invalid filter step was provided.')
|
35
37
|
end
|
36
38
|
end
|
37
39
|
result
|
@@ -46,8 +48,7 @@ module RecordFilter
|
|
46
48
|
|
47
49
|
def add_restriction(column_name, operator, value, options={})
|
48
50
|
check_column_exists!(column_name)
|
49
|
-
|
50
|
-
restriction = restriction_class.new("#{@table_name}.#{column_name}", value, options)
|
51
|
+
restriction = RestrictionFactory.build(operator, "#{@table_name}.#{column_name}", value, options)
|
51
52
|
self << restriction
|
52
53
|
restriction
|
53
54
|
end
|
@@ -60,8 +61,7 @@ module RecordFilter
|
|
60
61
|
def add_join_on_association(association_name, join_type)
|
61
62
|
table = @table
|
62
63
|
while association_name.is_a?(Hash)
|
63
|
-
|
64
|
-
table = result.right_table
|
64
|
+
table = table.join_association(association_name.keys[0], join_type).right_table
|
65
65
|
association_name = association_name.values[0]
|
66
66
|
end
|
67
67
|
table.join_association(association_name, join_type)
|
@@ -30,6 +30,24 @@ module RecordFilter
|
|
30
30
|
nil
|
31
31
|
end
|
32
32
|
|
33
|
+
# Define an offset for the results returned from the current
|
34
|
+
# filter. This method can only be called from the outermost scope of a filter
|
35
|
+
# (i.e. not inside of a having block, etc.). If it is called multiple times, the
|
36
|
+
# last one will override any others.
|
37
|
+
#
|
38
|
+
# ==== Parameters
|
39
|
+
# offset<Integer>::
|
40
|
+
# The offset of the query.
|
41
|
+
#
|
42
|
+
# ==== Returns
|
43
|
+
# nil
|
44
|
+
#
|
45
|
+
# @public
|
46
|
+
def offset(offset)
|
47
|
+
@conjunction.add_limit(nil, offset)
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
33
51
|
# Define an order clause for the current query, with options for specifying
|
34
52
|
# both the column to use as well as the direction. This method can only be called
|
35
53
|
# in the outermost scope of a filter (i.e. not inside of a having block, etc.).
|
@@ -51,9 +69,11 @@ module RecordFilter
|
|
51
69
|
# column<Symbol, Hash>::
|
52
70
|
# Specify the column for the ordering. If a symbol is given, it is assumed to represent
|
53
71
|
# a column in the class that is being filtered. With a hash argument, it is possible
|
54
|
-
# to specify a path to a column in one of the joined tables, as seen above.
|
72
|
+
# to specify a path to a column in one of the joined tables, as seen above. If a string
|
73
|
+
# is given and it doesn't match up with a column name, it is used as a literal string
|
74
|
+
# for ordering.
|
55
75
|
# direction<Symbol>::
|
56
|
-
# Specifies the direction of the
|
76
|
+
# Specifies the direction of the order. Should be either :asc or :desc and defaults to :asc.
|
57
77
|
#
|
58
78
|
# ==== Returns
|
59
79
|
# nil
|
@@ -63,7 +83,7 @@ module RecordFilter
|
|
63
83
|
# If the direction is neither :asc nor :desc.
|
64
84
|
#
|
65
85
|
# ==== Alternatives
|
66
|
-
# As described above, it is possible to pass
|
86
|
+
# As described above, it is possible to pass a symbol, a hash or a string as the first
|
67
87
|
# argument.
|
68
88
|
#
|
69
89
|
# @public
|
@@ -190,7 +190,7 @@ module RecordFilter
|
|
190
190
|
#
|
191
191
|
# ==== Parameters
|
192
192
|
# value::
|
193
|
-
# Either a single item
|
193
|
+
# Either a single item, an array of values, or a range to form the values to be tested for inclusion.
|
194
194
|
#
|
195
195
|
# ==== Returns
|
196
196
|
# Restriction:: self
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module RecordFilter
|
2
2
|
class GroupBy # :nodoc: all
|
3
|
+
include ColumnParser
|
4
|
+
|
3
5
|
attr_reader :column, :table
|
4
6
|
|
5
7
|
def initialize(column, table)
|
@@ -7,11 +9,7 @@ module RecordFilter
|
|
7
9
|
end
|
8
10
|
|
9
11
|
def to_sql
|
10
|
-
|
11
|
-
while column.is_a?(Hash)
|
12
|
-
table = table.join_association(column.keys[0]).right_table
|
13
|
-
column = column.values[0]
|
14
|
-
end
|
12
|
+
column, table = parse_column_in_table(@column, @table)
|
15
13
|
|
16
14
|
if (table.has_column(column))
|
17
15
|
"#{table.table_alias}.#{column}"
|
data/lib/record_filter/join.rb
CHANGED
@@ -46,9 +46,11 @@ module RecordFilter
|
|
46
46
|
unless @right_table.has_column(dsl_restriction.column)
|
47
47
|
raise ColumnNotFoundException.new("The column #{dsl_restriction.column} was not found in the table #{@right_table.table_name}")
|
48
48
|
end
|
49
|
-
|
50
|
-
|
51
|
-
"#{@right_table.table_alias}.#{dsl_restriction.column}",
|
49
|
+
restriction = RestrictionFactory.build(
|
50
|
+
dsl_restriction.operator,
|
51
|
+
"#{@right_table.table_alias}.#{dsl_restriction.column}",
|
52
|
+
dsl_restriction.value,
|
53
|
+
:negated => dsl_restriction.negated)
|
52
54
|
@right_table.model_class.merge_conditions(restriction.to_conditions)
|
53
55
|
end
|
54
56
|
|
data/lib/record_filter/order.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module RecordFilter
|
2
2
|
class Order # :nodoc: all
|
3
|
+
include ColumnParser
|
4
|
+
|
3
5
|
attr_reader :column, :direction, :table
|
4
6
|
|
5
7
|
def initialize(column, direction, table)
|
@@ -10,19 +12,16 @@ module RecordFilter
|
|
10
12
|
dir = case @direction
|
11
13
|
when :asc then 'ASC'
|
12
14
|
when :desc then 'DESC'
|
13
|
-
|
14
|
-
|
15
|
-
table, column = @table, @column
|
16
|
-
while column.is_a?(Hash)
|
17
|
-
table = table.join_association(column.keys[0]).right_table
|
18
|
-
column = column.values[0]
|
15
|
+
else raise InvalidFilterException.new("An invalid order of #{@direction} was specified.")
|
19
16
|
end
|
20
17
|
|
21
|
-
|
22
|
-
raise ColumnNotFoundException.new("The column #{column} was not found in #{table.table_name}.")
|
23
|
-
end
|
18
|
+
column, table = parse_column_in_table(@column, @table)
|
24
19
|
|
25
|
-
|
20
|
+
if (table.has_column(column))
|
21
|
+
"#{table.table_alias}.#{column} #{dir}"
|
22
|
+
else
|
23
|
+
"#{column} #{dir}"
|
24
|
+
end
|
26
25
|
end
|
27
26
|
end
|
28
27
|
end
|
data/lib/record_filter/query.rb
CHANGED
@@ -20,15 +20,7 @@ module RecordFilter
|
|
20
20
|
params = {}
|
21
21
|
conditions = @conjunction.to_conditions
|
22
22
|
params = { :conditions => conditions } if conditions
|
23
|
-
|
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
|
31
|
-
end
|
23
|
+
add_joins(params, count_query)
|
32
24
|
orders = @table.orders
|
33
25
|
params[:order] = orders.map { |order| order.to_sql } * ', ' unless orders.empty?
|
34
26
|
group_bys = @table.group_bys
|
@@ -41,6 +33,18 @@ module RecordFilter
|
|
41
33
|
|
42
34
|
protected
|
43
35
|
|
36
|
+
def add_joins(params, count_query)
|
37
|
+
joins = @table.all_joins
|
38
|
+
params[:joins] = joins.map { |join| join.to_sql } unless joins.empty?
|
39
|
+
if (joins.any? { |j| j.requires_distinct_select? })
|
40
|
+
if count_query
|
41
|
+
params[:select] = "DISTINCT #{@table.table_name}.#{@table.model_class.primary_key}"
|
42
|
+
else
|
43
|
+
params[:select] = "DISTINCT #{@table.table_name}.*"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
44
48
|
def dsl_for_named_filter(clazz, named_filter)
|
45
49
|
return DSL::DSLFactory.create(clazz) if named_filter.blank?
|
46
50
|
while (clazz)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module RecordFilter
|
2
|
+
class RestrictionFactory # :nodoc: all
|
3
|
+
|
4
|
+
OPERATOR_HASH = {
|
5
|
+
:equal_to => Restrictions::EqualTo,
|
6
|
+
:is_null => Restrictions::IsNull,
|
7
|
+
:less_than => Restrictions::LessThan,
|
8
|
+
:less_than_or_equal_to => Restrictions::LessThanOrEqualTo,
|
9
|
+
:greater_than => Restrictions::GreaterThan,
|
10
|
+
:greater_than_or_equal_to => Restrictions::GreaterThanOrEqualTo,
|
11
|
+
:in => Restrictions::In,
|
12
|
+
:between => Restrictions::Between,
|
13
|
+
:like => Restrictions::Like
|
14
|
+
}
|
15
|
+
|
16
|
+
def self.build(operator, column_name, value, options)
|
17
|
+
OPERATOR_HASH[operator].new(column_name, value, options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
data/lib/record_filter/table.rb
CHANGED
@@ -5,7 +5,7 @@ module RecordFilter
|
|
5
5
|
def initialize(model_class, table_alias = nil)
|
6
6
|
@model_class = model_class
|
7
7
|
@aliased = !table_alias.nil?
|
8
|
-
@table_alias = table_alias ||
|
8
|
+
@table_alias = table_alias || table_name
|
9
9
|
@joins_cache = {}
|
10
10
|
@joins = []
|
11
11
|
@orders = []
|
@@ -13,7 +13,7 @@ module RecordFilter
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def table_name
|
16
|
-
@model_class.quoted_table_name
|
16
|
+
@table_name ||= @model_class.quoted_table_name
|
17
17
|
end
|
18
18
|
|
19
19
|
def join_association(association_name, join_type=nil, options={})
|
@@ -41,6 +41,7 @@ module RecordFilter
|
|
41
41
|
simple_join(association, join_type, options)
|
42
42
|
when :has_and_belongs_to_many
|
43
43
|
compound_join(association, join_type)
|
44
|
+
else raise InvalidJoinException.new("I don't know how to join on an association of type #{association.macro}.")
|
44
45
|
end
|
45
46
|
end
|
46
47
|
end
|
@@ -77,13 +78,7 @@ module RecordFilter
|
|
77
78
|
private
|
78
79
|
|
79
80
|
def simple_join(association, join_type, options)
|
80
|
-
join_predicate =
|
81
|
-
case association.macro
|
82
|
-
when :belongs_to
|
83
|
-
[{ association.options[:foreign_key] || association.primary_key_name.to_sym => @model_class.primary_key }]
|
84
|
-
when :has_many, :has_one
|
85
|
-
[{ association.options[:primary_key] || @model_class.primary_key => association.primary_key_name.to_sym }]
|
86
|
-
end
|
81
|
+
join_predicate = simple_join_predicate(association)
|
87
82
|
|
88
83
|
if association.options[:as]
|
89
84
|
join_predicate << DSL::Restriction.new(association.options[:as].to_s + '_type').equal_to(association.active_record.base_class.name)
|
@@ -101,6 +96,17 @@ module RecordFilter
|
|
101
96
|
join
|
102
97
|
end
|
103
98
|
|
99
|
+
def simple_join_predicate(association)
|
100
|
+
join_predicate =
|
101
|
+
case association.macro
|
102
|
+
when :belongs_to
|
103
|
+
[{ association.options[:foreign_key] || association.primary_key_name.to_sym => @model_class.primary_key }]
|
104
|
+
when :has_many, :has_one
|
105
|
+
[{ association.options[:primary_key] || @model_class.primary_key => association.primary_key_name.to_sym }]
|
106
|
+
else raise InvalidJoinException.new("I don't know how to do a simple join on an association of type #{association.macro}.")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
104
110
|
def compound_join(association, join_type)
|
105
111
|
pivot_join_predicate = [{ @model_class.primary_key => association.primary_key_name.to_sym }]
|
106
112
|
table_name = @model_class.connection.quote_table_name(association.options[:join_table])
|
@@ -116,7 +122,9 @@ module RecordFilter
|
|
116
122
|
protected
|
117
123
|
|
118
124
|
def alias_for_association(association)
|
119
|
-
|
125
|
+
@alias_cache ||= {}
|
126
|
+
@alias_cache[association.name] ||=
|
127
|
+
"#{@aliased ? @table_alias.to_s : @model_class.table_name}__#{association.name.to_s.downcase}"
|
120
128
|
end
|
121
129
|
|
122
130
|
alias_method :alias_for_class, :alias_for_association
|
data/lib/record_filter.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'rubygems'
|
2
|
-
gem 'activerecord', '~> 2.
|
2
|
+
gem 'activerecord', '~> 2.3'
|
3
3
|
require 'active_record'
|
4
4
|
|
5
|
-
%w(active_record query table conjunctions restrictions filter join order group_by dsl).each do |file|
|
5
|
+
%w(active_record column_parser query table conjunctions restrictions restriction_factory filter join order group_by dsl).each do |file|
|
6
6
|
require File.join(File.dirname(__FILE__), 'record_filter', file)
|
7
7
|
end
|
8
8
|
|
data/spec/exception_spec.rb
CHANGED
@@ -34,12 +34,12 @@ describe 'raising exceptions' do
|
|
34
34
|
}.should raise_error(RecordFilter::ColumnNotFoundException)
|
35
35
|
end
|
36
36
|
|
37
|
-
it 'should get ColumnNotFoundException for order' do
|
37
|
+
it 'should not get ColumnNotFoundException for order' do
|
38
38
|
lambda {
|
39
39
|
Post.filter do
|
40
|
-
order(
|
40
|
+
order('this_is_not_there', :asc)
|
41
41
|
end.inspect
|
42
|
-
}.
|
42
|
+
}.should_not raise_error(RecordFilter::ColumnNotFoundException)
|
43
43
|
end
|
44
44
|
|
45
45
|
it 'should not get ColumnNotFoundException for group_by' do
|
@@ -63,6 +63,50 @@ describe 'filter qualifiers' do
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
+
describe 'offsets' do
|
67
|
+
describe 'simple offset setting' do
|
68
|
+
before do
|
69
|
+
Post.filter do
|
70
|
+
with :published, true
|
71
|
+
offset 10
|
72
|
+
end.inspect
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should add the offset to the parameters' do
|
76
|
+
Post.last_find[:offset].should == 10
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'with multiple calls to offset' do
|
81
|
+
before do
|
82
|
+
Post.filter do
|
83
|
+
offset 5
|
84
|
+
with :published, true
|
85
|
+
offset 6
|
86
|
+
end.inspect
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should add the offset to the parameters' do
|
90
|
+
Post.last_find[:offset].should == 6
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe 'offsetting named scopes' do
|
95
|
+
before do
|
96
|
+
@post = Class.new(Post)
|
97
|
+
@post.named_filter(:published_ones) do
|
98
|
+
offset(2)
|
99
|
+
with(:published, false)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should offset the query' do
|
104
|
+
@post.published_ones.inspect
|
105
|
+
@post.last_find[:offset].should == 2
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
66
110
|
describe 'ordering' do
|
67
111
|
describe 'with a simple order supplied' do
|
68
112
|
before do
|
@@ -115,10 +159,24 @@ describe 'filter qualifiers' do
|
|
115
159
|
end.inspect
|
116
160
|
end
|
117
161
|
|
118
|
-
it 'should add the
|
162
|
+
it 'should add the order to the parameters' do
|
119
163
|
Post.last_find[:order].should == %q(posts__photo.path DESC, "posts".permalink ASC)
|
120
164
|
end
|
121
165
|
end
|
166
|
+
|
167
|
+
describe 'with the order supplied as a string' do
|
168
|
+
before do
|
169
|
+
Post.filter do
|
170
|
+
with :published, true
|
171
|
+
group_by(:published)
|
172
|
+
order('SUM(id)', :desc)
|
173
|
+
end.inspect
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should add the order to the query' do
|
177
|
+
Post.last_find[:order].should == %q(SUM(id) DESC)
|
178
|
+
end
|
179
|
+
end
|
122
180
|
end
|
123
181
|
|
124
182
|
describe 'group_by' do
|
data/spec/restrictions_spec.rb
CHANGED
@@ -89,6 +89,13 @@ describe 'RecordFilter restrictions' do
|
|
89
89
|
Post.last_find.should == { :conditions => [%q("posts".blog_id IN (?)), nil] }
|
90
90
|
end
|
91
91
|
|
92
|
+
it 'should do the right thing for IN filters with a range' do
|
93
|
+
Post.filter do
|
94
|
+
with(:blog_id).in(1..5)
|
95
|
+
end.inspect
|
96
|
+
Post.last_find.should == { :conditions => [%q("posts".blog_id IN (?)), 1..5] }
|
97
|
+
end
|
98
|
+
|
92
99
|
it 'should negate is_not_null conditions correctly' do
|
93
100
|
Post.filter do
|
94
101
|
with(:blog_id).is_not_null.not
|
data/spec/test.db
CHANGED
Binary file
|
data/test/performance_test.rb
CHANGED
@@ -19,6 +19,7 @@ ActiveRecord::Base.establish_connection(
|
|
19
19
|
@blog.named_filter :somethings do
|
20
20
|
having(:ads) do
|
21
21
|
with(:content, nil)
|
22
|
+
with(:id).greater_than(25)
|
22
23
|
end
|
23
24
|
join(Post, :left) do
|
24
25
|
on(:id => :blog_id)
|
@@ -28,6 +29,8 @@ ActiveRecord::Base.establish_connection(
|
|
28
29
|
end
|
29
30
|
end
|
30
31
|
group_by(:id)
|
32
|
+
limit(10, 100)
|
33
|
+
order(:ads => :id)
|
31
34
|
end
|
32
35
|
|
33
36
|
10000.times do
|
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.4
|
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-05-
|
13
|
+
date: 2009-05-09 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -28,6 +28,7 @@ files:
|
|
28
28
|
- VERSION.yml
|
29
29
|
- lib/record_filter.rb
|
30
30
|
- lib/record_filter/active_record.rb
|
31
|
+
- lib/record_filter/column_parser.rb
|
31
32
|
- lib/record_filter/conjunctions.rb
|
32
33
|
- lib/record_filter/dsl.rb
|
33
34
|
- lib/record_filter/dsl/class_join.rb
|
@@ -48,6 +49,7 @@ files:
|
|
48
49
|
- lib/record_filter/join.rb
|
49
50
|
- lib/record_filter/order.rb
|
50
51
|
- lib/record_filter/query.rb
|
52
|
+
- lib/record_filter/restriction_factory.rb
|
51
53
|
- lib/record_filter/restrictions.rb
|
52
54
|
- lib/record_filter/table.rb
|
53
55
|
- spec/active_record_spec.rb
|
@@ -64,7 +66,7 @@ files:
|
|
64
66
|
- spec/test.db
|
65
67
|
- test/performance_test.rb
|
66
68
|
- test/test.db
|
67
|
-
has_rdoc:
|
69
|
+
has_rdoc: false
|
68
70
|
homepage: http://github.com/aub/record_filter/tree/master
|
69
71
|
post_install_message:
|
70
72
|
rdoc_options:
|