aub-record_filter 0.8.0 → 0.9.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/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