record_filter 0.9.17 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION.yml +3 -3
- data/lib/record_filter/conjunctions.rb +6 -1
- data/lib/record_filter/dsl/conjunction.rb +5 -1
- data/lib/record_filter/dsl/dsl.rb +13 -2
- data/lib/record_filter/dsl/restriction.rb +1 -1
- data/lib/record_filter/filter.rb +14 -0
- data/lib/record_filter/query.rb +13 -3
- data/lib/record_filter/restrictions.rb +20 -4
- data/record_filter.gemspec +4 -2
- data/spec/explicit_subquery_spec.rb +55 -0
- data/spec/select_spec.rb +41 -4
- data/spec/test.db +0 -0
- metadata +6 -4
data/VERSION.yml
CHANGED
@@ -1,4 +1,4 @@
|
|
1
1
|
---
|
2
|
-
:major:
|
3
|
-
:minor:
|
4
|
-
:patch:
|
2
|
+
:major: 1
|
3
|
+
:minor: 0
|
4
|
+
:patch: 0
|
@@ -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, :distinct
|
4
|
+
attr_reader :table_name, :limit, :offset, :distinct, :select_columns
|
5
5
|
|
6
6
|
def self.create_from(dsl_conjunction, table)
|
7
7
|
result = case dsl_conjunction.type
|
@@ -39,6 +39,7 @@ module RecordFilter
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
result.set_distinct(dsl_conjunction.distinct)
|
42
|
+
result.set_select_columns(dsl_conjunction.select_columns)
|
42
43
|
result
|
43
44
|
end
|
44
45
|
|
@@ -94,6 +95,10 @@ module RecordFilter
|
|
94
95
|
@distinct = value
|
95
96
|
end
|
96
97
|
|
98
|
+
def set_select_columns(select_columns)
|
99
|
+
@select_columns = select_columns
|
100
|
+
end
|
101
|
+
|
97
102
|
def add_named_filter(name, args)
|
98
103
|
unless @table.model_class.named_filters.include?(name.to_sym)
|
99
104
|
raise NamedFilterNotFoundException.new("The named filter #{name} was not found in #{@table.model_class}")
|
@@ -2,7 +2,7 @@ module RecordFilter
|
|
2
2
|
module DSL
|
3
3
|
class Conjunction # :nodoc: all
|
4
4
|
|
5
|
-
attr_reader :type, :steps, :distinct
|
5
|
+
attr_reader :type, :steps, :distinct, :select_columns
|
6
6
|
|
7
7
|
def initialize(model_class, type=:all_of)
|
8
8
|
@model_class, @type, @steps, @distinct = model_class, type, [], false
|
@@ -53,6 +53,10 @@ module RecordFilter
|
|
53
53
|
@distinct = true
|
54
54
|
end
|
55
55
|
|
56
|
+
def set_select_columns(columns)
|
57
|
+
@select_columns = columns
|
58
|
+
end
|
59
|
+
|
56
60
|
def add_named_filter(method, *args)
|
57
61
|
@steps << NamedFilter.new(method, *args)
|
58
62
|
end
|
@@ -129,16 +129,27 @@ module RecordFilter
|
|
129
129
|
# block, etc.).
|
130
130
|
#
|
131
131
|
# ==== Parameters
|
132
|
-
#
|
132
|
+
#
|
133
|
+
# columns...<Symbol>:
|
134
|
+
# Zero or more column names to select. Equivalent to
|
135
|
+
# select(:col1, :col2) ; distinct. If empty, select all columns.
|
133
136
|
#
|
134
137
|
# ==== Returns
|
135
138
|
# nil
|
136
139
|
#
|
137
140
|
# @public
|
138
|
-
def distinct
|
141
|
+
def distinct(*columns)
|
142
|
+
select(*columns) unless columns.empty?
|
139
143
|
@conjunction.set_distinct
|
140
144
|
nil
|
141
145
|
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# Specify one or more columns to select from the database.
|
149
|
+
#
|
150
|
+
def select(*columns)
|
151
|
+
@conjunction.set_select_columns(columns)
|
152
|
+
end
|
142
153
|
end
|
143
154
|
end
|
144
155
|
end
|
data/lib/record_filter/filter.rb
CHANGED
@@ -65,6 +65,20 @@ module RecordFilter
|
|
65
65
|
@query.to_find_params(count_query)
|
66
66
|
end
|
67
67
|
|
68
|
+
def to_subquery(single_result = true)
|
69
|
+
proxy_options = proxy_options()
|
70
|
+
if @query.select_columns && @query.select_columns.length > 1
|
71
|
+
raise(InvalidFilterException, "Only one select column allowed in a subquery")
|
72
|
+
end
|
73
|
+
@clazz.module_eval do
|
74
|
+
options = proxy_options.reverse_merge(
|
75
|
+
:select => "#{quoted_table_name}.#{primary_key}"
|
76
|
+
)
|
77
|
+
options.merge!(:limit => 1) if single_result
|
78
|
+
construct_finder_sql(options)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
68
82
|
protected
|
69
83
|
|
70
84
|
def method_missing(method, *args, &block) # :nodoc:
|
data/lib/record_filter/query.rb
CHANGED
@@ -36,15 +36,25 @@ module RecordFilter
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
def select_columns
|
40
|
+
@conjunction.select_columns
|
41
|
+
end
|
42
|
+
|
39
43
|
protected
|
40
44
|
|
41
45
|
def set_select(params, count_query)
|
42
|
-
|
46
|
+
select_column_statement =
|
43
47
|
if count_query
|
44
|
-
|
48
|
+
"#{@table.table_name}.#{@table.model_class.primary_key}"
|
49
|
+
elsif select_columns
|
50
|
+
select_columns.map { |column| "#{@table.table_name}.#{column}" }.join(', ')
|
45
51
|
else
|
46
|
-
|
52
|
+
"#{@table.table_name}.*"
|
47
53
|
end
|
54
|
+
if @conjunction.distinct || (@table.all_joins.any? { |j| j.requires_distinct_select? })
|
55
|
+
params[:select] = "DISTINCT #{select_column_statement}"
|
56
|
+
elsif @conjunction.select_columns
|
57
|
+
params[:select] = select_column_statement
|
48
58
|
end
|
49
59
|
end
|
50
60
|
|
@@ -5,11 +5,11 @@ module RecordFilter
|
|
5
5
|
|
6
6
|
def initialize(column_name, value, table, options={})
|
7
7
|
@column_name, @value, @table, @negated = column_name, value, table, !!options.delete(:negated)
|
8
|
-
@value = @value.id if @value.kind_of?(ActiveRecord::Base)
|
8
|
+
@value = @value.id if !subquery? && @value.kind_of?(ActiveRecord::Base)
|
9
9
|
end
|
10
10
|
|
11
11
|
def to_conditions
|
12
|
-
if @value.nil? || @value.is_a?(Hash)
|
12
|
+
if subquery? || @value.nil? || @value.is_a?(Hash)
|
13
13
|
[to_sql]
|
14
14
|
else
|
15
15
|
[to_sql, @value]
|
@@ -25,7 +25,9 @@ module RecordFilter
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def value_hash_as_column_or_question_mark
|
28
|
-
if
|
28
|
+
if subquery?
|
29
|
+
"(#{@value.to_subquery})"
|
30
|
+
elsif @value.is_a?(Hash)
|
29
31
|
column, table = parse_column_in_table(@value, @table)
|
30
32
|
if (table.has_column(column))
|
31
33
|
"#{table.table_alias}.#{column}"
|
@@ -34,6 +36,10 @@ module RecordFilter
|
|
34
36
|
'?'
|
35
37
|
end
|
36
38
|
end
|
39
|
+
|
40
|
+
def subquery?
|
41
|
+
@value.respond_to?(:to_subquery)
|
42
|
+
end
|
37
43
|
end
|
38
44
|
|
39
45
|
class EqualTo < Base
|
@@ -94,12 +100,22 @@ module RecordFilter
|
|
94
100
|
# produce a condition like "WHERE id NOT IN (NULL)", and that will
|
95
101
|
# return an empty set, which is presumably not what the user meant
|
96
102
|
# to get. Doing some babysitting here.
|
97
|
-
if
|
103
|
+
if subquery?
|
104
|
+
[to_sql]
|
105
|
+
elsif @negated && @value.respond_to?(:empty?) && @value.empty?
|
98
106
|
[]
|
99
107
|
else
|
100
108
|
[to_sql, @value]
|
101
109
|
end
|
102
110
|
end
|
111
|
+
|
112
|
+
def value_hash_as_column_or_question_mark
|
113
|
+
if subquery?
|
114
|
+
@value.to_subquery(false)
|
115
|
+
else
|
116
|
+
super
|
117
|
+
end
|
118
|
+
end
|
103
119
|
end
|
104
120
|
|
105
121
|
class Between < Base
|
data/record_filter.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{record_filter}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "1.0.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Aubrey Holland", "Mat Brown"]
|
12
|
-
s.date = %q{2010-03-
|
12
|
+
s.date = %q{2010-03-18}
|
13
13
|
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.}
|
14
14
|
s.email = %q{aubreyholland@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -55,6 +55,7 @@ Gem::Specification.new do |s|
|
|
55
55
|
"spec/active_record_spec.rb",
|
56
56
|
"spec/exception_spec.rb",
|
57
57
|
"spec/explicit_join_spec.rb",
|
58
|
+
"spec/explicit_subquery_spec.rb",
|
58
59
|
"spec/implicit_join_spec.rb",
|
59
60
|
"spec/limits_and_ordering_spec.rb",
|
60
61
|
"spec/models.rb",
|
@@ -80,6 +81,7 @@ Gem::Specification.new do |s|
|
|
80
81
|
"spec/active_record_spec.rb",
|
81
82
|
"spec/exception_spec.rb",
|
82
83
|
"spec/explicit_join_spec.rb",
|
84
|
+
"spec/explicit_subquery_spec.rb",
|
83
85
|
"spec/implicit_join_spec.rb",
|
84
86
|
"spec/limits_and_ordering_spec.rb",
|
85
87
|
"spec/models.rb",
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe 'explicit subqueries' do
|
4
|
+
before :each do
|
5
|
+
TestModel.extended_models.each { |model| model.last_find = {} }
|
6
|
+
end
|
7
|
+
|
8
|
+
after :each do
|
9
|
+
Blog.last_find.should be_empty
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'IN subquery' do
|
13
|
+
it 'should generate an explicit subquery on ID field' do
|
14
|
+
Post.filter do
|
15
|
+
with(:blog_id).in(Blog.filter { with(:name, 'Test Name') })
|
16
|
+
end.inspect
|
17
|
+
Post.last_find[:conditions].should ==
|
18
|
+
[%q("posts".blog_id IN (SELECT "blogs".id FROM "blogs" WHERE ("blogs".name = 'Test Name') ))]
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should generate an explicit subquery on selected field' do
|
22
|
+
Post.filter do
|
23
|
+
with(:blog_id).in(Blog.filter { select(:name) ; with(:name, 'Test Name') })
|
24
|
+
end.inspect
|
25
|
+
Post.last_find[:conditions].should ==
|
26
|
+
[%q("posts".blog_id IN (SELECT "blogs".name FROM "blogs" WHERE ("blogs".name = 'Test Name') ))]
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should perform a negated subquery' do
|
30
|
+
Post.filter do
|
31
|
+
with(:blog_id).not.in(Blog.filter { with(:name, 'Test Name') })
|
32
|
+
end.inspect
|
33
|
+
Post.last_find[:conditions].should ==
|
34
|
+
[%q("posts".blog_id NOT IN (SELECT "blogs".id FROM "blogs" WHERE ("blogs".name = 'Test Name') ))]
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should fail fast if subselect performed with multiple select fields' do
|
38
|
+
lambda do
|
39
|
+
Post.filter do
|
40
|
+
with(:blog_id).in(Blog.filter { select(:id, :name) ; with(:name, 'Test Name') })
|
41
|
+
end.inspect
|
42
|
+
end.should raise_error(RecordFilter::InvalidFilterException)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'equality subquery' do
|
47
|
+
it 'should perform subquery and implicitly add limit' do
|
48
|
+
Post.filter do
|
49
|
+
with(:blog_id, Blog.filter { with(:name, 'Test Name') })
|
50
|
+
end.inspect
|
51
|
+
Post.last_find[:conditions].should ==
|
52
|
+
[%q("posts".blog_id = (SELECT "blogs".id FROM "blogs" WHERE ("blogs".name = 'Test Name') LIMIT 1))]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/spec/select_spec.rb
CHANGED
@@ -6,22 +6,40 @@ describe 'with custom selects for cases where DISTINCT is required' do
|
|
6
6
|
end
|
7
7
|
|
8
8
|
describe 'on a standard filter' do
|
9
|
-
it 'should put nothing in the select' do
|
9
|
+
it 'should put nothing in the select by default' do
|
10
10
|
Post.filter do
|
11
11
|
having(:comments).with(:offensive, true)
|
12
12
|
end.inspect
|
13
13
|
Post.last_find[:select].should be_nil
|
14
14
|
end
|
15
|
+
|
16
|
+
it 'should use the columns specified in the select' do
|
17
|
+
Post.filter do
|
18
|
+
select(:id, :title)
|
19
|
+
having(:comments).with(:offensive, true)
|
20
|
+
end.inspect
|
21
|
+
Post.last_find[:select].should == '"posts".id, "posts".title'
|
22
|
+
end
|
23
|
+
|
24
|
+
#XXX check bogus column names
|
15
25
|
end
|
16
26
|
|
17
|
-
|
18
|
-
|
19
|
-
|
27
|
+
[:left, :right].each do |join_type|
|
28
|
+
describe "with #{join_type} join" do
|
29
|
+
it 'should put the distinct clause in the select' do
|
20
30
|
Post.filter do
|
21
31
|
having(:comments, :join_type => join_type).with(:offensive, true)
|
22
32
|
end.inspect rescue nil # required because sqlite doesn't support right joins
|
23
33
|
Post.last_find[:select].should == %q(DISTINCT "posts".*)
|
24
34
|
end
|
35
|
+
|
36
|
+
it 'should put the distinct clause with the columns specified in the select' do
|
37
|
+
Post.filter do
|
38
|
+
select(:id, :title)
|
39
|
+
having(:comments, :join_type => join_type).with(:offensive, true)
|
40
|
+
end.inspect rescue nil # required because sqlite doesn't support right joins
|
41
|
+
Post.last_find[:select].should == %q(DISTINCT "posts".id, "posts".title)
|
42
|
+
end
|
25
43
|
end
|
26
44
|
end
|
27
45
|
|
@@ -63,6 +81,25 @@ describe 'with custom selects for cases where DISTINCT is required' do
|
|
63
81
|
end.inspect
|
64
82
|
Blog.last_find[:select].should == %q(DISTINCT "blogs".*)
|
65
83
|
end
|
84
|
+
|
85
|
+
it 'should create a distinct query on selected columns' do
|
86
|
+
Blog.filter do
|
87
|
+
with(:created_at).gt(1.day.ago)
|
88
|
+
having(:posts).with(:permalink, nil)
|
89
|
+
distinct
|
90
|
+
select(:id, :name)
|
91
|
+
end.inspect
|
92
|
+
Blog.last_find[:select].should == %q(DISTINCT "blogs".id, "blogs".name)
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should create a distinct query on selected columns passed into distinct()' do
|
96
|
+
Blog.filter do
|
97
|
+
with(:created_at).gt(1.day.ago)
|
98
|
+
having(:posts).with(:permalink, nil)
|
99
|
+
distinct(:id, :name)
|
100
|
+
end.inspect
|
101
|
+
Blog.last_find[:select].should == %q(DISTINCT "blogs".id, "blogs".name)
|
102
|
+
end
|
66
103
|
end
|
67
104
|
|
68
105
|
describe 'using the distinct method when chaining named scopes with joins that do not need it' do
|
data/spec/test.db
CHANGED
Binary file
|
metadata
CHANGED
@@ -3,10 +3,10 @@ name: record_filter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
|
+
- 1
|
6
7
|
- 0
|
7
|
-
-
|
8
|
-
|
9
|
-
version: 0.9.17
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Aubrey Holland
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-03-
|
18
|
+
date: 2010-03-18 00:00:00 -04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -90,6 +90,7 @@ files:
|
|
90
90
|
- spec/active_record_spec.rb
|
91
91
|
- spec/exception_spec.rb
|
92
92
|
- spec/explicit_join_spec.rb
|
93
|
+
- spec/explicit_subquery_spec.rb
|
93
94
|
- spec/implicit_join_spec.rb
|
94
95
|
- spec/limits_and_ordering_spec.rb
|
95
96
|
- spec/models.rb
|
@@ -138,6 +139,7 @@ test_files:
|
|
138
139
|
- spec/active_record_spec.rb
|
139
140
|
- spec/exception_spec.rb
|
140
141
|
- spec/explicit_join_spec.rb
|
142
|
+
- spec/explicit_subquery_spec.rb
|
141
143
|
- spec/implicit_join_spec.rb
|
142
144
|
- spec/limits_and_ordering_spec.rb
|
143
145
|
- spec/models.rb
|