outoftime-record_filter 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 4
2
+ :patch: 0
3
3
  :major: 0
4
- :minor: 1
4
+ :minor: 2
@@ -3,7 +3,7 @@ module RecordFilter
3
3
  module ClassMethods
4
4
 
5
5
  def filter(&block)
6
- Filter.new(self, nil, nil, &block)
6
+ Filter.new(self, nil, &block)
7
7
  end
8
8
 
9
9
  def named_filter(name, &block)
@@ -15,7 +15,7 @@ module RecordFilter
15
15
 
16
16
  (class << self; self; end).instance_eval do
17
17
  define_method(name.to_s) do |*args|
18
- Filter.new(self, name, nil, *args)
18
+ Filter.new(self, name, *args)
19
19
  end
20
20
  end
21
21
  end
@@ -1,44 +1,92 @@
1
1
  module RecordFilter
2
2
  class Filter
3
3
 
4
- delegate :inspect, :to => :loaded_data
4
+ NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? respond_to?)
5
+ [].methods.each do |m|
6
+ unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
7
+ delegate m, :to => :loaded_data
8
+ end
9
+ end
5
10
 
6
- def initialize(clazz, named_filter, combine_conjunction, *args, &block)
11
+ def initialize(clazz, named_filter, *args, &block)
12
+ @current_scoped_methods = clazz.send(:current_scoped_methods)
7
13
  @clazz = clazz
8
14
 
9
15
  @dsl = dsl_for_named_filter(@clazz, named_filter)
10
16
  @dsl.instance_eval(&block) if block
11
17
  @dsl.send(named_filter, *args) if named_filter && @dsl.respond_to?(named_filter)
12
- @dsl.conjunction.steps.unshift(combine_conjunction.steps).flatten! if combine_conjunction
18
+ @query = Query.new(@clazz, @dsl.conjunction)
13
19
  end
14
20
 
15
- def filter(&block)
16
- Filter.new(@clazz, nil, @dsl.conjunction, &block)
21
+ def first(*args)
22
+ if args.first.kind_of?(Integer)
23
+ loaded_data.first(*args)
24
+ else
25
+ do_with_scope do
26
+ @clazz.find(:first, *args)
27
+ end
28
+ end
17
29
  end
18
30
 
19
- def method_missing(method, *args, &block)
20
- if @clazz.named_filters.include?(method)
21
- Filter.new(@clazz, method, @dsl.conjunction, *args)
22
- elsif [:size, :count, :length].include?(method)
23
- loaded_count_data.send(method, *args, &block)
31
+ def last(*args)
32
+ if args.first.kind_of?(Integer)
33
+ loaded_data.last(*args)
34
+ else
35
+ do_with_scope do
36
+ @clazz.find(:last, *args)
37
+ end
38
+ end
39
+ end
40
+
41
+ def size
42
+ @loaded_data ? @loaded_data.length : count
43
+ end
44
+
45
+ def empty?
46
+ @loaded_data ? @loaded_data.empty? : count.zero?
47
+ end
48
+
49
+ def any?
50
+ if block_given?
51
+ loaded_data.any? { |*block_args| yield(*block_args) }
24
52
  else
25
- loaded_data.send(method, *args, &block)
53
+ !empty?
26
54
  end
27
55
  end
28
56
 
57
+ def filter(&block)
58
+ do_with_scope do
59
+ Filter.new(@clazz, nil, &block)
60
+ end
61
+ end
62
+
63
+ def proxy_options(count_query=false)
64
+ @query.to_find_params(count_query)
65
+ end
66
+
29
67
  protected
30
68
 
31
- def loaded_data
32
- @loaded_data ||= begin
33
- query = Query.new(@clazz, @dsl.conjunction)
34
- @clazz.scoped(query.to_find_params)
69
+ def method_missing(method, *args, &block)
70
+ if @clazz.named_filters.include?(method)
71
+ do_with_scope do
72
+ Filter.new(@clazz, method, *args)
73
+ end
74
+ else
75
+ do_with_scope(method == :count) do
76
+ @clazz.send(method, *args, &block)
77
+ end
35
78
  end
36
79
  end
37
80
 
38
- def loaded_count_data
39
- @loaded_count_data ||= begin
40
- query = Query.new(@clazz, @dsl.conjunction)
41
- @clazz.scoped(query.to_find_params(true))
81
+ def do_with_scope(count_query=false, &block)
82
+ @clazz.send(:with_scope, { :find => proxy_options(count_query), :create => proxy_options(count_query) }, :reverse_merge) do
83
+ if @current_scoped_methods
84
+ @clazz.send(:with_scope, @current_scoped_methods) do
85
+ block.call
86
+ end
87
+ else
88
+ block.call
89
+ end
42
90
  end
43
91
  end
44
92
 
@@ -51,5 +99,11 @@ module RecordFilter
51
99
  end
52
100
  nil
53
101
  end
102
+
103
+ def loaded_data
104
+ @loaded_data ||= do_with_scope do
105
+ @clazz.find(:all)
106
+ end
107
+ end
54
108
  end
55
109
  end
@@ -22,7 +22,7 @@ module RecordFilter
22
22
  end
23
23
 
24
24
  def requires_distinct_select?
25
- [:left, :outer, :left_outer].include?(@join_type)
25
+ [:right, :left].include?(@join_type)
26
26
  end
27
27
 
28
28
  protected
@@ -55,9 +55,8 @@ module RecordFilter
55
55
  def join_type_string
56
56
  @join_type_string ||= case(@join_type)
57
57
  when :inner then 'INNER'
58
- when :left then 'LEFT'
59
- when :left_outer then 'LEFT OUTER'
60
- when :outer then 'OUTER'
58
+ when :left then 'LEFT OUTER'
59
+ when :right then 'RIGHT OUTER'
61
60
  else nil
62
61
  end
63
62
  end
@@ -7,7 +7,9 @@ module RecordFilter
7
7
  end
8
8
 
9
9
  def to_find_params(count_query=false)
10
- params = { :conditions => @conjunction.to_conditions }
10
+ params = {}
11
+ conditions = @conjunction.to_conditions
12
+ params = { :conditions => conditions } if conditions
11
13
  joins = @table.all_joins
12
14
  params[:joins] = joins.map { |join| join.to_sql } * ' ' unless joins.empty?
13
15
  if (joins.any? { |j| j.requires_distinct_select? })
@@ -23,11 +23,18 @@ module RecordFilter
23
23
  if association.nil?
24
24
  raise AssociationNotFoundException.new("The association #{association_name} was not found on #{@model_class.name}.")
25
25
  end
26
- case association.macro
27
- when :belongs_to, :has_many, :has_one
28
- simple_join(association, join_type)
29
- when :has_and_belongs_to_many
30
- compound_join(association, join_type)
26
+
27
+ if (association.options[:through])
28
+ through_association = @model_class.reflect_on_association(association.options[:through])
29
+ through_join = join_association(association.options[:through], join_type)
30
+ through_join.right_table.join_association(association_name, join_type)
31
+ else
32
+ case association.macro
33
+ when :belongs_to, :has_many, :has_one
34
+ simple_join(association, join_type)
35
+ when :has_and_belongs_to_many
36
+ compound_join(association, join_type)
37
+ end
31
38
  end
32
39
  end
33
40
  end
@@ -72,7 +79,7 @@ module RecordFilter
72
79
  end
73
80
 
74
81
  if association.options[:as]
75
- join_predicate << DSL::Restriction.new(association.options[:as].to_s + '_type').equal_to(@model_class.name)
82
+ join_predicate << DSL::Restriction.new(association.options[:as].to_s + '_type').equal_to(association.active_record.base_class.name)
76
83
  end
77
84
  join_table = Table.new(association.klass, alias_for_association(association))
78
85
  @joins << join = Join.new(self, join_table, join_predicate, join_type)
@@ -1,7 +1,10 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper')
2
2
 
3
3
  describe 'raising exceptions' do
4
-
4
+ before do
5
+ TestModel.extended_models.each { |model| model.last_find = {} }
6
+ end
7
+
5
8
  describe 'on missing associations' do
6
9
  it 'should get AssociationNotFoundException' do
7
10
  lambda {
@@ -16,7 +16,7 @@ describe 'explicit joins' do
16
16
  end
17
17
 
18
18
  it 'should add correct join' do
19
- Post.last_find[:joins].should == %q(LEFT JOIN "blogs" AS posts_blogs ON "posts".blog_id = posts_blogs.id)
19
+ Post.last_find[:joins].should == %q(LEFT OUTER JOIN "blogs" AS posts_blogs ON "posts".blog_id = posts_blogs.id)
20
20
  end
21
21
 
22
22
  it 'should query against condition on join table' do
@@ -36,7 +36,7 @@ describe 'explicit joins' do
36
36
  end
37
37
 
38
38
  it 'should add correct join' do
39
- Review.last_find[:joins].should == %q(LEFT JOIN "features" AS reviews_features ON "reviews".reviewable_id = reviews_features.featurable_id AND "reviews".reviewable_type = reviews_features.featurable_type)
39
+ Review.last_find[:joins].should == %q(LEFT OUTER JOIN "features" AS reviews_features ON "reviews".reviewable_id = reviews_features.featurable_id AND "reviews".reviewable_type = reviews_features.featurable_type)
40
40
  end
41
41
 
42
42
  it 'should query against condition on join table' do
@@ -57,7 +57,7 @@ describe 'explicit joins' do
57
57
  end
58
58
 
59
59
  it 'should add correct join' do
60
- Review.last_find[:joins].should == %q(LEFT JOIN "features" AS reviews__Feature ON "reviews".reviewable_id = reviews__Feature.featurable_id AND "reviews".reviewable_type = reviews__Feature.featurable_type AND (reviews__Feature.featurable_type = 'SomeType'))
60
+ Review.last_find[:joins].should == %q(LEFT OUTER JOIN "features" AS reviews__Feature ON "reviews".reviewable_id = reviews__Feature.featurable_id AND "reviews".reviewable_type = reviews__Feature.featurable_type AND (reviews__Feature.featurable_type = 'SomeType'))
61
61
  end
62
62
  end
63
63
 
@@ -73,7 +73,7 @@ describe 'explicit joins' do
73
73
  end
74
74
 
75
75
  it 'should add the correct join' do
76
- Review.last_find[:joins].should == %q(LEFT JOIN "features" AS reviews__Feature ON (reviews__Feature.featurable_type IS NULL) AND (reviews__Feature.featurable_id >= 12) AND (reviews__Feature.priority <> 6))
76
+ Review.last_find[:joins].should == %q(LEFT OUTER JOIN "features" AS reviews__Feature ON (reviews__Feature.featurable_type IS NULL) AND (reviews__Feature.featurable_id >= 12) AND (reviews__Feature.priority <> 6))
77
77
  end
78
78
  end
79
79
 
@@ -100,7 +100,7 @@ describe 'explicit joins' do
100
100
  end
101
101
 
102
102
  it 'should produce the correct join' do
103
- Blog.last_find[:joins].should == %q(INNER JOIN "ads" AS blogs__ads ON "blogs".id = blogs__ads.blog_id LEFT JOIN "posts" AS blogs__Post ON "blogs".id = blogs__Post.blog_id INNER JOIN "comments" AS blogs__Post__Comment ON blogs__Post.id = blogs__Post__Comment.post_id AND (blogs__Post__Comment.offensive = 't'))
103
+ Blog.last_find[:joins].should == %q(INNER JOIN "ads" AS blogs__ads ON "blogs".id = blogs__ads.blog_id LEFT OUTER JOIN "posts" AS blogs__Post ON "blogs".id = blogs__Post.blog_id INNER JOIN "comments" AS blogs__Post__Comment ON blogs__Post.id = blogs__Post__Comment.post_id AND (blogs__Post__Comment.offensive = 't'))
104
104
  end
105
105
  end
106
106
  end
@@ -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(:left_outer, :posts) do
227
+ having(:left, :posts) do
228
228
  with(:permalink, 'ack')
229
229
  end
230
230
  end.inspect
@@ -242,7 +242,7 @@ describe 'implicit joins' do
242
242
  describe 'passing the join type to having with multiple joins' do
243
243
  before do
244
244
  Blog.filter do
245
- having(:left_outer, :posts => :comments) do
245
+ having(:left, :posts => :comments) do
246
246
  with(:offensive, true)
247
247
  end
248
248
  end.inspect
@@ -259,7 +259,7 @@ describe 'implicit joins' do
259
259
 
260
260
  describe 'on polymorphic associations' do
261
261
  before do
262
- Post.filter do
262
+ PublicPost.filter do
263
263
  having(:reviews) do
264
264
  with(:stars_count, 3)
265
265
  end
@@ -267,11 +267,47 @@ describe 'implicit joins' do
267
267
  end
268
268
 
269
269
  it 'should create the correct condition' do
270
- Post.last_find[:conditions].should == [%q(posts__reviews.stars_count = ?), 3]
270
+ PublicPost.last_find[:conditions].should == [%q(posts__reviews.stars_count = ?), 3]
271
+ end
272
+
273
+ it 'should create the correct join' do
274
+ PublicPost.last_find[:joins].should == %q(INNER JOIN "reviews" AS posts__reviews ON "posts".id = posts__reviews.reviewable_id AND (posts__reviews.reviewable_type = 'Post'))
275
+ end
276
+ end
277
+
278
+ describe 'on has_many_through associations' do
279
+ before do
280
+ Blog.filter do
281
+ having(:comments) do
282
+ with(:offensive, true)
283
+ end
284
+ end.inspect
285
+ end
286
+
287
+ it 'should create the correct condition' do
288
+ Blog.last_find[:conditions].should == [%q(blogs__posts__comments.offensive = ?), true]
289
+ end
290
+
291
+ it 'should create the correct join' do
292
+ Blog.last_find[:joins].should == %q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)
293
+ end
294
+ end
295
+
296
+ describe 'on has_one_through associations' do
297
+ before do
298
+ Post.filter do
299
+ having(:user) do
300
+ with(:first_name, 'Joe')
301
+ end
302
+ end.inspect
303
+ end
304
+
305
+ it 'should create the correct condition' do
306
+ Post.last_find[:conditions].should == [%q(posts__author__user.first_name = ?), 'Joe']
271
307
  end
272
308
 
273
309
  it 'should create the correct join' do
274
- Post.last_find[:joins].should == %q(INNER JOIN "reviews" AS posts__reviews ON "posts".id = posts__reviews.reviewable_id AND (posts__reviews.reviewable_type = 'Post'))
310
+ Post.last_find[:joins].should == %q(INNER JOIN "authors" AS posts__author ON "posts".id = posts__author.post_id INNER JOIN "users" AS posts__author__user ON posts__author.user_id = posts__author__user.id)
275
311
  end
276
312
  end
277
313
  end
@@ -21,7 +21,7 @@ describe 'filter qualifiers' do
21
21
 
22
22
  describe 'with multiple calls to limit' do
23
23
  before do
24
- Post.filter do
24
+ Post.filter do
25
25
  limit 5
26
26
  with :published, true
27
27
  limit 6
data/spec/models.rb ADDED
@@ -0,0 +1,70 @@
1
+ class Ad < ActiveRecord::Base
2
+ extend TestModel
3
+ belongs_to :blog
4
+ end
5
+
6
+
7
+ class Author < ActiveRecord::Base
8
+ extend TestModel
9
+ belongs_to :user
10
+ belongs_to :post
11
+ end
12
+
13
+
14
+ class Blog < ActiveRecord::Base
15
+ extend TestModel
16
+ has_many :posts
17
+ has_many :comments, :through => :posts
18
+ has_many :ads
19
+ end
20
+
21
+
22
+ class Comment < ActiveRecord::Base
23
+ extend TestModel
24
+ belongs_to :post
25
+ belongs_to :user
26
+ end
27
+
28
+
29
+ class Feature < ActiveRecord::Base
30
+ extend TestModel
31
+ belongs_to :featurable, :polymorphic => true
32
+ end
33
+
34
+
35
+ class Photo < ActiveRecord::Base
36
+ belongs_to :post
37
+ end
38
+
39
+
40
+ class Post < ActiveRecord::Base
41
+ extend TestModel
42
+ belongs_to :blog
43
+ has_many :comments
44
+ has_one :photo
45
+ has_and_belongs_to_many :tags
46
+ has_many :features, :as => :featurable
47
+ has_many :reviews, :as => :reviewable
48
+ has_one :author
49
+ has_one :user, :through => :author
50
+ end
51
+
52
+ class PublicPost < Post
53
+ end
54
+
55
+ class Review < ActiveRecord::Base
56
+ extend TestModel
57
+ belongs_to :reviewable, :polymorphic => true
58
+ end
59
+
60
+
61
+ class Tag < ActiveRecord::Base
62
+ has_and_belongs_to_many :posts
63
+ end
64
+
65
+
66
+ class User < ActiveRecord::Base
67
+ extend TestModel
68
+ has_one :author
69
+ has_many :comments
70
+ end
@@ -50,7 +50,8 @@ describe 'named filters' do
50
50
 
51
51
  it 'should call the filter passing all of the arguments' do
52
52
  Blog.with_name_and_post_with_permalink('booya', 'ftw').inspect
53
- Blog.last_find[:conditions].should == [%q(("blogs".name = ?) AND (blogs__posts.permalink = ?)), 'booya', 'ftw']
53
+ Blog.last_find[:conditions].should ==
54
+ [%q(("blogs".name = ?) AND (blogs__posts.permalink = ?)), 'booya', 'ftw']
54
55
  end
55
56
  end
56
57
 
@@ -81,7 +82,8 @@ describe 'named filters' do
81
82
 
82
83
  it 'should execute the parent class filters correctly' do
83
84
  NiceComment.with_contents('test contents').inspect
84
- NiceComment.last_find[:conditions].should == [%q("comments".contents = ?), 'test contents']
85
+ NiceComment.last_find[:conditions].should ==
86
+ [%q("comments".contents = ?), 'test contents']
85
87
  end
86
88
 
87
89
  it 'should not have the subclass filters in the parent class' do
@@ -90,7 +92,8 @@ describe 'named filters' do
90
92
 
91
93
  it 'should have parent class filters in the subclass' do
92
94
  NiceComment.offensive.with_contents('something').inspect
93
- NiceComment.last_find[:conditions].should == [%q(("comments".offensive = ?) AND ("comments".contents = ?)), true, 'something']
95
+ NiceComment.last_find[:conditions].should ==
96
+ %q(("comments".contents = 'something') AND ("comments".offensive = 't'))
94
97
  end
95
98
 
96
99
  it 'should provide access to the named filters' do
@@ -126,25 +129,25 @@ describe 'named filters' do
126
129
 
127
130
  it 'should chain the filters into a single query' do
128
131
  Post.for_blog(1).with_offensive_comments.inspect
129
- Post.last_find[:conditions].should == [%q((posts__blog.id = ?) AND (posts__comments.offensive = ?)), 1, true]
130
- Post.last_find[:joins].should == %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)
132
+ Post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1))
133
+ Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
131
134
  end
132
135
 
133
136
  it 'should remove duplicate joins' do
134
137
  Post.for_blog(1).with_offensive_comments.with_interesting_comments.inspect
135
- Post.last_find[:joins].should == %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)
138
+ Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
136
139
  end
137
140
 
138
141
  it 'should allow for filtering a named_filter' do
139
142
  Post.for_blog(1).filter { having(:comments).with :offensive, true }.inspect
140
- Post.last_find[:conditions].should == [%q((posts__blog.id = ?) AND (posts__comments.offensive = ?)), 1, true]
141
- Post.last_find[:joins].should == %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)
143
+ Post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1))
144
+ Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
142
145
  end
143
146
 
144
147
  it 'should allow for applying a named filter to a filter' do
145
148
  Post.filter { having(:comments).with :offensive, false }.for_blog(1).inspect
146
- Post.last_find[:conditions].should == [%q((posts__comments.offensive = ?) AND (posts__blog.id = ?)), false, 1]
147
- Post.last_find[:joins].should == %q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)
149
+ Post.last_find[:conditions].should == %q((posts__blog.id = 1) AND (posts__comments.offensive = 'f'))
150
+ Post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id), %q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)]
148
151
  end
149
152
 
150
153
  it 'should not change the inner filter conditions when chaining filters' do
@@ -163,11 +166,74 @@ describe 'named filters' do
163
166
 
164
167
  it 'should not change an original filter when reusing it' do
165
168
  base = Post.for_blog(1)
166
- level1 = base.with_offensive_comments
169
+ level1 = base.with_offensive_comments.inspect
167
170
  level2 = base.with_interesting_comments
168
- level1.inspect
169
- Post.last_find[:conditions].should == [%q((posts__blog.id = ?) AND (posts__comments.offensive = ?)), 1, true]
170
- Post.last_find[:joins].should == %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)
171
+ Post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1))
172
+ Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
173
+ end
174
+ end
175
+
176
+ describe 'chaining named filters with regular AR associations' do
177
+ before do
178
+ Post.named_filter(:published) do
179
+ with(:published, true)
180
+ end
181
+ @blog = Blog.create
182
+ @blog.posts.published.inspect
183
+ end
184
+
185
+ it 'should combine the conditions from the association with the named filter' do
186
+ Post.last_find[:conditions].should == "(\"posts\".published = 't') AND (\"posts\".blog_id = #{@blog.id})"
187
+ end
188
+ end
189
+
190
+ describe 'chaining named filters with AR associations that involve joins' do
191
+ before do
192
+ Comment.named_filter(:with_user_named) do |name|
193
+ having(:user).with(:first_name, name)
194
+ end
195
+ @blog = Blog.create
196
+ @blog.comments.with_user_named('Bob').inspect
197
+ end
198
+
199
+ it 'should combine the joins from the association with the named filter' do
200
+ Comment.last_find[:joins].should == [%q(INNER JOIN "users" AS comments__user ON "comments".user_id = comments__user.id), %q(INNER JOIN "posts" ON "comments".post_id = "posts".id)]
201
+ end
202
+
203
+ it 'should combine the conditions from the association with the named filter' do
204
+ Comment.last_find[:conditions].should == "(comments__user.first_name = 'Bob') AND ((\"posts\".blog_id = #{@blog.id}))"
205
+ end
206
+ end
207
+
208
+ describe 'chaining multiple named filters with an AR association' do
209
+ before do
210
+ Comment.named_filter(:offensive) { with(:offensive, true) }
211
+ Comment.named_filter(:with_fun_in_contents) { with(:contents).like('%fun%') }
212
+ @post = Post.create
213
+ @post.comments.offensive.with_fun_in_contents.inspect
214
+ end
215
+
216
+ it 'should combine the conditions correctly' do
217
+ Comment.last_find[:conditions].should == "(\"comments\".contents LIKE '%fun%') AND ((\"comments\".offensive = 't') AND (\"comments\".post_id = #{@post.id}))"
218
+ end
219
+ end
220
+
221
+ describe 'chaining multiple named filters with different joins' do
222
+ before do
223
+ Blog.named_filter(:with_offensive_comments) { having(:comments).with(:offensive, true) }
224
+ Blog.named_filter(:with_ads_with_content) { |content| having(:ads).with(:content, content) }
225
+ end
226
+
227
+ it 'compile the joins correctly' do
228
+ Blog.with_offensive_comments.with_ads_with_content('ack').inspect
229
+ Blog.last_find[:joins].should == [%q(INNER JOIN "ads" AS blogs__ads ON "blogs".id = blogs__ads.blog_id), %q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)]
230
+ end
231
+ end
232
+
233
+ describe 'with named filters that only include orders' do
234
+ it 'should have an empty conditions hash' do
235
+ Blog.named_filter(:ordered_by_id) { order(:id, :desc) }
236
+ Blog.ordered_by_id.proxy_options.should == { :order => %q("blogs".id DESC) }
171
237
  end
172
238
  end
173
239
  end
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'proxying to the found data' do
4
+ before do
5
+ TestModel.extended_models.each { |model| model.last_find = {} }
6
+ end
7
+
8
+ describe 'calling first and last on a filter result' do
9
+ before do
10
+ Blog.all.each { |b| b.destroy }
11
+ Blog.named_filter(:by_name) do
12
+ order(:name)
13
+ end
14
+ @blog1 = Blog.create(:name => 'a')
15
+ @blog2 = Blog.create(:name => 'b')
16
+ @blog3 = Blog.create(:name => 'c')
17
+ end
18
+
19
+ it 'should return the first element in the result correctly' do
20
+ Blog.by_name.first.should == @blog1
21
+ end
22
+
23
+ it 'should return the last element in the result correctly' do
24
+ Blog.by_name.last.should == @blog3
25
+ end
26
+
27
+ it 'should proxy arguments through' do
28
+ Blog.by_name.first(:conditions => ['name = ?', 'b']).should == @blog2
29
+ end
30
+ end
31
+
32
+ describe 'calling reject! on a filter result' do
33
+ before do
34
+ Blog.all.each { |blog| blog.destroy }
35
+ Blog.named_filter(:by_name) do
36
+ order(:name)
37
+ end
38
+ @blog1 = Blog.create
39
+ @blog2 = Blog.create
40
+ end
41
+
42
+ it 'should remove the rejected items from the list' do
43
+ items = Blog.by_name
44
+ items.size.should == 2
45
+ items.reject! { |i| true } # should reject them all
46
+ items.size.should == 0
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,58 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'with custom selects for cases where DISTINCT is required' do
4
+ before do
5
+ TestModel.extended_models.each { |model| model.last_find = {} }
6
+ end
7
+
8
+ describe 'on a standard filter' do
9
+ it 'should put nothing in the select' do
10
+ Post.filter do
11
+ having(:comments).with(:offensive, true)
12
+ end.inspect
13
+ Post.last_find[:select].should be_nil
14
+ end
15
+ end
16
+
17
+ describe 'with join types that require distinct' do
18
+ it 'should put the distinct clause in the select' do
19
+ [:left, :right].each do |join_type|
20
+ Post.filter do
21
+ having(join_type, :comments).with(:offensive, true)
22
+ end.inspect rescue nil # required because sqlite doesn't support right joins
23
+ Post.last_find[:select].should == %q(DISTINCT "posts".*)
24
+ end
25
+ end
26
+ end
27
+
28
+ describe 'with join types that do not require distinct' do
29
+ it 'should not put the distinct clause in the select' do
30
+ [:inner].each do |join_type|
31
+ Post.filter do
32
+ having(join_type, :comments).with(:offensive, true)
33
+ end.inspect
34
+ Post.last_find[:select].should be_nil
35
+ end
36
+ end
37
+ end
38
+
39
+ describe 'on a filter with nested joins that require distinct' do
40
+ it 'should put the distinct clause in the select' do
41
+ Blog.filter do
42
+ having(:posts) do
43
+ having(:left, :comments).with(:offensive, true)
44
+ end
45
+ end.inspect
46
+ Blog.last_find[:select].should == %q(DISTINCT "blogs".*)
47
+ end
48
+ end
49
+
50
+ describe 'on a filter that requires distinct with a count call' do
51
+ it 'should put the distinct clause in the select' do
52
+ Post.filter do
53
+ having(:left, :comments).with(:offensive, true)
54
+ end.count
55
+ Post.last_find[:select].should == %q(DISTINCT "posts".id)
56
+ end
57
+ end
58
+ end
data/spec/spec_helper.rb CHANGED
@@ -13,8 +13,13 @@ module TestModel
13
13
 
14
14
  attr_accessor :last_find
15
15
 
16
- def scoped(params = {})
17
- @last_find = params
16
+ def find(*args)
17
+ @last_find = current_scoped_methods[:find] if current_scoped_methods
18
+ super
19
+ end
20
+
21
+ def count(*args)
22
+ @last_find = current_scoped_methods[:find] if current_scoped_methods
18
23
  super
19
24
  end
20
25
 
@@ -23,7 +28,7 @@ module TestModel
23
28
  end
24
29
  end
25
30
 
26
- Dir.glob(File.join(File.dirname(__FILE__), 'models', '*.rb')).each { |file| require file }
31
+ require File.join(File.dirname(__FILE__), 'models')
27
32
 
28
33
  ActiveRecord::Base.establish_connection(
29
34
  :adapter => 'sqlite3',
data/spec/test.db CHANGED
Binary file
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
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mat Brown
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-04-22 00:00:00 -07:00
13
+ date: 2009-04-28 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -52,15 +52,11 @@ files:
52
52
  - spec/explicit_join_spec.rb
53
53
  - spec/implicit_join_spec.rb
54
54
  - spec/limits_and_ordering_spec.rb
55
- - spec/models/blog.rb
56
- - spec/models/comment.rb
57
- - spec/models/feature.rb
58
- - spec/models/photo.rb
59
- - spec/models/post.rb
60
- - spec/models/review.rb
61
- - spec/models/tag.rb
55
+ - spec/models.rb
62
56
  - spec/named_filter_spec.rb
57
+ - spec/proxying_spec.rb
63
58
  - spec/restrictions_spec.rb
59
+ - spec/select_spec.rb
64
60
  - spec/spec_helper.rb
65
61
  - spec/test.db
66
62
  has_rdoc: true
@@ -94,13 +90,9 @@ test_files:
94
90
  - spec/explicit_join_spec.rb
95
91
  - spec/implicit_join_spec.rb
96
92
  - spec/limits_and_ordering_spec.rb
97
- - spec/models/blog.rb
98
- - spec/models/comment.rb
99
- - spec/models/feature.rb
100
- - spec/models/photo.rb
101
- - spec/models/post.rb
102
- - spec/models/review.rb
103
- - spec/models/tag.rb
93
+ - spec/models.rb
104
94
  - spec/named_filter_spec.rb
95
+ - spec/proxying_spec.rb
105
96
  - spec/restrictions_spec.rb
97
+ - spec/select_spec.rb
106
98
  - spec/spec_helper.rb
data/spec/models/blog.rb DELETED
@@ -1,6 +0,0 @@
1
- class Blog < ActiveRecord::Base
2
- extend TestModel
3
-
4
- has_many :posts
5
- has_many :ads
6
- end
@@ -1,5 +0,0 @@
1
- class Comment < ActiveRecord::Base
2
- extend TestModel
3
-
4
- belongs_to :post
5
- end
@@ -1,5 +0,0 @@
1
- class Feature < ActiveRecord::Base
2
- extend TestModel
3
-
4
- belongs_to :featurable, :polymorphic => true
5
- end
data/spec/models/photo.rb DELETED
@@ -1,3 +0,0 @@
1
- class Photo < ActiveRecord::Base
2
- belongs_to :post
3
- end
data/spec/models/post.rb DELETED
@@ -1,10 +0,0 @@
1
- class Post < ActiveRecord::Base
2
- extend TestModel
3
-
4
- belongs_to :blog
5
- has_many :comments
6
- has_one :photo
7
- has_and_belongs_to_many :tags
8
- has_many :features, :as => :featurable
9
- has_many :reviews, :as => :reviewable
10
- end
@@ -1,5 +0,0 @@
1
- class Review < ActiveRecord::Base
2
- extend TestModel
3
-
4
- belongs_to :reviewable, :polymorphic => true
5
- end
data/spec/models/tag.rb DELETED
@@ -1,3 +0,0 @@
1
- class Tag < ActiveRecord::Base
2
- has_and_belongs_to_many :posts
3
- end