aub-record_filter 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -18,7 +18,7 @@ module RecordFilter
18
18
  when DSL::Conjunction
19
19
  result.add_conjunction(create_from(step, table))
20
20
  when DSL::Join
21
- join = result.add_join_on_association(step.association)
21
+ join = result.add_join_on_association(step.association, step.join_type)
22
22
  result.add_conjunction(create_from(step.conjunction, join.right_table))
23
23
  when DSL::ClassJoin
24
24
  join = result.add_join_on_class(
@@ -55,14 +55,14 @@ module RecordFilter
55
55
  conjunction
56
56
  end
57
57
 
58
- def add_join_on_association(association_name)
58
+ def add_join_on_association(association_name, join_type)
59
59
  table = @table
60
60
  while association_name.is_a?(Hash)
61
- result = table.join_association(association_name.keys[0])
61
+ result = table.join_association(association_name.keys[0], join_type)
62
62
  table = result.right_table
63
63
  association_name = association_name.values[0]
64
64
  end
65
- table.join_association(association_name)
65
+ table.join_association(association_name, join_type)
66
66
  end
67
67
 
68
68
  def add_join_on_class(join_class, join_type, table_alias, conditions)
@@ -94,16 +94,20 @@ module RecordFilter
94
94
  else
95
95
  @restrictions.map do |restriction|
96
96
  conditions = restriction.to_conditions
97
- conditions[0] = "(#{conditions[0]})"
98
- conditions
99
- end.inject do |conditions, new_conditions|
97
+ if conditions
98
+ conditions[0] = "(#{conditions[0]})"
99
+ conditions
100
+ else
101
+ nil
102
+ end
103
+ end.compact.inject do |conditions, new_conditions|
100
104
  conditions.first << " #{conjunctor} #{new_conditions.shift}"
101
105
  conditions.concat(new_conditions)
102
106
  conditions
103
107
  end
104
108
  end
105
109
  end
106
- result[0] = "!(#{result[0]})" if (negated && !result.nil? && !result[0].nil?)
110
+ result[0] = "NOT (#{result[0]})" if (negated && !result.nil? && !result[0].nil?)
107
111
  result
108
112
  end
109
113
 
@@ -19,10 +19,10 @@ module RecordFilter
19
19
  @steps << dsl.conjunction
20
20
  end
21
21
 
22
- def add_join(association, &block)
22
+ def add_join(association, join_type, &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, dsl.conjunction)
25
+ @steps << Join.new(association, join_type, dsl.conjunction)
26
26
  dsl
27
27
  end
28
28
 
@@ -37,8 +37,13 @@ module RecordFilter
37
37
  end
38
38
 
39
39
  # join
40
- def having(association, &block)
41
- @conjunction.add_join(association, &block)
40
+ def having(join_type_or_association, association=nil, &block)
41
+ if association.nil?
42
+ association, join_type = join_type_or_association, nil
43
+ else
44
+ join_type = join_type_or_association
45
+ end
46
+ @conjunction.add_join(association, join_type, &block)
42
47
  end
43
48
 
44
49
  def join(clazz, join_type, table_alias=nil, &block)
@@ -2,10 +2,10 @@ module RecordFilter
2
2
  module DSL
3
3
  class Join
4
4
 
5
- attr_reader :association, :conjunction
5
+ attr_reader :association, :join_type, :conjunction
6
6
 
7
- def initialize(association, conjunction)
8
- @association, @conjunction = association, conjunction
7
+ def initialize(association, join_type, conjunction)
8
+ @association, @join_type, @conjunction = association, join_type, conjunction
9
9
  end
10
10
  end
11
11
  end
@@ -1,35 +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)
19
+ end
20
+
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
29
+ end
30
+
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) }
52
+ else
53
+ !empty?
54
+ end
13
55
  end
14
56
 
15
57
  def filter(&block)
16
- Filter.new(@clazz, nil, @dsl.conjunction, &block)
58
+ do_with_scope do
59
+ Filter.new(@clazz, nil, &block)
60
+ end
17
61
  end
18
62
 
63
+ def proxy_options(count_query=false)
64
+ @query.to_find_params(count_query)
65
+ end
66
+
67
+ protected
68
+
19
69
  def method_missing(method, *args, &block)
20
70
  if @clazz.named_filters.include?(method)
21
- Filter.new(@clazz, method, @dsl.conjunction, *args)
71
+ do_with_scope do
72
+ Filter.new(@clazz, method, *args)
73
+ end
22
74
  else
23
- loaded_data.send(method, *args, &block)
75
+ do_with_scope(method == :count) do
76
+ @clazz.send(method, *args, &block)
77
+ end
24
78
  end
25
79
  end
26
80
 
27
- protected
28
-
29
- def loaded_data
30
- @loaded_data ||= begin
31
- query = Query.new(@clazz, @dsl.conjunction)
32
- @clazz.scoped(query.to_find_params)
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
33
90
  end
34
91
  end
35
92
 
@@ -42,5 +99,11 @@ module RecordFilter
42
99
  end
43
100
  nil
44
101
  end
102
+
103
+ def loaded_data
104
+ @loaded_data ||= do_with_scope do
105
+ @clazz.find(:all)
106
+ end
107
+ end
45
108
  end
46
109
  end
@@ -17,7 +17,7 @@ module RecordFilter
17
17
  raise ColumnNotFoundException.new("The column #{column} was not found in #{table.table_name}.")
18
18
  end
19
19
 
20
- "#{table.table_name}.#{column}"
20
+ "#{table.table_alias}.#{column}"
21
21
  end
22
22
  end
23
23
  end
@@ -2,9 +2,10 @@ module RecordFilter
2
2
  class Join
3
3
  attr_reader :left_table, :right_table
4
4
 
5
- def initialize(left_table, right_table, join_conditions, join_type=:inner)
5
+ def initialize(left_table, right_table, join_conditions, join_type=nil)
6
6
  @left_table, @right_table, @join_conditions, @join_type =
7
7
  left_table, right_table, join_conditions, join_type
8
+ @join_type ||= :inner
8
9
  end
9
10
 
10
11
  def to_sql
@@ -20,6 +21,10 @@ module RecordFilter
20
21
  "#{join_type_string} JOIN #{@right_table.table_name} AS #{@right_table.table_alias} ON #{predicate_sql}"
21
22
  end
22
23
 
24
+ def requires_distinct_select?
25
+ [:left, :outer, :left_outer].include?(@join_type)
26
+ end
27
+
23
28
  protected
24
29
 
25
30
  def condition_to_predicate_part(condition)
@@ -51,6 +56,8 @@ module RecordFilter
51
56
  @join_type_string ||= case(@join_type)
52
57
  when :inner then 'INNER'
53
58
  when :left then 'LEFT'
59
+ when :left_outer then 'LEFT OUTER'
60
+ when :outer then 'OUTER'
54
61
  else nil
55
62
  end
56
63
  end
@@ -22,7 +22,7 @@ module RecordFilter
22
22
  raise ColumnNotFoundException.new("The column #{column} was not found in #{table.table_name}.")
23
23
  end
24
24
 
25
- "#{table.table_name}.#{column} #{dir}"
25
+ "#{table.table_alias}.#{column} #{dir}"
26
26
  end
27
27
  end
28
28
  end
@@ -6,10 +6,19 @@ module RecordFilter
6
6
  @conjunction = RecordFilter::Conjunctions::Base.create_from(dsl_conjunction, @table)
7
7
  end
8
8
 
9
- def to_find_params
10
- params = { :conditions => @conjunction.to_conditions }
9
+ def to_find_params(count_query=false)
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?
15
+ if (joins.any? { |j| j.requires_distinct_select? })
16
+ if count_query
17
+ params[:select] = "DISTINCT #{@table.model_class.quoted_table_name}.#{@table.model_class.primary_key}"
18
+ else
19
+ params[:select] = "DISTINCT #{@table.model_class.quoted_table_name}.*"
20
+ end
21
+ end
13
22
  orders = @table.orders
14
23
  params[:order] = orders.map { |order| order.to_sql } * ', ' unless orders.empty?
15
24
  group_bys = @table.group_bys
@@ -15,7 +15,7 @@ module RecordFilter
15
15
  end
16
16
 
17
17
  def to_negative_sql
18
- "!(#{to_positive_sql})"
18
+ "NOT (#{to_positive_sql})"
19
19
  end
20
20
 
21
21
  def self.class_from_operator(operator)
@@ -29,7 +29,7 @@ module RecordFilter
29
29
  end
30
30
 
31
31
  def to_negative_sql
32
- "#{@column_name} != ?"
32
+ "#{@column_name} <> ?"
33
33
  end
34
34
  end
35
35
 
@@ -16,18 +16,25 @@ module RecordFilter
16
16
  @model_class.quoted_table_name
17
17
  end
18
18
 
19
- def join_association(association_name)
19
+ def join_association(association_name, join_type=nil)
20
20
  @joins_cache[association_name] ||=
21
21
  begin
22
22
  association = @model_class.reflect_on_association(association_name)
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)
29
- when :has_and_belongs_to_many
30
- compound_join(association)
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
@@ -62,7 +69,7 @@ module RecordFilter
62
69
 
63
70
  private
64
71
 
65
- def simple_join(association)
72
+ def simple_join(association, join_type)
66
73
  join_predicate =
67
74
  case association.macro
68
75
  when :belongs_to
@@ -70,19 +77,23 @@ module RecordFilter
70
77
  when :has_many, :has_one
71
78
  [{ :id => association.primary_key_name.to_sym }]
72
79
  end
80
+
81
+ if association.options[:as]
82
+ join_predicate << DSL::Restriction.new(association.options[:as].to_s + '_type').equal_to(association.active_record.base_class.name)
83
+ end
73
84
  join_table = Table.new(association.klass, alias_for_association(association))
74
- @joins << join = Join.new(self, join_table, join_predicate)
85
+ @joins << join = Join.new(self, join_table, join_predicate, join_type)
75
86
  join
76
87
  end
77
88
 
78
- def compound_join(association)
89
+ def compound_join(association, join_type)
79
90
  pivot_join_predicate = [{ :id => association.primary_key_name.to_sym }]
80
91
  table_name = @model_class.connection.quote_table_name(association.options[:join_table])
81
92
  pivot_table = PivotTable.new(table_name, association, "__#{alias_for_association(association)}")
82
- pivot_join = Join.new(self, pivot_table, pivot_join_predicate)
93
+ pivot_join = Join.new(self, pivot_table, pivot_join_predicate, join_type)
83
94
  join_predicate = [{ association.association_foreign_key.to_sym => :id }]
84
95
  join_table = Table.new(association.klass, alias_for_association(association))
85
- pivot_table.joins << join = Join.new(pivot_table, join_table, join_predicate)
96
+ pivot_table.joins << join = Join.new(pivot_table, join_table, join_predicate, join_type)
86
97
  @joins << pivot_join
87
98
  join
88
99
  end
@@ -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 {
@@ -73,7 +73,34 @@ 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 JOIN "features" AS reviews__Feature ON (reviews__Feature.featurable_type IS NULL) AND (reviews__Feature.featurable_id >= 12) AND (reviews__Feature.priority <> 6))
77
+ end
78
+ end
79
+
80
+ describe 'using implicit and explicit joins together with conditions' do
81
+ before do
82
+ Blog.named_filter :somethings do
83
+ having(:ads) do
84
+ with(:content, nil)
85
+ end
86
+ join(Post, :left) do
87
+ on(:id => :blog_id)
88
+ join(Comment, :inner) do
89
+ on(:id => :post_id)
90
+ on(:offensive, true)
91
+ end
92
+ end
93
+ group_by(:id)
94
+ end
95
+ Blog.somethings.inspect
96
+ end
97
+
98
+ it 'should produce the correct conditions' do
99
+ Blog.last_find[:conditions].should == [%q((blogs__ads.content IS NULL))]
100
+ end
101
+
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'))
77
104
  end
78
105
  end
79
106
  end
@@ -191,7 +191,7 @@ describe 'implicit joins' do
191
191
  end
192
192
 
193
193
  it 'should create the correct condition' do
194
- Comment.last_find[:conditions].should == [%q("comments".offensive != ?), false]
194
+ Comment.last_find[:conditions].should == [%q("comments".offensive <> ?), false]
195
195
  end
196
196
  end
197
197
 
@@ -220,4 +220,94 @@ describe 'implicit joins' do
220
220
  Comment.last_find[:conditions].should == [%q(("comments".contents IS NOT NULL) AND ("comments".offensive = ?)), true]
221
221
  end
222
222
  end
223
+
224
+ describe 'passing the join type to having' do
225
+ before do
226
+ Blog.filter do
227
+ having(:left_outer, :posts) do
228
+ with(:permalink, 'ack')
229
+ end
230
+ end.inspect
231
+ end
232
+
233
+ it 'should create the correct condition' do
234
+ Blog.last_find[:conditions].should == [%q(blogs__posts.permalink = ?), 'ack']
235
+ end
236
+
237
+ it 'should create the correct join' do
238
+ Blog.last_find[:joins].should == %q(LEFT OUTER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id)
239
+ end
240
+ end
241
+
242
+ describe 'passing the join type to having with multiple joins' do
243
+ before do
244
+ Blog.filter do
245
+ having(:left_outer, :posts => :comments) do
246
+ with(:offensive, true)
247
+ end
248
+ end.inspect
249
+ end
250
+
251
+ it 'should create the correct condition' do
252
+ Blog.last_find[:conditions].should == [%q(blogs__posts__comments.offensive = ?), true]
253
+ end
254
+
255
+ it 'should create the correct join' do
256
+ Blog.last_find[:joins].should == %q(LEFT OUTER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id LEFT OUTER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)
257
+ end
258
+ end
259
+
260
+ describe 'on polymorphic associations' do
261
+ before do
262
+ PublicPost.filter do
263
+ having(:reviews) do
264
+ with(:stars_count, 3)
265
+ end
266
+ end.inspect
267
+ end
268
+
269
+ it 'should create the correct condition' do
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']
307
+ end
308
+
309
+ it 'should create the correct join' do
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)
311
+ end
312
+ end
223
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
@@ -115,7 +115,7 @@ describe 'filter qualifiers' do
115
115
  end
116
116
 
117
117
  it 'should add the limit to the parameters' do
118
- Post.last_find[:order].should == %q("photos".path DESC, "posts".permalink ASC)
118
+ Post.last_find[:order].should == %q(posts__photo.path DESC, "posts".permalink ASC)
119
119
  end
120
120
  end
121
121
 
@@ -184,7 +184,7 @@ describe 'filter qualifiers' do
184
184
  group_by(:created_at)
185
185
  group_by(:photo => :format)
186
186
  end.inspect
187
- Post.last_find[:group].should == %q("posts".created_at, "photos".format)
187
+ Post.last_find[:group].should == %q("posts".created_at, posts__photo.format)
188
188
  end
189
189
  end
190
190
  end
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
@@ -72,7 +72,7 @@ describe 'RecordFilter restrictions' do
72
72
  with(:permalink, 'eek')
73
73
  end
74
74
  end.inspect
75
- Post.last_find.should == { :conditions => [%q{!(("posts".blog_id = ?) OR ("posts".permalink = ?))}, 1, 'eek'] }
75
+ Post.last_find.should == { :conditions => [%q{NOT (("posts".blog_id = ?) OR ("posts".permalink = ?))}, 1, 'eek'] }
76
76
  end
77
77
 
78
78
  it 'should filter by not_all_of' do
@@ -82,7 +82,7 @@ describe 'RecordFilter restrictions' do
82
82
  with(:permalink, 'eek')
83
83
  end
84
84
  end.inspect
85
- Post.last_find.should == { :conditions => [%q{!(("posts".blog_id = ?) AND ("posts".permalink = ?))}, 1, 'eek'] }
85
+ Post.last_find.should == { :conditions => [%q{NOT (("posts".blog_id = ?) AND ("posts".permalink = ?))}, 1, 'eek'] }
86
86
  end
87
87
 
88
88
  it 'should filter by disjunction' do
@@ -137,6 +137,6 @@ describe 'RecordFilter restrictions' do
137
137
  Post.filter do
138
138
  with(:permalink).not.equal_to(filter_class.name)
139
139
  end.inspect
140
- Post.last_find.should == { :conditions => [%q("posts".permalink != ?), 'Post'] }
140
+ Post.last_find.should == { :conditions => [%q("posts".permalink <> ?), 'Post'] }
141
141
  end
142
142
  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, :outer, :left_outer].each do |join_type|
20
+ Post.filter do
21
+ having(join_type, :comments).with(:offensive, true)
22
+ end.inspect
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_outer, :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_outer, :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,14 @@ 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
23
+ super
18
24
  end
19
25
 
20
26
  def self.extended(base)
@@ -22,7 +28,7 @@ module TestModel
22
28
  end
23
29
  end
24
30
 
25
- Dir.glob(File.join(File.dirname(__FILE__), 'models', '*.rb')).each { |file| require file }
31
+ require File.join(File.dirname(__FILE__), 'models')
26
32
 
27
33
  ActiveRecord::Base.establish_connection(
28
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: aub-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-27 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,5 +0,0 @@
1
- class Blog < ActiveRecord::Base
2
- extend TestModel
3
-
4
- has_many :posts
5
- 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