aub-record_filter 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,23 @@
1
+ = 0.9.8
2
+
3
+ * BREAKING CHANGE: changed the 'limit' method so that the limit is always the
4
+ first argument, with an optional offset as the second argument. This makes the
5
+ functionality much clearer by avoiding obnoxious argument swapping.
6
+
7
+ * BREAKING CHANGE: changed the arguments to the 'join' method so that it
8
+ takes a class and an options hash. The options hash can contain a :join_type
9
+ and an :alias, allowing only the arguments that the client wants to use to be
10
+ specified. This will break existing code where the join type was passed as
11
+ the second argument.
12
+
13
+ * BREAKING CHANGE: changed the arguments to the 'having' method so that it
14
+ takes an association name and an options hash. The options hash takes
15
+ :join_type and :alias. This will break existing code where the join_type was
16
+ passed as the first argument to the method. The alias is a new option that
17
+ allows you to alias joins, which lets you join in the same association twice.
18
+
19
+ * Added a distinct method to force queries to be distinct.
20
+
1
21
  = 0.9.7
2
22
 
3
23
  * Fix a bug where explicit joins would not honor different aliases if joining
data/README.rdoc CHANGED
@@ -122,7 +122,7 @@ The following example shows the use of each of these techniques:
122
122
  with(:offensive, true)
123
123
  end
124
124
  end
125
- limit(10, 100)
125
+ limit(100, 10)
126
126
  order(:created_at, :desc)
127
127
  group_by(:comments => :offensive)
128
128
  end
@@ -234,7 +234,7 @@ In a filter for a Post model that has_many comments, the following two examples
234
234
 
235
235
  having(:comments)
236
236
 
237
- join(Comment, :inner) do
237
+ join(Comment, :join_type => :inner) do
238
238
  on(:id => :post_id)
239
239
  end
240
240
 
@@ -248,7 +248,7 @@ added. Explicit joins also allow conditions to be set on columns of the table be
248
248
  with(:created_at).greater_than(2.days.ago)
249
249
  end
250
250
 
251
- join(Comment, :inner) do
251
+ join(Comment, :join_type => :inner) do
252
252
  on(:id => :commentable_id)
253
253
  on(:commentable_type).equal_to('Post')
254
254
  with(:created_at).less_than(1.year.ago)
@@ -260,13 +260,29 @@ example will join both tables and add a condition on the author.
260
260
 
261
261
  having(:comments => :author).with(:name, 'Bob')
262
262
 
263
+ For both join types, an options hash can be provided as the second argument for passing the join
264
+ type and/or an alias to use for the joined table. The join type defaults to :inner, and the alias
265
+ defaults to a unique name for identifying the table. Using aliases allows you to join to a given table
266
+ twice with two different names. How about a contrived example? Awesome.
267
+
268
+ Blog.filter do
269
+ having(:posts, :join_type => :left, :alias => 'posts_1').with(:title, 'a')
270
+ having(:posts, :alias => 'posts_2').with(:title, 'b')
271
+ end
272
+
273
+ # SELECT DISTINCT "blogs".* FROM "blogs"
274
+ # LEFT OUTER JOIN "posts" AS posts_1 ON "blogs".id = posts_1.blog_id
275
+ # INNER JOIN "posts" AS posts_2 ON "blogs".id = posts_2.blog_id
276
+ # WHERE ((posts_1.title = 'a') AND (posts_2.title = 'b'))
277
+
263
278
  === Limits and Offsets
264
279
 
265
- These are specified using the 'limit' method, which takes two arguments, the offset and the
266
- limit. If only one argument is given, it is assumed to be the limit.
280
+ These are specified using the 'limit' method, which takes two arguments, the limit and the
281
+ offset. The offset is optional. For specifying only offsets, the 'offset' method is also available.
267
282
 
268
- limit(10, 100) # :offset => 10, :limit => 100
269
- limit(100) # :offset => 0, :limit => 100
283
+ limit(100, 10) # :limit => 100, :offset => 100
284
+ limit(100) # :limit => 100
285
+ offset(10) # :offset => 10
270
286
 
271
287
  === Ordering
272
288
 
@@ -299,6 +315,19 @@ order in which they were given.
299
315
 
300
316
  # :group => "'posts'.permalink, posts__comments.offensive'
301
317
 
318
+ === Distinct
319
+
320
+ Filters that include outer joins are automatically made distinct by record_filter. For filters that
321
+ use inner joins, use the 'distinct' method in the DSL to force the select to be distinct.
322
+
323
+ Blog.filter do
324
+ with(:created_at).greater_than(1.day.ago)
325
+ having(:posts).with(:permalink, nil)
326
+ distinct
327
+ end
328
+
329
+ # :select => "DISTINCT 'blogs'.*"
330
+
302
331
  == LICENSE:
303
332
 
304
333
  (The MIT License)
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ * Add ability to do custom selects.
2
+ * Add hooks for custom SQL in general.
3
+
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 9
4
- :patch: 7
4
+ :patch: 8
@@ -1,7 +1,7 @@
1
1
  module RecordFilter
2
2
  module Conjunctions # :nodoc: all
3
3
  class Base
4
- attr_reader :table_name, :limit, :offset
4
+ attr_reader :table_name, :limit, :offset, :distinct
5
5
 
6
6
  def self.create_from(dsl_conjunction, table)
7
7
  result = case dsl_conjunction.type
@@ -19,7 +19,7 @@ module RecordFilter
19
19
  when DSL::Conjunction
20
20
  result.add_conjunction(create_from(step, table))
21
21
  when DSL::Join
22
- join = result.add_join_on_association(step.association, step.join_type)
22
+ join = result.add_join_on_association(step.association, step.join_type, step.aliaz)
23
23
  result.add_conjunction(create_from(step.conjunction, join.right_table))
24
24
  when DSL::ClassJoin
25
25
  join = result.add_join_on_class(
@@ -36,6 +36,7 @@ module RecordFilter
36
36
  else raise InvalidFilterException.new('And invalid filter step was provided.')
37
37
  end
38
38
  end
39
+ result.set_distinct(dsl_conjunction.distinct)
39
40
  result
40
41
  end
41
42
 
@@ -44,6 +45,7 @@ module RecordFilter
44
45
  @table_name = table.table_alias
45
46
  @restrictions = restrictions || []
46
47
  @joins = joins || []
48
+ @distinct = false
47
49
  end
48
50
 
49
51
  def add_restriction(column_name, operator, value, options={})
@@ -57,13 +59,13 @@ module RecordFilter
57
59
  conjunction
58
60
  end
59
61
 
60
- def add_join_on_association(association_name, join_type)
62
+ def add_join_on_association(association_name, join_type, aliaz)
61
63
  table = @table
62
64
  while association_name.is_a?(Hash)
63
- table = table.join_association(association_name.keys[0], join_type).right_table
65
+ table = table.join_association(association_name.keys[0], { :join_type => join_type }).right_table
64
66
  association_name = association_name.values[0]
65
67
  end
66
- table.join_association(association_name, join_type)
68
+ table.join_association(association_name, :join_type => join_type, :alias => aliaz)
67
69
  end
68
70
 
69
71
  def add_join_on_class(join_class, join_type, table_alias, conditions)
@@ -82,6 +84,10 @@ module RecordFilter
82
84
  @limit, @offset = limit, offset
83
85
  end
84
86
 
87
+ def set_distinct(value)
88
+ @distinct = value
89
+ end
90
+
85
91
  def add_named_filter(name, args)
86
92
  unless @table.model_class.named_filters.include?(name.to_sym)
87
93
  raise NamedFilterNotFoundException.new("The named filter #{name} was not found in #{@table.model_class}")
@@ -2,10 +2,10 @@ module RecordFilter
2
2
  module DSL
3
3
  class Conjunction # :nodoc: all
4
4
 
5
- attr_reader :type, :steps
5
+ attr_reader :type, :steps, :distinct
6
6
 
7
7
  def initialize(model_class, type=:all_of)
8
- @model_class, @type, @steps = model_class, type, []
8
+ @model_class, @type, @steps, @distinct = model_class, type, [], false
9
9
  end
10
10
 
11
11
  def add_restriction(column, value)
@@ -19,10 +19,10 @@ module RecordFilter
19
19
  @steps << dsl.conjunction
20
20
  end
21
21
 
22
- def add_join(association, join_type, &block)
22
+ def add_join(association, join_type, aliaz, &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, join_type, dsl.conjunction)
25
+ @steps << Join.new(association, join_type, dsl.conjunction, aliaz)
26
26
  dsl
27
27
  end
28
28
 
@@ -45,6 +45,10 @@ module RecordFilter
45
45
  @steps << GroupBy.new(column)
46
46
  end
47
47
 
48
+ def set_distinct
49
+ @distinct = true
50
+ end
51
+
48
52
  def add_named_filter(method, *args)
49
53
  @steps << NamedFilter.new(method, *args)
50
54
  end
@@ -169,20 +169,28 @@ 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 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
- # :right and the second one is the association name. An alias will automatically be created
172
+ # Options can be passed to provide a custom join type or table alias through the options
173
+ # hash. The :join_type option can be either :inner, :left or :right and will default to
174
+ # :inner if not provided. The :alias option allows you to provide an alias for use in joining
175
+ # the table. If the same association is joined twice with different aliases, it will be treated
176
+ # as two separate joins. By default an alias will automatically be created
176
177
  # for the joined table named "#{left_table}__#{association_name}", so in the above example, the
177
178
  # alias would be posts__comments. It is also possible to provide a hash as the association
178
179
  # name, in which case a trail of associations can be joined in one statment.
179
180
  #
180
181
  # ==== Parameters
181
- # join_type<Symbol>::
182
- # Specifies the type of join to perform, and can be one of :inner, :left or :right. :left
183
- # and :right will create left and right outer joins, respectively.
184
182
  # association<Symbol>::
185
183
  # The name of the association to use as a base for the join.
184
+ # options<Hash>::
185
+ # An options hash (see below)
186
+ #
187
+ # ==== Options (options)
188
+ # :join_type<Symbol>::
189
+ # The type of join to use. Available options are :inner, :left and :right. Defaults to :inner.
190
+ # :alias<String>::
191
+ # An alias to use for the table name in the join. If provided, will create a unique name for
192
+ # the join and allow the same association to be joined multiple times. By default, the alias
193
+ # will be "#{left_table}__#{association_name}".
186
194
  #
187
195
  # ==== Returns
188
196
  # ConjunctionDSL::
@@ -193,30 +201,34 @@ module RecordFilter
193
201
  # be used as the association name.
194
202
  #
195
203
  # @public
196
- def having(join_type, association=nil, &block)
197
- if association.nil?
198
- association, join_type = join_type, nil
199
- end
200
- @conjunction.add_join(association, join_type, &block)
204
+ def having(association, options={}, &block)
205
+ @conjunction.add_join(association, options[:join_type], options[:alias], &block)
201
206
  end
202
207
 
203
208
  # Create an explicit join on the table of the given class. This method allows more complex
204
209
  # joins to be speficied than can be created using having, including jump joins and ones that
205
210
  # include conditions on column values. The method accepts a block that can contain any sequence
206
211
  # of conjunctions, restrictions, or other joins, but it must also contain at least one call to
207
- # JoinDSL.on to specify the conditions for the join.
212
+ # JoinDSL.on to specify the conditions for the join. The options hash accepts :join_type and
213
+ # :alias parameters. The :join_type parameter can be either :inner, :left or :right and defaults
214
+ # to :inner. The :alias parameter allows you to specify an alias for the table in the join and
215
+ # defaults to "#{left_table}__#{clazz.name}".
208
216
  #
209
217
  # ==== Parameters
210
218
  # clazz<Class>::
211
219
  # The class that is being joined to.
212
- # join_type<Symbol>::
220
+ # options<Hash>::
221
+ # An options hash (see below)
222
+ # block<Proc>
223
+ # The contents of the join block can contain any sequence of conjunctions, restrictions, or joins.
224
+ #
225
+ # ==== Options(options)
226
+ # :join_type<Symbol>::
213
227
  # Indicates the type of join to use and must be one of :inner, :left or :right, where :left
214
228
  # or :right will create a LEFT or RIGHT OUTER join respectively.
215
- # table_alias<String, optional>::
229
+ # :alias<String>::
216
230
  # If provided, will specify an alias to use in the SQL when referring to the joined table.
217
231
  # If the argument is not given, the alias will be "#{left_table}__#{clazz.name}"
218
- # block<Proc>
219
- # The contents of the join block can contain any sequence of conjunctions, restrictions, or joins.
220
232
  #
221
233
  # ==== Returns
222
234
  # JoinDSL::
@@ -224,8 +236,8 @@ module RecordFilter
224
236
  # for constructions like: join(Comment, :inner).on(:id => :post_id)
225
237
  #
226
238
  # @public
227
- def join(clazz, join_type, table_alias=nil, &block)
228
- @conjunction.add_class_join(clazz, join_type, table_alias, &block)
239
+ def join(clazz, options={}, &block)
240
+ @conjunction.add_class_join(clazz, options[:join_type], options[:alias], &block)
229
241
  end
230
242
 
231
243
  # Access the class that the current filter is being applied to. This is necessary
@@ -285,7 +297,7 @@ module RecordFilter
285
297
  # Define these_methods here just so that we can throw exceptions when they are called. They should not
286
298
  # be callable in the scope of a conjunction_dsl.
287
299
  #
288
- def limit(offset_or_limit, limit=nil) # :nodoc:
300
+ def limit(limit, offset=nil) # :nodoc:
289
301
  raise InvalidFilterException.new('Calls to limit can only be made in the outer block of a filter.')
290
302
  end
291
303
 
@@ -8,25 +8,18 @@ module RecordFilter
8
8
  # last one will override any others.
9
9
  #
10
10
  # ==== Parameters
11
+ # limit<Integer>::
12
+ # Used for the limit of the query.
11
13
  # offset<Integer>::
12
- # Used for the offset of the query.
13
- # limit<Integer::
14
- # Used as the limit for the query.
14
+ # Used as the offset for the query. This argument is optional, with the default
15
+ # being no offset.
15
16
  #
16
17
  # ==== Returns
17
18
  # nil
18
19
  #
19
- # ==== Alternatives
20
- # If called with a single argument, it is assumed to represent the limit, and
21
- # no offset will be specified.
22
- #
23
20
  # @public
24
- def limit(offset, limit=nil)
25
- if limit
26
- @conjunction.add_limit(limit, offset)
27
- else
28
- @conjunction.add_limit(offset, nil)
29
- end
21
+ def limit(limit, offset=nil)
22
+ @conjunction.add_limit(limit, offset)
30
23
  nil
31
24
  end
32
25
 
@@ -129,6 +122,22 @@ module RecordFilter
129
122
  @conjunction.add_group_by(column)
130
123
  nil
131
124
  end
125
+
126
+ # Specify that the resulting query should select distinct results. This method
127
+ # can only be called in the outermost scope of a filter (i.e. not inside of a having
128
+ # block, etc.).
129
+ #
130
+ # ==== Parameters
131
+ # none
132
+ #
133
+ # ==== Returns
134
+ # nil
135
+ #
136
+ # @public
137
+ def distinct
138
+ @conjunction.set_distinct
139
+ nil
140
+ end
132
141
  end
133
142
  end
134
143
  end
@@ -2,10 +2,10 @@ module RecordFilter
2
2
  module DSL
3
3
  class Join # :nodoc: all
4
4
 
5
- attr_reader :association, :join_type, :conjunction
5
+ attr_reader :association, :join_type, :conjunction, :aliaz
6
6
 
7
- def initialize(association, join_type, conjunction)
8
- @association, @join_type, @conjunction = association, join_type, conjunction
7
+ def initialize(association, join_type, conjunction, aliaz)
8
+ @association, @join_type, @conjunction, @aliaz = association, join_type, conjunction, aliaz
9
9
  end
10
10
  end
11
11
  end
@@ -20,7 +20,9 @@ module RecordFilter
20
20
  params = {}
21
21
  conditions = @conjunction.to_conditions
22
22
  params = { :conditions => conditions } if conditions
23
- add_joins(params, count_query)
23
+ joins = @table.all_joins
24
+ params[:joins] = joins.map { |join| join.to_sql } unless joins.empty?
25
+ set_select(params, count_query)
24
26
  orders = @table.orders
25
27
  params[:order] = orders.map { |order| order.to_sql } * ', ' unless orders.empty?
26
28
  group_bys = @table.group_bys
@@ -34,10 +36,8 @@ module RecordFilter
34
36
 
35
37
  protected
36
38
 
37
- def add_joins(params, count_query)
38
- joins = @table.all_joins
39
- params[:joins] = joins.map { |join| join.to_sql } unless joins.empty?
40
- if (joins.any? { |j| j.requires_distinct_select? })
39
+ def set_select(params, count_query)
40
+ if @conjunction.distinct || (@table.all_joins.any? { |j| j.requires_distinct_select? })
41
41
  if count_query
42
42
  params[:select] = "DISTINCT #{@table.table_name}.#{@table.model_class.primary_key}"
43
43
  else
@@ -16,35 +16,39 @@ module RecordFilter
16
16
  @table_name ||= @model_class.quoted_table_name
17
17
  end
18
18
 
19
- def join_association(association_name, join_type=nil, options={})
19
+ def join_association(association_name, options={})
20
20
  association_name = association_name.to_sym
21
- @joins_cache[association_name] ||=
22
- begin
23
- association = @model_class.reflect_on_association(association_name)
24
- if association.nil?
25
- raise AssociationNotFoundException.new("The association #{association_name} was not found on #{@model_class.name}.")
26
- end
27
- if (association.options[:through])
28
- through_association = @model_class.reflect_on_association(association.options[:through])
29
-
30
- through_join = join_association(
31
- association.options[:through],
32
- join_type,
33
- :type_restriction => association.options[:source_type],
34
- :source => association.options[:source])
35
-
36
- through_join.right_table.join_association(
37
- association.options[:source] || association_name, join_type, :join_class => association.options[:source_type])
38
- else
39
- case association.macro
40
- when :belongs_to, :has_many, :has_one
41
- simple_join(association, join_type, options)
42
- when :has_and_belongs_to_many
43
- compound_join(association, join_type)
44
- else raise InvalidJoinException.new("I don't know how to join on an association of type #{association.macro}.")
45
- end
21
+ join_type = options[:join_type] || :inner
22
+ cache_key = options[:alias] || association_name
23
+ @joins_cache[cache_key] ||= begin
24
+ association = @model_class.reflect_on_association(association_name)
25
+ if association.nil?
26
+ raise AssociationNotFoundException.new("The association #{association_name} was not found on #{@model_class.name}.")
27
+ end
28
+ if (association.options[:through])
29
+ through_association = @model_class.reflect_on_association(association.options[:through])
30
+
31
+ through_join = join_association(
32
+ association.options[:through],
33
+ :join_type => join_type,
34
+ :type_restriction => association.options[:source_type],
35
+ :source => association.options[:source])
36
+
37
+ through_join.right_table.join_association(
38
+ association.options[:source] || association_name,
39
+ :join_type => join_type,
40
+ :alias => options[:alias],
41
+ :join_class => association.options[:source_type])
42
+ else
43
+ case association.macro
44
+ when :belongs_to, :has_many, :has_one
45
+ simple_join(association, join_type, options)
46
+ when :has_and_belongs_to_many
47
+ compound_join(association, join_type, options)
48
+ else raise InvalidJoinException.new("I don't know how to join on an association of type #{association.macro}.")
46
49
  end
47
50
  end
51
+ end
48
52
  end
49
53
 
50
54
  def join_class(clazz, join_type, table_alias, conditions)
@@ -109,7 +113,7 @@ module RecordFilter
109
113
 
110
114
  clazz = options[:join_class].nil? ? association.klass : options[:join_class].constantize
111
115
 
112
- join_table = Table.new(clazz, alias_for_association(association))
116
+ join_table = Table.new(clazz, options[:alias] || alias_for_association(association))
113
117
  @joins << join = Join.new(self, join_table, join_predicate, join_type)
114
118
  join
115
119
  end
@@ -125,13 +129,13 @@ module RecordFilter
125
129
  end
126
130
  end
127
131
 
128
- def compound_join(association, join_type)
132
+ def compound_join(association, join_type, options)
129
133
  pivot_join_predicate = [{ @model_class.primary_key => association.primary_key_name.to_sym }]
130
134
  table_name = @model_class.connection.quote_table_name(association.options[:join_table])
131
- pivot_table = PivotTable.new(table_name, association, "__#{alias_for_association(association)}")
135
+ pivot_table = PivotTable.new(table_name, association, "__#{options[:alias] || alias_for_association(association)}")
132
136
  pivot_join = Join.new(self, pivot_table, pivot_join_predicate, join_type)
133
137
  join_predicate = [{ association.association_foreign_key.to_sym => @model_class.primary_key }]
134
- join_table = Table.new(association.klass, alias_for_association(association))
138
+ join_table = Table.new(association.klass, options[:alias] || alias_for_association(association))
135
139
  pivot_table.joins << join = Join.new(pivot_table, join_table, join_predicate, join_type)
136
140
  @joins << pivot_join
137
141
  join
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{record_filter}
5
- s.version = "0.9.7"
5
+ s.version = "0.9.8"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Aubrey Holland", "Mat Brown"]
9
- s.date = %q{2009-06-03}
9
+ s.date = %q{2009-06-04}
10
10
  s.description = %q{RecordFilter is a Pure-ruby criteria API for building complex queries in ActiveRecord. It supports queries that are built on the fly as well as named filters that can be added to objects and chained to create complex queries. It also gets rid of the nasty hard-coded SQL that shows up in most ActiveRecord code with a clean API that makes queries simple and intuitive to build.}
11
11
  s.email = %q{aubreyholland@gmail.com}
12
12
  s.extra_rdoc_files = [
@@ -17,6 +17,7 @@ Gem::Specification.new do |s|
17
17
  "CHANGELOG",
18
18
  "README.rdoc",
19
19
  "Rakefile",
20
+ "TODO",
20
21
  "VERSION.yml",
21
22
  "config/roodi.yml",
22
23
  "lib/record_filter.rb",
@@ -61,7 +61,7 @@ describe 'raising exceptions' do
61
61
  it 'should raise ColumnNotFoundException for explicit joins on bad column names for the right table' do
62
62
  lambda {
63
63
  Review.filter do
64
- join(Feature, :left) do
64
+ join(Feature, :join_type => :left) do
65
65
  on(:reviewable_id => :ftrable_id)
66
66
  on(:reviewable_type => :ftrable_type)
67
67
  with(:priority, 5)
@@ -73,7 +73,7 @@ describe 'raising exceptions' do
73
73
  it 'should raise ColumnNotFoundException for explicit joins on bad column names for the left table' do
74
74
  lambda {
75
75
  Review.filter do
76
- join(Feature, :inner) do
76
+ join(Feature, :join_type => :inner) do
77
77
  on(:rvwable_id => :featurable_id)
78
78
  on(:rvwable_type => :featurable_type)
79
79
  with(:priority, 5)
@@ -85,7 +85,7 @@ describe 'raising exceptions' do
85
85
  it 'should raise ColumnNotFoundException for explicit joins on bad column names in conditions' do
86
86
  lambda {
87
87
  Review.filter do
88
- join(Feature, :inner) do
88
+ join(Feature, :join_type => :inner) do
89
89
  on(:reviewable_id).gt(12)
90
90
  end
91
91
  end.inspect
@@ -95,7 +95,7 @@ describe 'raising exceptions' do
95
95
  it 'should raise an ArgumentError if an invalid join type is specified' do
96
96
  lambda {
97
97
  Review.filter do
98
- join(Feature, :crazy) do
98
+ join(Feature, :join_type => :crazy) do
99
99
  on(:reviewable_type => :featurable_type)
100
100
  end
101
101
  end.inspect
@@ -105,7 +105,7 @@ describe 'raising exceptions' do
105
105
  it 'should raise an InvalidJoinException if no columns are specified for the join' do
106
106
  lambda {
107
107
  Review.filter do
108
- join(Feature, :inner)
108
+ join(Feature, :join_type => :inner)
109
109
  end.inspect
110
110
  }.should raise_error(RecordFilter::InvalidJoinException)
111
111
  end
@@ -8,7 +8,7 @@ describe 'explicit joins' do
8
8
  describe 'specifying a simple join' do
9
9
  before do
10
10
  Post.filter do
11
- join(Blog, :left, :posts_blogs) do
11
+ join(Blog, :join_type => :left, :alias => :posts_blogs) do
12
12
  on(:blog_id => :id)
13
13
  with(:name, 'Test Name')
14
14
  end
@@ -27,7 +27,7 @@ describe 'explicit joins' do
27
27
  describe 'specifying a complex join through polymorphic associations' do
28
28
  before do
29
29
  Review.filter do
30
- join(Feature, :left, :reviews_features) do
30
+ join(Feature, :join_type => :left, :alias => :reviews_features) do
31
31
  on(:reviewable_id => :featurable_id)
32
32
  on(:reviewable_type => :featurable_type)
33
33
  with(:priority, 5)
@@ -47,7 +47,7 @@ describe 'explicit joins' do
47
47
  describe 'should use values as join parameters instead of columns if given' do
48
48
  before do
49
49
  Review.filter do
50
- join(Feature, :left) do
50
+ join(Feature, :join_type => :left) do
51
51
  on(:reviewable_id => :featurable_id)
52
52
  on(:reviewable_type => :featurable_type)
53
53
  on(:featurable_type, 'SomeType')
@@ -64,7 +64,7 @@ describe 'explicit joins' do
64
64
  describe 'using restrictions on join conditions' do
65
65
  before do
66
66
  Review.filter do
67
- join(Feature, :left) do
67
+ join(Feature, :join_type => :left) do
68
68
  on(:featurable_type, nil)
69
69
  on(:featurable_id).gte(12)
70
70
  on(:priority).not(6)
@@ -84,9 +84,9 @@ describe 'explicit joins' do
84
84
  having(:ads) do
85
85
  with(:content, nil)
86
86
  end
87
- join(Post, :left) do
87
+ join(Post, :join_type => :left) do
88
88
  on(:id => :blog_id)
89
- join(Comment, :inner) do
89
+ join(Comment, :join_type => :inner) do
90
90
  on(:id => :post_id)
91
91
  on(:offensive, true)
92
92
  end
@@ -109,11 +109,11 @@ describe 'explicit joins' do
109
109
  before do
110
110
  @blog = Class.new(Blog)
111
111
  @blog.named_filter(:things) do
112
- join(Post, :inner, 'blogs_posts_1') do
112
+ join(Post, :join_type => :inner, :alias => 'blogs_posts_1') do
113
113
  on(:id => :blog_id)
114
114
  with(:title, 'ack')
115
115
  end
116
- join(Post, :inner, 'blogs_posts_2') do
116
+ join(Post, :join_type => :inner, :alias => 'blogs_posts_2') do
117
117
  on(:id => :blog_id)
118
118
  with(:title, 'hmm')
119
119
  end
@@ -224,7 +224,7 @@ describe 'implicit joins' do
224
224
  describe 'passing the join type to having' do
225
225
  before do
226
226
  Blog.filter do
227
- having(:left, :posts) do
227
+ having(:posts, :join_type => :left) do
228
228
  with(:permalink, 'ack')
229
229
  end
230
230
  end.inspect
@@ -242,7 +242,7 @@ describe 'implicit joins' do
242
242
  describe 'passing the join type to having with multiple joins' do
243
243
  before do
244
244
  Blog.filter do
245
- having(:left, :posts => :comments) do
245
+ having({ :posts => :comments }, :join_type => :left) do
246
246
  with(:offensive, true)
247
247
  end
248
248
  end.inspect
@@ -329,4 +329,75 @@ describe 'implicit joins' do
329
329
  Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)]
330
330
  end
331
331
  end
332
+
333
+ describe 'using a table alias' do
334
+ before do
335
+ Post.filter do
336
+ having(:comments, :alias => 'arghs') do
337
+ with(:offensive, true)
338
+ end
339
+ end.inspect
340
+ end
341
+
342
+ it 'should create the correct condition' do
343
+ Post.last_find[:conditions].should == [%q(arghs.offensive = ?), true]
344
+ end
345
+
346
+ it 'should create the correct join' do
347
+ Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS arghs ON "posts".id = arghs.post_id)]
348
+ end
349
+ end
350
+
351
+ describe 'using a table alias to do multiple joins on the same association' do
352
+ before do
353
+ Post.filter do
354
+ having(:comments, :alias => 'ooohs').with(:offensive, true)
355
+ having(:comments, :alias => 'aaahs').with(:offensive, false)
356
+ end.inspect
357
+ end
358
+
359
+ it 'should create the correct condition' do
360
+ Post.last_find[:conditions].should == [%q((ooohs.offensive = ?) AND (aaahs.offensive = ?)), true, false]
361
+ end
362
+
363
+ it 'should create the correct join' do
364
+ Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS ooohs ON "posts".id = ooohs.post_id), %q(INNER JOIN "comments" AS aaahs ON "posts".id = aaahs.post_id)]
365
+ end
366
+ end
367
+
368
+ describe 'using a table alias with has_many :through associations' do
369
+ before do
370
+ Blog.filter do
371
+ having(:comments, :alias => 'arghs') do
372
+ with(:offensive, true)
373
+ end
374
+ end.inspect
375
+ end
376
+
377
+ it 'should create the correct condition' do
378
+ Blog.last_find[:conditions].should == [%q(arghs.offensive = ?), true]
379
+ end
380
+
381
+ it 'should create the correct join' do
382
+ Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id), %q(INNER JOIN "comments" AS arghs ON blogs__posts.id = arghs.post_id)]
383
+ end
384
+ end
385
+
386
+ describe 'using a table alias' do
387
+ before do
388
+ Post.filter do
389
+ having(:comments, :alias => 'arghs') do
390
+ with(:offensive, true)
391
+ end
392
+ end.inspect
393
+ end
394
+
395
+ it 'should create the correct condition' do
396
+ Post.last_find[:conditions].should == [%q(arghs.offensive = ?), true]
397
+ end
398
+
399
+ it 'should create the correct join' do
400
+ Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS arghs ON "posts".id = arghs.post_id)]
401
+ end
402
+ end
332
403
  end
@@ -52,7 +52,7 @@ describe 'filter qualifiers' do
52
52
  before do
53
53
  Post.filter do
54
54
  with :published, true
55
- limit(20, 10)
55
+ limit(10, 20)
56
56
  end.inspect
57
57
  end
58
58
 
@@ -168,7 +168,7 @@ describe 'filter qualifiers' do
168
168
  before do
169
169
  Post.filter do
170
170
  with(:published, false)
171
- join(Comment, :inner) do
171
+ join(Comment, :join_type => :inner) do
172
172
  on(:id => :post_id)
173
173
  end
174
174
  order(Comment => :id)
data/spec/select_spec.rb CHANGED
@@ -18,7 +18,7 @@ describe 'with custom selects for cases where DISTINCT is required' do
18
18
  it 'should put the distinct clause in the select' do
19
19
  [:left, :right].each do |join_type|
20
20
  Post.filter do
21
- having(join_type, :comments).with(:offensive, true)
21
+ having(:comments, :join_type => join_type).with(:offensive, true)
22
22
  end.inspect rescue nil # required because sqlite doesn't support right joins
23
23
  Post.last_find[:select].should == %q(DISTINCT "posts".*)
24
24
  end
@@ -27,12 +27,10 @@ describe 'with custom selects for cases where DISTINCT is required' do
27
27
 
28
28
  describe 'with join types that do not require distinct' do
29
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
30
+ Post.filter do
31
+ having(:comments, :join_type => :inner).with(:offensive, true)
32
+ end.inspect
33
+ Post.last_find[:select].should be_nil
36
34
  end
37
35
  end
38
36
 
@@ -40,7 +38,7 @@ describe 'with custom selects for cases where DISTINCT is required' do
40
38
  it 'should put the distinct clause in the select' do
41
39
  Blog.filter do
42
40
  having(:posts) do
43
- having(:left, :comments).with(:offensive, true)
41
+ having(:comments, :join_type => :left).with(:offensive, true)
44
42
  end
45
43
  end.inspect
46
44
  Blog.last_find[:select].should == %q(DISTINCT "blogs".*)
@@ -50,9 +48,20 @@ describe 'with custom selects for cases where DISTINCT is required' do
50
48
  describe 'on a filter that requires distinct with a count call' do
51
49
  it 'should put the distinct clause in the select' do
52
50
  Post.filter do
53
- having(:left, :comments).with(:offensive, true)
51
+ having(:comments, :join_type => :left).with(:offensive, true)
54
52
  end.count
55
53
  Post.last_find[:select].should == %q(DISTINCT "posts".id)
56
54
  end
57
55
  end
56
+
57
+ describe 'using the distinct method' do
58
+ it 'should always create a distinct query' do
59
+ Blog.filter do
60
+ with(:created_at).gt(1.day.ago)
61
+ having(:posts).with(:permalink, nil)
62
+ distinct
63
+ end.inspect
64
+ Blog.last_find[:select].should == %q(DISTINCT "blogs".*)
65
+ end
66
+ end
58
67
  end
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.9.7
4
+ version: 0.9.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aubrey Holland
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-06-03 00:00:00 -07:00
13
+ date: 2009-06-04 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -27,6 +27,7 @@ files:
27
27
  - CHANGELOG
28
28
  - README.rdoc
29
29
  - Rakefile
30
+ - TODO
30
31
  - VERSION.yml
31
32
  - config/roodi.yml
32
33
  - lib/record_filter.rb