aub-record_filter 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -14,176 +14,242 @@ record_filter has the following top-level features:
14
14
 
15
15
  gem install outoftime-record_filter --source=http://gems.github.com
16
16
 
17
- == Usage
17
+ == Using Filters
18
18
 
19
- === Ad-hoc filters
19
+ Given a Blog model having a has_many relationship with a Post model, a simple
20
+ filter with conditions and joins might look like this.
20
21
 
21
- Post.filter do
22
- with(:permalink, 'blog-post')
23
- having(:blog).with(:name, 'Blog')
22
+ Blog.filter do
23
+ with(:created_at).greater_than(1.day.ago)
24
+ having(:posts).with(:permalink, nil)
24
25
  end
25
26
 
26
27
  This could be expressed in ActiveRecord as:
27
28
 
28
- Post.find(:all, :joins => :blog, :conditions => ['posts.permalink = ? AND blogs.name = ?', 'blog-post', 'Blog')
29
-
30
- and it returns the same result, a list of Post objects that are returned from the query.
29
+ Blog.find(
30
+ :all,
31
+ :joins => :posts,
32
+ :conditions => ['posts.permalink IS NULL AND blogs.created_at > ?', 'blog-post', 1.day.ago)
31
33
 
32
- === Named filters
34
+ and it returns the same result, a list of Blog objects that are returned from the query. This
35
+ type of filter is designed to be created on the fly, but if you have a filter that you would like
36
+ to use in more than one place, it can be added to a class as a named filter. The following example
37
+ creates the same filter as above and executes it:
33
38
 
34
39
  class Post < ActiveRecord::Base
35
- named_filter(:with_title) do |title|
36
- with(:title, title)
40
+ named_filter(:new_with_nil_permalink) do
41
+ with(:created_at).greater_than(1.day.ago)
42
+ having(:posts).with(:permalink, nil)
37
43
  end
38
44
  end
39
45
 
40
- Post.with_title('posted')
46
+ Post.new_with_nil_permalink
41
47
 
42
- This is the same as the following code using named scopes, and returns the same result:
48
+ This returns the same result as the example above but with the advantages that it is
49
+ easily reusable and that it can be combined with other named filters to produce a more
50
+ complex query:
43
51
 
44
52
  class Post < ActiveRecord::Base
45
- named_scope :with_title, lambda { |title| { :conditions => ['title = ?', title] }}
53
+ named_filter(:title_is_monkeys) { with(:title, 'monkeys') }
54
+ named_filter(:permalink_is_donkeys) { with(:title, 'donkeys') }
46
55
  end
47
-
48
- Post.with_title('scoped')
49
-
50
- === Restrictions
51
-
52
- Restrictions are specified through the API using the 'with' function. The first argument to 'with' should be the
53
- name of the field that the restriction applies to. All restriction types can be negated by chaining the 'with'
54
- method with a call to 'not', as seen in some examples below.
55
56
 
56
- ==== Equality
57
+ Post.title_is_monkeys.permalink_is_donkeys
57
58
 
58
- If a second argument is supplied, it is assumed that you are
59
- expressing an equality condition and that argument is used as the value.
59
+ This example will return all of the posts that meet both animal-related conditions.
60
+ There is no limit to the number of filters that can be combined, and because record_filter works
61
+ seamlessly with named scopes, they can also be combined in this way as well.
60
62
 
61
- with(:title, 'abc') # :conditions => ['title = ?', 'abc']
63
+ Named filters can also be customized by taking any number of arguments. The example above can
64
+ be replicated with the following filter:
62
65
 
63
- Which can be negated with:
64
-
65
- with(:title, 'abc').not # :conditions => ['title <> ?', 'abc']
66
+ class Post < ActiveRecord::Base
67
+ named_filter(:with_title_and_permalink) do |title, permalink|
68
+ with(:title, title)
69
+ with(:permalink, permalink)
70
+ end
71
+ end
66
72
 
67
- For the more verbose among us, this can also be specified as:
73
+ Post.with_title_and_permalink('monkeys', 'donkeys')
68
74
 
69
- with(:title).equal_to('abc') # :conditions => ['title = ?', 'abc']
75
+ Named filters can also be called from other named filters and will be invoked on the correct
76
+ model even if called from a join.
70
77
 
71
- Other types of restrictions are specified by omitting the second argument to 'with' and chaining it with one of the
72
- restriction methods.
78
+ class Comment < ActiveRecord::Base
79
+ named_filter(:offensive) { with(:offensive, true) }
80
+ end
73
81
 
74
- ==== Comparison operators
82
+ class Post < ActiveRecord::Base
83
+ named_filter(:recursive_test) do
84
+ having(:comments) do
85
+ offensive
86
+ end
87
+ end
88
+ end
75
89
 
76
- with(:price).greater_than(10) # :conditions => ['price > ?', 10]
90
+ == Specifying Filters
77
91
 
78
- with(:created_at).less_than(2.days.ago) # :conditions => ['created_at < ?', 2.days.ago]
92
+ record_filter supports all of SQL query abstractions provided by ActiveRecord, specifically:
79
93
 
80
- These methods can also have _or_equal_to tagged onto the end, to obvious affect, and all of the comparison operators are aliased to
81
- their standard single-character variants:
94
+ * Conditions
95
+ * Boolean operations
96
+ * Joins
97
+ * Limits
98
+ * Offsets
99
+ * Ordering
100
+ * Grouping
82
101
 
83
- gt, gte, lt and lte
102
+ The following example shows the use of each of these techniques:
84
103
 
85
- ==== IS NULL
104
+ Post.filter do
105
+ any_of do
106
+ with(:permalink).is_null
107
+ having(:comments) do
108
+ with(:offensive, true)
109
+ end
110
+ end
111
+ limit(10, 100)
112
+ order(:created_at, :desc)
113
+ group_by(:comments => :offensive)
114
+ end
86
115
 
87
- with(:price, nil) # :conditions => ['price IS NULL']
116
+ === Conditions
88
117
 
89
- This short form can also be made explicit by using the is_null, null, or nil functions on with:
118
+ Conditions are specified using the 'with' function, which takes as its first argument
119
+ the name of the column to restrict. If a second argument is given, it will automatically
120
+ be used as the value in an equality condition. The 'with' function will return a Restriction
121
+ object that has methods to specify a number of different conditions and to negate them:
90
122
 
91
- with(:price).is_null # :conditions => ['price IS NULL']
123
+ with(:permalink, 'aardvarks') # :conditions => ['permalink = ?', 'aardvarks']
124
+ with(:permalink).equal_to('sheep') # :conditions => ['permalink = ?', 'sheep']
125
+ with(:permalink).not.equal_to('cats') # :conditions => ['permailnk <> ?', 'cats']
92
126
 
93
- It can be negated either by chaining with the 'not' function or by using is_not_null:
127
+ with(:permalink, nil) # :conditions => ['permalink IS NULL']
128
+ with(:permalink).is_null # :conditions => ['permalink IS NULL']
129
+ with(:permalink, nil).not # :conditions => ['permalink IS NOT NULL']
94
130
 
95
- with(:price).is_not_null # :conditions => ['price IS NOT NULL']
96
- with(:price).is_null.not # "
131
+ The following condition types are supported through the Restriction API:
97
132
 
98
- ==== IN
133
+ * Equality
134
+ * Comparisons (> >= < <=)
135
+ * Between
136
+ * In
137
+ * Is null
138
+ * Like
99
139
 
100
- with(:id).in([1, 2, 3]) # :conditions => ['id IN (?)', [1, 2, 3]]
140
+ === Boolean Operations
101
141
 
102
- ==== BETWEEN
142
+ Conditions can be combined with boolean operators using the methods all_of, any_of, none_of
143
+ and not_all_of. These methods take a block where any conditions they contain will be combined
144
+ using AND, OR and NOT to create the correct condition. The block can also contain any number of
145
+ joins or other boolean operations. The default operator is all_of.
103
146
 
104
- with(:id).between(1, 5) # :conditions => ['id BETWEEN ? AND ?', 1, 5]
147
+ Post.filter do
148
+ with(:id, 4)
149
+ with(:permalink, 'ack')
150
+ end
105
151
 
106
- The argument to between can also be either a tuple or a range
152
+ :conditions => ['id = ? AND permalink = ?', 4, 'ack']
107
153
 
108
- with(:created_at).between([Time.now, 3.days.ago]) # :conditions => ['created_at BETWEEN ? AND ?', Time.now, 3.days.ago]
154
+ Post.filter do
155
+ any_of
156
+ with(:id, 3)
157
+ with(:permalink, 'booya')
158
+ end
159
+ end
109
160
 
110
- with(:price).between(1..5) # :conditions => ['price BETWEEN ? AND ?', 1, 5]
161
+ :conditions => ['id = ? OR permalink = ?', 3, 'booya']
111
162
 
112
- ==== LIKE
163
+ Post.filter do
164
+ none_of
165
+ with(:id, 2)
166
+ with(:permalink, 'ouch')
167
+ end
168
+ end
113
169
 
114
- with(:title).like('%help%') # :conditions => ['title LIKE ?', '%help%']
170
+ :conditions => ['NOT (id = ? OR permalink = ?', 2, 'ouch']
115
171
 
172
+ === Joins
116
173
 
117
- === Implicit Joins
174
+ Joins in record_filter come in two varieties. Using the information in ActiveRecord associations,
175
+ it is possible to perform most joins easily using the 'having' method, which requires no specification
176
+ of the columns to use for the join. In cases where an association does not apply, it is also possible
177
+ to create an explicit join that can include both the columns to combine as well as restrictions on
178
+ the columns in the join table.
118
179
 
119
- Implicit joins are specified using the 'having' function, which takes as its argument the name of an association to join on.
180
+ In a filter for a Post model that has_many comments, the following two examples are equivalent:
120
181
 
121
- having(:comments) # :joins => :comments
182
+ having(:comments)
122
183
 
123
- This function can be chained with calls to 'with' in order to specify conditions on the joined table:
184
+ join(Comment, :inner) do
185
+ on(:id => :post_id)
186
+ end
124
187
 
125
- having(:comments).with(:created_at).gt(2.days.ago) # :joins => :comments, :conditions => ['comments.created_at > ?', 2.days.ago]
188
+ With an explicit join, any number of columns can be matched in this way, and both join types
189
+ accept a block in which any number of conditions, boolean operations, or other joins can be
190
+ added. Explicit joins also allow conditions to be set on columns of the table being joined:
126
191
 
127
- It can also take a block that can have any number of conditions or other clauses (including other joins) in it:
192
+ having(:comments).with(:offensive, true)
128
193
 
129
194
  having(:comments) do
130
- with(:created_at).gt(2.days.ago)
131
- having(:author).with(:name, 'Bubba')
195
+ with(:created_at).greater_than(2.days.ago)
132
196
  end
133
197
 
134
- The 'having' function can also take :inner, :left or :right as its second argument in order to specify that a particular join type
135
- should be used.
198
+ join(Comment, :inner) do
199
+ on(:id => :commentable_id)
200
+ on(:commentable_type).equal_to('Post')
201
+ end
136
202
 
137
- === Explicit joins
203
+ With implicit joins, it is also possible to use a hash as the association name, in which case
204
+ multiple joins can be created with one statement. If the comment model has_one Author, this
205
+ example will join both tables and add a condition on the author.
138
206
 
139
- In cases where there is no ActiveRecord association that can be used to specify an implicit join, explicit joins are also
140
- supported, using the 'join' function. Its arguments are the class to be joined against, the join type (:inner, left or :right) and
141
- an optional alias for the join table. A block should also be supplied in order to specify the columns to use for the join using the
142
- 'on' method.
207
+ having(:comments => :author).with(:name, 'Bob')
143
208
 
144
- Post.filter do
145
- join(Comment, :inner, :posts__comments_alias) do
146
- on(:id => :commentable_id)
147
- on(:commentable_type, 'Post')
148
- end
149
- end
209
+ === Limits and Offsets
150
210
 
151
- === Conjunctions
211
+ These are specified using the 'limit' method, which takes two arguments, the offset and the
212
+ limit. If only one argument is given, it is assumed to be the limit.
152
213
 
153
- The following conjunction types are supported:
214
+ limit(10, 100) # :offset => 10, :limit => 100
215
+ limit(100) # :offset => 0, :limit => 100
154
216
 
155
- * any_of
156
- * all_of
157
- * none_of
158
- * not_all_of
217
+ === Ordering
159
218
 
160
- Each takes a block that can contain conditions or joins and will apply the expected boolean logic to the combination.
219
+ Ordering is done through the 'order' method, which accepts arguments for the column and direction.
220
+ The column can either be passed as the name of a column in the class that is being filtered or as
221
+ a hash that represents a path through the joined associations to the correct column. The direction argument
222
+ should be either :asc or :desc and defaults to :asc if not given. Multiple calls to 'order' are
223
+ allowed and will be applied in the order in which they were given.
161
224
 
162
- any_of
163
- with(:price, 2)
164
- with(:price).gt(100)
225
+ Post.filter do
226
+ having(:comments).with(:offensive, true)
227
+ order(:created_at, :desc)
228
+ order(:comments => :id)
165
229
  end
166
230
 
167
- # :conditions => ['price = ? OR price > ?', 2, 100]
168
-
169
- === Limits and ordering
231
+ # :order => "'posts'.created_at DESC posts__comments.id ASC"
170
232
 
171
- limit(20)
233
+ === Grouping
172
234
 
173
- order(:id, :desc)
235
+ Grouping is specified with the 'group_by' method, which accepts either the name of a column in the
236
+ class that is being filtered or a hash that represents a path through the joined associations. If
237
+ there are multiple calls to 'group_by' they will be combined in the final result, maintaining the
238
+ order in which they were given.
174
239
 
175
- Multiple order clauses can be specified, and they will be applied in the order in which they are specified.
176
- When joins are used, order can also take a hash as the first argument that leads to the column to order on through the joins:
177
-
178
- having(:comments).with(:offensive, true)
179
- order(:comments => :id, :desc)
240
+ Post.filter do
241
+ having(:comments).with(:created_at).greater_than(1.hour.ago)
242
+ group_by(:permalink)
243
+ group_by(:comments => :offensive)
244
+ end
180
245
 
246
+ # :group => "'posts'.permalink, posts__comments.offensive'
181
247
 
182
248
  == LICENSE:
183
249
 
184
250
  (The MIT License)
185
251
 
186
- Copyright (c) 2008 Mat Brown
252
+ Copyright (c) 2008 Mat Brown, Aubrey Holland
187
253
 
188
254
  Permission is hereby granted, free of charge, to any person obtaining
189
255
  a copy of this software and associated documentation files (the
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
- :minor: 8
3
+ :minor: 9
4
4
  :patch: 0
@@ -159,7 +159,7 @@ module RecordFilter
159
159
 
160
160
  # Create an implicit join using an association as the target. This method allows you to
161
161
  # easily specify a join without specifying the columns to use by taking any needed data
162
- # from the specified ActiveRecord association. If given, the block will be evaluated in
162
+ # from the given ActiveRecord association. If provided, the block will be evaluated in
163
163
  # the context of the table that has been joined, so any restrictions or other joins will
164
164
  # be performed using its columns and associations. For example, if a Post has_many comments
165
165
  # then the following code will join to the comments table and restrict the comments based
@@ -169,12 +169,13 @@ module RecordFilter
169
169
  # with(:created_at).greater_than(3.days.ago)
170
170
  # end
171
171
  # end
172
- # If one argument is given, it is assumed to be a symbol representing the name of the association
173
- # that will be used for the join and a join type of :inner will be used. If two arguments are
174
- # provided, the first one is assumed to be the join type, which can be one of :inner, :left or
172
+ # If one argument is given, it is assumed to represent the name of the association
173
+ # that will be used for the join and a join type of :inner will be used by default. If two arguments
174
+ # are provided, the first one is assumed to be the join type, which can be one of :inner, :left or
175
175
  # :right and the second one is the association name. An alias will automatically be created
176
176
  # for the joined table named "#{left_table}__#{association_name}", so in the above example, the
177
- # alias would be posts__comments.
177
+ # alias would be posts__comments. It is also possible to provide a hash as the association
178
+ # name, in which case a trail of associations can be joined in one statment.
178
179
  #
179
180
  # ==== Parameters
180
181
  # join_type<Symbol>::
@@ -53,7 +53,7 @@ module RecordFilter
53
53
  # a column in the class that is being filtered. With a hash argument, it is possible
54
54
  # to specify a path to a column in one of the joined tables, as seen above.
55
55
  # direction<Symbol>::
56
- # Specifies the direction of the join. Should be either :asc or :desc.
56
+ # Specifies the direction of the join. Should be either :asc or :desc and defaults to :asc.
57
57
  #
58
58
  # ==== Returns
59
59
  # nil
@@ -201,6 +201,21 @@ module RecordFilter
201
201
  self
202
202
  end
203
203
 
204
+ # Create a negated IN restriction of the form ['column NOT IN (?)', value]
205
+ #
206
+ # ==== Parameters
207
+ # value::
208
+ # Either a single item or an array of values to form the inclusion test.
209
+ #
210
+ # ==== Returns
211
+ # Restriction:: self
212
+ #
213
+ # @public
214
+ def not_in(value)
215
+ @value, @operator, @negated = value, :in, true
216
+ self
217
+ end
218
+
204
219
  # Create a LIKE restriction of the form ['column LIKE ?', value]
205
220
  #
206
221
  # ==== Parameters
@@ -2,7 +2,7 @@ module RecordFilter
2
2
  # This class is the value that is returned from the execution of a filter.
3
3
  class Filter
4
4
 
5
- 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
+ NON_DELEGATE_METHODS = %w(debugger nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? respond_to?)
6
6
 
7
7
  [].methods.each do |m|
8
8
  unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
@@ -21,7 +21,7 @@ module RecordFilter
21
21
  conditions = @conjunction.to_conditions
22
22
  params = { :conditions => conditions } if conditions
23
23
  joins = @table.all_joins
24
- params[:joins] = joins.map { |join| join.to_sql } * ' ' unless joins.empty?
24
+ params[:joins] = joins.map { |join| join.to_sql } unless joins.empty?
25
25
  if (joins.any? { |j| j.requires_distinct_select? })
26
26
  if count_query
27
27
  params[:select] = "DISTINCT #{@table.model_class.quoted_table_name}.#{@table.model_class.primary_key}"
@@ -17,6 +17,7 @@ module RecordFilter
17
17
  end
18
18
 
19
19
  def join_association(association_name, join_type=nil, options={})
20
+ association_name = association_name.to_sym
20
21
  @joins_cache[association_name] ||=
21
22
  begin
22
23
  association = @model_class.reflect_on_association(association_name)
@@ -115,7 +116,7 @@ module RecordFilter
115
116
  protected
116
117
 
117
118
  def alias_for_association(association)
118
- "#{@aliased ? @table_alias.to_s : @model_class.table_name}__#{association.name}"
119
+ "#{@aliased ? @table_alias.to_s : @model_class.table_name}__#{association.name.to_s.downcase}"
119
120
  end
120
121
 
121
122
  alias_method :alias_for_class, :alias_for_association
@@ -18,7 +18,7 @@ describe 'active record options' do
18
18
  end
19
19
 
20
20
  it 'should create the correct join' do
21
- Blog.last_find[:joins].should == %q(INNER JOIN "news_stories" AS blogs__stories ON "blogs".id = blogs__stories.blog_id)
21
+ Blog.last_find[:joins].should == [%q(INNER JOIN "news_stories" AS blogs__stories ON "blogs".id = blogs__stories.blog_id)]
22
22
  end
23
23
  end
24
24
 
@@ -34,7 +34,7 @@ describe 'active record options' do
34
34
  end
35
35
 
36
36
  it 'should create the correct join' do
37
- Blog.last_find[:joins].should == %q(INNER JOIN "posts" AS blogs__special_posts ON "blogs".id = blogs__special_posts.special_blog_id)
37
+ Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__special_posts ON "blogs".id = blogs__special_posts.special_blog_id)]
38
38
  end
39
39
  end
40
40
 
@@ -50,7 +50,7 @@ describe 'active record options' do
50
50
  end
51
51
 
52
52
  it 'should create the correct join' do
53
- Blog.last_find[:joins].should == %q(INNER JOIN "posts" AS blogs__special_public_posts ON "blogs".special_id = blogs__special_public_posts.blog_id)
53
+ Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__special_public_posts ON "blogs".special_id = blogs__special_public_posts.blog_id)]
54
54
  end
55
55
  end
56
56
 
@@ -66,7 +66,7 @@ describe 'active record options' do
66
66
  end
67
67
 
68
68
  it 'should create the correct join' do
69
- 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__bad_comments ON blogs__posts.id = blogs__posts__bad_comments.post_id)
69
+ Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id), %q(INNER JOIN "comments" AS blogs__posts__bad_comments ON blogs__posts.id = blogs__posts__bad_comments.post_id)]
70
70
  end
71
71
  end
72
72
 
@@ -82,22 +82,9 @@ describe 'active record options' do
82
82
  end
83
83
 
84
84
  it 'should create the correct join' do
85
- Blog.last_find[:joins].should == %q(INNER JOIN "features" AS blogs__features ON "blogs".id = blogs__features.blog_id AND (blogs__features.featurable_type = 'Post') INNER JOIN "posts" AS blogs__features__featurable ON blogs__features.featurable_id = blogs__features__featurable.id)
86
- end
87
- end
88
-
89
- # :include
90
- # :finder_sql
91
- # :counter_sql
92
- # :group
93
- # :having
94
- # :limit
95
- # :offset
96
- # :select
97
- # :uniq
98
- # :readonly
99
- # :order
100
- # :conditions
85
+ Blog.last_find[:joins].should == [%q(INNER JOIN "features" AS blogs__features ON "blogs".id = blogs__features.blog_id AND (blogs__features.featurable_type = 'Post')), %q(INNER JOIN "posts" AS blogs__features__featurable ON blogs__features.featurable_id = blogs__features__featurable.id)]
86
+ end
87
+ end
101
88
  end
102
89
 
103
90
  describe 'for belongs_to' do
@@ -114,7 +101,7 @@ describe 'active record options' do
114
101
  end
115
102
 
116
103
  it 'should create the correct join' do
117
- Post.last_find[:joins].should == %q(INNER JOIN "blogs" AS posts__publication ON "posts".blog_id = posts__publication.id)
104
+ Post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__publication ON "posts".blog_id = posts__publication.id)]
118
105
  end
119
106
  end
120
107
 
@@ -130,15 +117,81 @@ describe 'active record options' do
130
117
  end
131
118
 
132
119
  it 'should create the correct join' do
133
- Post.last_find[:joins].should == %q(INNER JOIN "blogs" AS posts__special_blog ON "posts".special_blog_id = posts__special_blog.id)
120
+ Post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__special_blog ON "posts".special_blog_id = posts__special_blog.id)]
134
121
  end
135
122
  end
123
+ end
124
+
125
+ describe 'working with named scopes' do
126
+ before do
127
+ @blog = Class.new(Blog)
128
+ @blog.named_scope :with_high_id, { :conditions => ['id > 100'] }
129
+ @blog.named_filter(:published) { with(:published, true) }
130
+ end
131
+
132
+ it 'should concatenate the filter with the scope correctly' do
133
+ @blog.with_high_id.published.inspect
134
+ @blog.last_find[:conditions].should == %q(("blogs".published = 't') AND (id > 100))
135
+ end
136
+
137
+ it 'should concatenate correctly when called in the other order' do
138
+ @blog.published.with_high_id.inspect
139
+ @blog.last_find[:conditions].should == %q((id > 100) AND ("blogs".published = 't'))
140
+ end
141
+ end
142
+
143
+ describe 'working with named scopes when there are a number of joins' do
144
+ before do
145
+ @blog = Class.new(Blog)
146
+ @blog.named_scope :ads_with_sale, { :joins => :ads, :conditions => ["'ads'.content LIKE ?", '%sale%'] }
147
+ @blog.named_filter(:with_permalinked_posts) { having(:posts).with(:permalink).is_not_null }
148
+ @blog.named_filter(:with_offensive_comments) { having(:comments).with(:offensive, true) }
149
+ @blog.with_permalinked_posts.ads_with_sale.with_offensive_comments.inspect
150
+ end
151
+
152
+ it 'should concatenate the conditions correctly' do
153
+ @blog.last_find[:conditions].should == %q((blogs__posts__comments.offensive = 't') AND (('ads'.content LIKE '%sale%') AND (blogs__posts.permalink IS NOT NULL)))
154
+ end
155
+
156
+ it 'should concatenate the joins correctly and not throw away my joins like AR usually does' do
157
+ @blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id), %q(INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id), %q(INNER JOIN "ads" ON ads.blog_id = blogs.id)]
158
+ end
159
+ end
136
160
 
137
- # :include
138
- # :conditions
139
- # :select
140
- # :foreign_key
141
- # :polymorphic
142
- # :readonly
161
+ describe 'working with named scopes that join to the same table' do
162
+ before do
163
+ @blog = Class.new(Blog)
164
+ @blog.named_scope :with_crazy_post_permalinks, { :joins => :posts, :conditions => ["'posts'.permalink = ?", 'crazy'] }
165
+ @blog.named_filter(:with_empty_permalinks) { having(:posts).with(:permalink, nil) }
166
+ end
167
+
168
+ it 'should concatenate the conditions correctly' do
169
+ @blog.with_crazy_post_permalinks.with_empty_permalinks.inspect
170
+ @blog.last_find[:conditions].should == %q((blogs__posts.permalink IS NULL) AND ('posts'.permalink = 'crazy'))
171
+ end
172
+
173
+ it 'should concatenate the joins correctly' do
174
+ @blog.with_crazy_post_permalinks.with_empty_permalinks.inspect
175
+ @blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id), %q(INNER JOIN "posts" ON posts.blog_id = blogs.id)]
176
+ end
177
+ end
178
+
179
+ describe 'working with default scopes' do
180
+ describe 'with a simple filter' do
181
+ before do
182
+ Article.filter do
183
+ with(:contents, 'something')
184
+ end.inspect
185
+ end
186
+
187
+ it 'should use the correct order' do
188
+ Article.last_find[:order].should == %q(created_at DESC)
189
+ end
190
+
191
+ it 'should use the correct conditions' do
192
+ pending 'currently the IS NULL condition is added twice.'
193
+ Article.last_find[:conditions].should == %q((("articles"."created_at" IS NULL) AND ("articles".contents = 'something')))
194
+ end
195
+ end
143
196
  end
144
197
  end
@@ -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 OUTER 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 OUTER 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 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'))
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 OUTER 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
 
@@ -101,7 +101,7 @@ describe 'explicit joins' do
101
101
  end
102
102
 
103
103
  it 'should produce the correct join' do
104
- @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
+ @blog.last_find[:joins].should == [%q(INNER JOIN "ads" AS blogs__ads ON "blogs".id = blogs__ads.blog_id), %q(LEFT OUTER JOIN "posts" AS blogs__post ON "blogs".id = blogs__post.blog_id), %q(INNER JOIN "comments" AS blogs__post__comment ON blogs__post.id = blogs__post__comment.post_id AND (blogs__post__comment.offensive = 't'))]
105
105
  end
106
106
  end
107
107
  end
@@ -14,7 +14,7 @@ describe 'implicit joins' do
14
14
  end
15
15
 
16
16
  it 'should add correct join' do
17
- Post.last_find[:joins].should == %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)
17
+ Post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
18
18
  end
19
19
 
20
20
  it 'should query against condition on join table' do
@@ -24,7 +24,7 @@ describe 'implicit joins' do
24
24
 
25
25
  shared_examples_for 'multiple conditions on single join' do
26
26
  it 'should add join once' do
27
- Post.last_find[:joins].should == %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)
27
+ Post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
28
28
  end
29
29
 
30
30
  it 'should query against conditions on join table' do
@@ -65,7 +65,7 @@ describe 'implicit joins' do
65
65
  end
66
66
 
67
67
  it 'should add correct join' do
68
- Blog.last_find[:joins].should == %q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id)
68
+ Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id)]
69
69
  end
70
70
 
71
71
  it 'should query against condition on join table' do
@@ -84,8 +84,8 @@ describe 'implicit joins' do
84
84
  end
85
85
 
86
86
  it 'should add both joins' do
87
- Blog.last_find[:joins].should == %q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id ) +
88
- %q(INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)
87
+ Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id),
88
+ %q(INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)]
89
89
  end
90
90
 
91
91
  it 'should query against both conditions' do
@@ -103,8 +103,8 @@ describe 'implicit joins' do
103
103
  end
104
104
 
105
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)
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
108
  end
109
109
 
110
110
  it 'should query against both conditions' do
@@ -120,7 +120,7 @@ describe 'implicit joins' do
120
120
  end
121
121
 
122
122
  it 'should add correct join' do
123
- Post.last_find[:joins].should == %q(INNER JOIN "photos" AS posts__photo ON "posts".id = posts__photo.post_id)
123
+ Post.last_find[:joins].should == [%q(INNER JOIN "photos" AS posts__photo ON "posts".id = posts__photo.post_id)]
124
124
  end
125
125
 
126
126
  it 'should query against condition on join table' do
@@ -136,8 +136,8 @@ describe 'implicit joins' do
136
136
  end
137
137
 
138
138
  it 'should add correct join' do
139
- Blog.last_find[:joins].should == %q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id ) +
140
- %q(INNER JOIN "photos" AS blogs__posts__photo ON blogs__posts.id = blogs__posts__photo.post_id)
139
+ Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id),
140
+ %q(INNER JOIN "photos" AS blogs__posts__photo ON blogs__posts.id = blogs__posts__photo.post_id)]
141
141
  end
142
142
 
143
143
  it 'should query against condition on join table' do
@@ -153,8 +153,8 @@ describe 'implicit joins' do
153
153
  end
154
154
 
155
155
  it 'should add correct join' do
156
- Post.last_find[:joins].should == %q(INNER JOIN "posts_tags" AS __posts__tags ON "posts".id = __posts__tags.post_id ) +
157
- %q(INNER JOIN "tags" AS posts__tags ON __posts__tags.tag_id = posts__tags.id)
156
+ Post.last_find[:joins].should == [%q(INNER JOIN "posts_tags" AS __posts__tags ON "posts".id = __posts__tags.post_id),
157
+ %q(INNER JOIN "tags" AS posts__tags ON __posts__tags.tag_id = posts__tags.id)]
158
158
  end
159
159
  end
160
160
 
@@ -235,7 +235,7 @@ describe 'implicit joins' do
235
235
  end
236
236
 
237
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)
238
+ Blog.last_find[:joins].should == [%q(LEFT OUTER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id)]
239
239
  end
240
240
  end
241
241
 
@@ -253,7 +253,7 @@ describe 'implicit joins' do
253
253
  end
254
254
 
255
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)
256
+ Blog.last_find[:joins].should == [%q(LEFT OUTER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id), %q(LEFT OUTER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)]
257
257
  end
258
258
  end
259
259
 
@@ -271,7 +271,7 @@ describe 'implicit joins' do
271
271
  end
272
272
 
273
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'))
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
275
  end
276
276
  end
277
277
 
@@ -289,7 +289,7 @@ describe 'implicit joins' do
289
289
  end
290
290
 
291
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)
292
+ Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id), %q(INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)]
293
293
  end
294
294
  end
295
295
 
@@ -307,7 +307,26 @@ describe 'implicit joins' do
307
307
  end
308
308
 
309
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)
310
+ Post.last_find[:joins].should == [%q(INNER JOIN "authors" AS posts__author ON "posts".id = posts__author.post_id), %q(INNER JOIN "users" AS posts__author__user ON posts__author.user_id = posts__author__user.id)]
311
+ end
312
+ end
313
+
314
+ describe 'passing strings instead of symbols' do
315
+ before do
316
+ Post.filter do
317
+ having('comments') do
318
+ with('offensive', true)
319
+ end
320
+ with('id').gte(12)
321
+ end.inspect
322
+ end
323
+
324
+ it 'should create the correct condition' do
325
+ Post.last_find[:conditions].should == [%q((posts__comments.offensive = ?) AND ("posts".id >= ?)), true, 12]
326
+ end
327
+
328
+ it 'should create the correct join' do
329
+ Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)]
311
330
  end
312
331
  end
313
332
  end
data/spec/models.rb CHANGED
@@ -4,6 +4,12 @@ class Ad < ActiveRecord::Base
4
4
  end
5
5
 
6
6
 
7
+ class Article < ActiveRecord::Base
8
+ extend TestModel
9
+ default_scope :order => 'created_at DESC', :conditions => { :created_at => nil }
10
+ end
11
+
12
+
7
13
  class Author < ActiveRecord::Base
8
14
  extend TestModel
9
15
  belongs_to :user
@@ -24,6 +30,7 @@ class Blog < ActiveRecord::Base
24
30
  has_many :features
25
31
  has_many :featured_posts, :through => :features, :source => :featurable, :source_type => 'Post'
26
32
  has_many :posts_with_comments, :class_name => 'Post', :include => :comments
33
+ has_many :articles
27
34
  end
28
35
 
29
36
 
@@ -119,7 +119,7 @@ describe 'named filters' do
119
119
  having(:comments).offensive_or_not(true)
120
120
  end.inspect
121
121
  Post.last_find[:conditions].should == [%q(posts__comments.offensive = ?), true]
122
- Post.last_find[:joins].should == %q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)
122
+ Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)]
123
123
  end
124
124
 
125
125
  it 'should work correctly with the named filter called within the having block' do
@@ -129,7 +129,7 @@ describe 'named filters' do
129
129
  end
130
130
  end.inspect
131
131
  Post.last_find[:conditions].should == [%q(posts__comments.offensive = ?), false]
132
- Post.last_find[:joins].should == %q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)
132
+ Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)]
133
133
  end
134
134
  end
135
135
 
@@ -181,7 +181,7 @@ describe 'named filters' do
181
181
  base = @post.for_blog(1)
182
182
  base.with_offensive_comments
183
183
  base.inspect
184
- @post.last_find[:joins].should == %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)
184
+ @post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)]
185
185
  end
186
186
 
187
187
  it 'should not change an original filter when reusing it' do
@@ -250,7 +250,7 @@ describe 'named filters' do
250
250
 
251
251
  it 'compile the joins correctly' do
252
252
  @blog.with_offensive_comments.with_ads_with_content('ack').inspect
253
- @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)]
253
+ @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), %q(INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)]
254
254
  end
255
255
  end
256
256
 
@@ -61,6 +61,13 @@ describe 'RecordFilter restrictions' do
61
61
  Post.last_find.should == { :conditions => [%q{"posts".blog_id NOT IN (?)}, [1, 3, 5]] }
62
62
  end
63
63
 
64
+ it 'should work correctly for NOT IN' do
65
+ Post.filter do
66
+ with(:blog_id).not_in [1, 3, 5]
67
+ end.inspect
68
+ Post.last_find.should == { :conditions => [%q{"posts".blog_id NOT IN (?)}, [1, 3, 5]] }
69
+ end
70
+
64
71
  it 'should do the right thing for IN filters with empty arrays' do
65
72
  Post.filter do
66
73
  with(:blog_id).in([])
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.8.0
4
+ version: 0.9.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-05-04 00:00:00 -07:00
13
+ date: 2009-05-06 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies: []
16
16