outoftime-record_filter 0.1.1 → 0.1.3
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/VERSION.yml +1 -1
- data/lib/record_filter/active_record.rb +6 -0
- data/lib/record_filter/conjunctions.rb +40 -20
- data/lib/record_filter/dsl/conjunction_dsl.rb +10 -0
- data/lib/record_filter/dsl/group_by.rb +11 -0
- data/lib/record_filter/dsl/restriction.rb +11 -1
- data/lib/record_filter/filter.rb +1 -1
- data/lib/record_filter/group_by.rb +23 -0
- data/lib/record_filter/restrictions.rb +11 -0
- data/spec/exception_spec.rb +50 -0
- data/spec/implicit_join_spec.rb +19 -0
- data/spec/named_filter_spec.rb +21 -8
- data/spec/restrictions_spec.rb +48 -0
- metadata +6 -2
data/VERSION.yml
CHANGED
@@ -7,6 +7,8 @@ module RecordFilter
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def named_filter(name, &block)
|
10
|
+
return if named_filters.include?(name.to_sym)
|
11
|
+
named_filters << name.to_sym
|
10
12
|
DSL::DSL::subclass(self).module_eval do
|
11
13
|
define_method(name, &block)
|
12
14
|
end
|
@@ -17,6 +19,10 @@ module RecordFilter
|
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
22
|
+
|
23
|
+
def named_filters
|
24
|
+
read_inheritable_attribute(:named_filters) || write_inheritable_attribute(:named_filters, [])
|
25
|
+
end
|
20
26
|
end
|
21
27
|
end
|
22
28
|
end
|
@@ -7,6 +7,8 @@ module RecordFilter
|
|
7
7
|
result = case dsl_conjunction.type
|
8
8
|
when :any_of then AnyOf.new(table)
|
9
9
|
when :all_of then AllOf.new(table)
|
10
|
+
when :none_of then NoneOf.new(table)
|
11
|
+
when :not_all_of then NotAllOf.new(table)
|
10
12
|
end
|
11
13
|
|
12
14
|
dsl_conjunction.steps.each do |step|
|
@@ -50,7 +52,13 @@ module RecordFilter
|
|
50
52
|
end
|
51
53
|
|
52
54
|
def add_join_on_association(association_name)
|
53
|
-
@table
|
55
|
+
table = @table
|
56
|
+
while association_name.is_a?(Hash)
|
57
|
+
result = table.join_association(association_name.keys[0])
|
58
|
+
table = result.right_table
|
59
|
+
association_name = association_name.values[0]
|
60
|
+
end
|
61
|
+
table.join_association(association_name)
|
54
62
|
end
|
55
63
|
|
56
64
|
def add_order(column_name, direction)
|
@@ -70,21 +78,25 @@ module RecordFilter
|
|
70
78
|
end
|
71
79
|
|
72
80
|
def to_conditions
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
@restrictions.
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
81
|
+
result = begin
|
82
|
+
if @restrictions.empty?
|
83
|
+
nil
|
84
|
+
elsif @restrictions.length == 1
|
85
|
+
@restrictions.first.to_conditions
|
86
|
+
else
|
87
|
+
@restrictions.map do |restriction|
|
88
|
+
conditions = restriction.to_conditions
|
89
|
+
conditions[0] = "(#{conditions[0]})"
|
90
|
+
conditions
|
91
|
+
end.inject do |conditions, new_conditions|
|
92
|
+
conditions.first << " #{conjunctor} #{new_conditions.shift}"
|
93
|
+
conditions.concat(new_conditions)
|
94
|
+
conditions
|
95
|
+
end
|
86
96
|
end
|
87
97
|
end
|
98
|
+
result[0] = "!(#{result[0]})" if (negated && !result.nil? && !result[0].nil?)
|
99
|
+
result
|
88
100
|
end
|
89
101
|
|
90
102
|
protected
|
@@ -97,15 +109,23 @@ module RecordFilter
|
|
97
109
|
end
|
98
110
|
|
99
111
|
class AnyOf < Base
|
100
|
-
def conjunctor
|
101
|
-
|
102
|
-
end
|
112
|
+
def conjunctor; 'OR'; end
|
113
|
+
def negated; false; end
|
103
114
|
end
|
104
115
|
|
105
116
|
class AllOf < Base
|
106
|
-
def conjunctor
|
107
|
-
|
108
|
-
|
117
|
+
def conjunctor; 'AND'; end
|
118
|
+
def negated; false; end
|
119
|
+
end
|
120
|
+
|
121
|
+
class NoneOf < Base
|
122
|
+
def conjunctor; 'OR'; end
|
123
|
+
def negated; true; end
|
124
|
+
end
|
125
|
+
|
126
|
+
class NotAllOf < Base
|
127
|
+
def conjunctor; 'AND'; end
|
128
|
+
def negated; true; end
|
109
129
|
end
|
110
130
|
end
|
111
131
|
end
|
@@ -30,6 +30,16 @@ module RecordFilter
|
|
30
30
|
nil
|
31
31
|
end
|
32
32
|
|
33
|
+
def none_of(&block)
|
34
|
+
@conjunction.add_conjunction(:none_of, &block)
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def not_all_of(&block)
|
39
|
+
@conjunction.add_conjunction(:not_all_of, &block)
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
33
43
|
# join
|
34
44
|
def having(association, &block)
|
35
45
|
join = @conjunction.add_join(association, &block)
|
@@ -8,7 +8,7 @@ module RecordFilter
|
|
8
8
|
@column, @negated, @operator = column, negated, nil
|
9
9
|
end
|
10
10
|
|
11
|
-
[:equal_to, :is_null, :less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to, :in, :
|
11
|
+
[:equal_to, :is_null, :less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to, :in, :like].each do |operator|
|
12
12
|
define_method(operator) do |*args|
|
13
13
|
@value = args[0]
|
14
14
|
@operator = operator
|
@@ -16,6 +16,16 @@ module RecordFilter
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
# Between can take either a tuple of [start, finish], a range, or two values.
|
20
|
+
def between(start, finish=nil)
|
21
|
+
@operator = :between
|
22
|
+
if !finish.nil?
|
23
|
+
@value = [start, finish]
|
24
|
+
else
|
25
|
+
@value = start
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
19
29
|
alias_method :gt, :greater_than
|
20
30
|
alias_method :gte, :greater_than_or_equal_to
|
21
31
|
alias_method :lt, :less_than
|
data/lib/record_filter/filter.rb
CHANGED
@@ -17,7 +17,7 @@ module RecordFilter
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def method_missing(method, *args, &block)
|
20
|
-
if @clazz.
|
20
|
+
if @clazz.named_filters.include?(method)
|
21
21
|
Filter.new(@clazz, method, @dsl.conjunction, *args)
|
22
22
|
else
|
23
23
|
loaded_data.send(method, *args, &block)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RecordFilter
|
2
|
+
class GroupBy
|
3
|
+
attr_reader :column, :table
|
4
|
+
|
5
|
+
def initialize(column, table)
|
6
|
+
@column, @table = column, table
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_sql
|
10
|
+
table, column = @table, @column
|
11
|
+
while column.is_a?(Hash)
|
12
|
+
table = table.join_association(column.keys[0]).right_table
|
13
|
+
column = column.values[0]
|
14
|
+
end
|
15
|
+
|
16
|
+
if (!table.has_column(column))
|
17
|
+
raise ColumnNotFoundException.new("The column #{column} was not found in #{table.table_name}.")
|
18
|
+
end
|
19
|
+
|
20
|
+
"#{table.table_name}.#{column}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -3,6 +3,7 @@ module RecordFilter
|
|
3
3
|
class Base
|
4
4
|
def initialize(column_name, value, options={})
|
5
5
|
@column_name, @value, @negated = column_name, value, !!options.delete(:negated)
|
6
|
+
@value = @value.id if @value.kind_of?(ActiveRecord::Base)
|
6
7
|
end
|
7
8
|
|
8
9
|
def to_conditions
|
@@ -77,5 +78,15 @@ module RecordFilter
|
|
77
78
|
["#{@column_name} #{'NOT ' if @negated}BETWEEN ? AND ?", @value.first, @value.last]
|
78
79
|
end
|
79
80
|
end
|
81
|
+
|
82
|
+
class Like < Base
|
83
|
+
def to_positive_sql
|
84
|
+
"#{@column_name} LIKE ?"
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_negative_sql
|
88
|
+
"#{@column_name} NOT LIKE ?"
|
89
|
+
end
|
90
|
+
end
|
80
91
|
end
|
81
92
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe 'raising exceptions' do
|
4
|
+
|
5
|
+
describe 'on missing associations' do
|
6
|
+
it 'should get AssociationNotFoundException' do
|
7
|
+
lambda {
|
8
|
+
Post.filter do
|
9
|
+
having(:something_that_does_not_exist) do
|
10
|
+
with(:something_bad)
|
11
|
+
end
|
12
|
+
end.inspect
|
13
|
+
}.should raise_error(RecordFilter::AssociationNotFoundException)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'on missing columns' do
|
18
|
+
it 'should get ColumnNotFoundException for with' do
|
19
|
+
lambda {
|
20
|
+
Post.filter do
|
21
|
+
with(:this_is_not_there, 2)
|
22
|
+
end.inspect
|
23
|
+
}.should raise_error(RecordFilter::ColumnNotFoundException)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should get ColumnNotFoundException for without' do
|
27
|
+
lambda {
|
28
|
+
Post.filter do
|
29
|
+
without(:this_is_not_there, 2)
|
30
|
+
end.inspect
|
31
|
+
}.should raise_error(RecordFilter::ColumnNotFoundException)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should get ColumnNotFoundException for order' do
|
35
|
+
lambda {
|
36
|
+
Post.filter do
|
37
|
+
order(:this_is_not_there, :asc)
|
38
|
+
end.inspect
|
39
|
+
}.should raise_error(RecordFilter::ColumnNotFoundException)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should get AssociationNotFoundException for orders on bad associations' do
|
43
|
+
lambda {
|
44
|
+
Post.filter do
|
45
|
+
order({ :this_is_not_there => :eh }, :asc)
|
46
|
+
end.inspect
|
47
|
+
}.should raise_error(RecordFilter::AssociationNotFoundException)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/spec/implicit_join_spec.rb
CHANGED
@@ -93,6 +93,25 @@ describe 'implicit joins' do
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
+
describe 'with one having statement expressing multiple joins' do
|
97
|
+
before do
|
98
|
+
Blog.filter do
|
99
|
+
having(:posts => :comments) do
|
100
|
+
with :offensive, true
|
101
|
+
end
|
102
|
+
end.inspect
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should add both joins' do
|
106
|
+
Blog.last_find[:joins].should == %q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id ) +
|
107
|
+
%q(INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'should query against both conditions' do
|
111
|
+
Blog.last_find[:conditions].should == [%q(blogs__posts__comments.offensive = ?), true]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
96
115
|
describe 'on has_one' do
|
97
116
|
before do
|
98
117
|
Post.filter do
|
data/spec/named_filter_spec.rb
CHANGED
@@ -54,8 +54,22 @@ describe 'named filters' do
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
+
describe 'taking active_record objects as arguments' do
|
58
|
+
it 'should use the id of the object as the actual parameter' do
|
59
|
+
Post.named_filter(:with_ar_arg) do |blog|
|
60
|
+
with(:blog_id, blog)
|
61
|
+
end
|
62
|
+
blog = Blog.create
|
63
|
+
Post.with_ar_arg(blog).inspect
|
64
|
+
Post.last_find[:conditions].should == [%q("posts".blog_id = ?), blog.id]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
57
68
|
describe 'using filters in subclasses' do
|
58
69
|
before do
|
70
|
+
Comment.named_filter(:with_contents) do |*args|
|
71
|
+
with :contents, args[0]
|
72
|
+
end
|
59
73
|
class NiceComment < Comment
|
60
74
|
extend TestModel
|
61
75
|
|
@@ -63,9 +77,6 @@ describe 'named filters' do
|
|
63
77
|
with :offensive, true
|
64
78
|
end
|
65
79
|
end
|
66
|
-
Comment.named_filter(:with_contents) do |*args|
|
67
|
-
with :contents, args[0]
|
68
|
-
end
|
69
80
|
end
|
70
81
|
|
71
82
|
it 'should execute the parent class filters correctly' do
|
@@ -81,17 +92,19 @@ describe 'named filters' do
|
|
81
92
|
NiceComment.offensive.with_contents('something').inspect
|
82
93
|
NiceComment.last_find[:conditions].should == [%q(("comments".offensive = ?) AND ("comments".contents = ?)), true, 'something']
|
83
94
|
end
|
95
|
+
|
96
|
+
it 'should provide access to the named filters' do
|
97
|
+
Comment.named_filters.should == [:with_contents]
|
98
|
+
NiceComment.named_filters.sort_by { |i| i.to_s }.should == [:offensive, :with_contents]
|
99
|
+
end
|
84
100
|
end
|
85
101
|
|
86
102
|
describe 'using compound filters' do
|
87
|
-
|
103
|
+
it 'should concatenate the filters correctly' do
|
104
|
+
pending 'nested chaining'
|
88
105
|
Post.named_filter(:with_offensive_comments) do
|
89
106
|
having(:comments).offensive(true)
|
90
107
|
end
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'should concatenate the filters correctly' do
|
94
|
-
pending 'nested chaining'
|
95
108
|
Post.with_offensive_comments.inspect
|
96
109
|
Post.last_find[:conditions].should == [%q(posts__comments.offensive = ?), true]
|
97
110
|
Post.last_find[:joins].should == %q(INNER JOIN "comments" AS posts__comments ON "comments".post_id = posts__blog.id)
|
data/spec/restrictions_spec.rb
CHANGED
@@ -51,6 +51,40 @@ describe 'RecordFilter restrictions' do
|
|
51
51
|
Post.last_find.should == { :conditions => [%q{"posts".created_at BETWEEN ? AND ?}, time1, time2] }
|
52
52
|
end
|
53
53
|
|
54
|
+
it 'should filter for between with two arguments passed' do
|
55
|
+
Post.filter do
|
56
|
+
with(:id).between(1, 5)
|
57
|
+
end.inspect
|
58
|
+
Post.last_find.should == { :conditions => [%q("posts".id BETWEEN ? AND ?), 1, 5] }
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should filter for between with an array passed' do
|
62
|
+
Post.filter do
|
63
|
+
with(:id).between([2, 6])
|
64
|
+
end.inspect
|
65
|
+
Post.last_find.should == { :conditions => [%q("posts".id BETWEEN ? AND ?), 2, 6] }
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should filter by none_of' do
|
69
|
+
Post.filter do
|
70
|
+
none_of do
|
71
|
+
with(:blog_id, 1)
|
72
|
+
with(:permalink, 'eek')
|
73
|
+
end
|
74
|
+
end.inspect
|
75
|
+
Post.last_find.should == { :conditions => [%q{!(("posts".blog_id = ?) OR ("posts".permalink = ?))}, 1, 'eek'] }
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should filter by not_all_of' do
|
79
|
+
Post.filter do
|
80
|
+
not_all_of do
|
81
|
+
with(:blog_id, 1)
|
82
|
+
with(:permalink, 'eek')
|
83
|
+
end
|
84
|
+
end.inspect
|
85
|
+
Post.last_find.should == { :conditions => [%q{!(("posts".blog_id = ?) AND ("posts".permalink = ?))}, 1, 'eek'] }
|
86
|
+
end
|
87
|
+
|
54
88
|
it 'should filter by disjunction' do
|
55
89
|
Post.filter do
|
56
90
|
any_of do
|
@@ -84,4 +118,18 @@ describe 'RecordFilter restrictions' do
|
|
84
118
|
Post.last_find.should == { :conditions => [%q("posts".permalink IS NULL)] }
|
85
119
|
end
|
86
120
|
end
|
121
|
+
|
122
|
+
it 'should support like' do
|
123
|
+
Post.filter do
|
124
|
+
with(:permalink).like('%piglets%')
|
125
|
+
end.inspect
|
126
|
+
Post.last_find.should == { :conditions => [%q("posts".permalink LIKE ?), '%piglets%'] }
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should support NOT LIKE' do
|
130
|
+
Post.filter do
|
131
|
+
without(:permalink).like('%ostriches%')
|
132
|
+
end.inspect
|
133
|
+
Post.last_find.should == { :conditions => [%q("posts".permalink NOT LIKE ?), '%ostriches%'] }
|
134
|
+
end
|
87
135
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: outoftime-record_filter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
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-04-
|
13
|
+
date: 2009-04-20 00:00:00 -07:00
|
14
14
|
default_executable:
|
15
15
|
dependencies: []
|
16
16
|
|
@@ -32,17 +32,20 @@ files:
|
|
32
32
|
- lib/record_filter/dsl/conjunction.rb
|
33
33
|
- lib/record_filter/dsl/conjunction_dsl.rb
|
34
34
|
- lib/record_filter/dsl/dsl.rb
|
35
|
+
- lib/record_filter/dsl/group_by.rb
|
35
36
|
- lib/record_filter/dsl/join.rb
|
36
37
|
- lib/record_filter/dsl/limit.rb
|
37
38
|
- lib/record_filter/dsl/order.rb
|
38
39
|
- lib/record_filter/dsl/restriction.rb
|
39
40
|
- lib/record_filter/filter.rb
|
41
|
+
- lib/record_filter/group_by.rb
|
40
42
|
- lib/record_filter/join.rb
|
41
43
|
- lib/record_filter/join_table.rb
|
42
44
|
- lib/record_filter/order.rb
|
43
45
|
- lib/record_filter/query.rb
|
44
46
|
- lib/record_filter/restrictions.rb
|
45
47
|
- lib/record_filter/table.rb
|
48
|
+
- spec/exception_spec.rb
|
46
49
|
- spec/implicit_join_spec.rb
|
47
50
|
- spec/limits_and_ordering_spec.rb
|
48
51
|
- spec/models/blog.rb
|
@@ -81,6 +84,7 @@ signing_key:
|
|
81
84
|
specification_version: 3
|
82
85
|
summary: Pure-ruby criteria API for building complex queries in ActiveRecord
|
83
86
|
test_files:
|
87
|
+
- spec/exception_spec.rb
|
84
88
|
- spec/implicit_join_spec.rb
|
85
89
|
- spec/limits_and_ordering_spec.rb
|
86
90
|
- spec/models/blog.rb
|