outoftime-record_filter 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
+ :patch: 4
2
3
  :major: 0
3
4
  :minor: 1
4
- :patch: 3
data/lib/record_filter.rb CHANGED
@@ -7,6 +7,7 @@ require 'active_record'
7
7
  end
8
8
 
9
9
  module RecordFilter
10
- class AssociationNotFoundException < Exception; end
11
- class ColumnNotFoundException < Exception; end
10
+ class AssociationNotFoundException < StandardError; end
11
+ class ColumnNotFoundException < StandardError; end
12
+ class InvalidJoinException < StandardError; end
12
13
  end
@@ -18,7 +18,11 @@ module RecordFilter
18
18
  when DSL::Conjunction
19
19
  result.add_conjunction(create_from(step, table))
20
20
  when DSL::Join
21
- join = result.add_join_on_association(step.association)
21
+ join = result.add_join_on_association(step.association, step.join_type)
22
+ result.add_conjunction(create_from(step.conjunction, join.right_table))
23
+ when DSL::ClassJoin
24
+ join = result.add_join_on_class(
25
+ step.join_class, step.join_type, step.table_alias, step.conditions)
22
26
  result.add_conjunction(create_from(step.conjunction, join.right_table))
23
27
  when DSL::Limit
24
28
  result.add_limit_and_offset(step.limit, step.offset)
@@ -40,7 +44,7 @@ module RecordFilter
40
44
 
41
45
  def add_restriction(column_name, operator, value, options={})
42
46
  check_column_exists!(column_name)
43
- restriction_class = "RecordFilter::Restrictions::#{operator.to_s.camelize}".constantize
47
+ restriction_class = RecordFilter::Restrictions::Base.class_from_operator(operator)
44
48
  restriction = restriction_class.new("#{@table_name}.#{column_name}", value, options)
45
49
  self << restriction
46
50
  restriction
@@ -51,16 +55,20 @@ module RecordFilter
51
55
  conjunction
52
56
  end
53
57
 
54
- def add_join_on_association(association_name)
58
+ def add_join_on_association(association_name, join_type)
55
59
  table = @table
56
60
  while association_name.is_a?(Hash)
57
- result = table.join_association(association_name.keys[0])
61
+ result = table.join_association(association_name.keys[0], join_type)
58
62
  table = result.right_table
59
63
  association_name = association_name.values[0]
60
64
  end
61
- table.join_association(association_name)
65
+ table.join_association(association_name, join_type)
62
66
  end
63
67
 
68
+ def add_join_on_class(join_class, join_type, table_alias, conditions)
69
+ @table.join_class(join_class, join_type, table_alias, conditions)
70
+ end
71
+
64
72
  def add_order(column_name, direction)
65
73
  @table.order_column(column_name, direction)
66
74
  end
@@ -86,16 +94,20 @@ module RecordFilter
86
94
  else
87
95
  @restrictions.map do |restriction|
88
96
  conditions = restriction.to_conditions
89
- conditions[0] = "(#{conditions[0]})"
90
- conditions
91
- end.inject do |conditions, new_conditions|
97
+ if conditions
98
+ conditions[0] = "(#{conditions[0]})"
99
+ conditions
100
+ else
101
+ nil
102
+ end
103
+ end.compact.inject do |conditions, new_conditions|
92
104
  conditions.first << " #{conjunctor} #{new_conditions.shift}"
93
105
  conditions.concat(new_conditions)
94
106
  conditions
95
107
  end
96
108
  end
97
109
  end
98
- result[0] = "!(#{result[0]})" if (negated && !result.nil? && !result[0].nil?)
110
+ result[0] = "NOT (#{result[0]})" if (negated && !result.nil? && !result[0].nil?)
99
111
  result
100
112
  end
101
113
 
@@ -1,4 +1,4 @@
1
- %w(conjunction conjunction_dsl dsl group_by join limit order restriction).each { |file| require File.join(File.dirname(__FILE__), 'dsl', file) }
1
+ %w(class_join conjunction conjunction_dsl dsl group_by join join_dsl join_condition limit order restriction).each { |file| require File.join(File.dirname(__FILE__), 'dsl', file) }
2
2
 
3
3
  module RecordFilter
4
4
  module DSL
@@ -0,0 +1,16 @@
1
+ module RecordFilter
2
+ module DSL
3
+ class ClassJoin
4
+ attr_reader :join_class, :join_type, :table_alias, :conjunction
5
+
6
+ def initialize(join_class, join_type, table_alias, conjunction, join_conditions)
7
+ @join_class, @join_type, @table_alias, @conjunction, @join_conditions =
8
+ join_class, join_type, table_alias, conjunction, join_conditions
9
+ end
10
+
11
+ def conditions
12
+ @join_conditions ? @join_conditions.map { |c| c.condition } : nil
13
+ end
14
+ end
15
+ end
16
+ end
@@ -4,35 +4,33 @@ module RecordFilter
4
4
 
5
5
  attr_reader :type, :steps
6
6
 
7
- DEFAULT_VALUE = Object.new
8
-
9
- def initialize(type=:all_of)
10
- @type, @steps = type, []
7
+ def initialize(model_class, type=:all_of)
8
+ @model_class, @type, @steps = model_class, type, []
11
9
  end
12
10
 
13
- def add_restriction(column, value, negated)
14
- @steps << (restriction = Restriction.new(column, negated))
15
- if value == DEFAULT_VALUE
16
- return restriction
17
- elsif value.nil?
18
- restriction.is_null
19
- else
20
- restriction.equal_to(value)
21
- end
22
- nil
11
+ def add_restriction(column, value)
12
+ @steps << (restriction = Restriction.new(column, value))
13
+ restriction
23
14
  end
24
15
 
25
16
  def add_conjunction(type, &block)
26
- dsl = ConjunctionDSL.new(Conjunction.new(type))
17
+ dsl = ConjunctionDSL.new(@model_class, Conjunction.new(@model_class, type))
27
18
  dsl.instance_eval(&block) if block
28
19
  @steps << dsl.conjunction
29
20
  end
30
21
 
31
- def add_join(association, &block)
32
- dsl = ConjunctionDSL.new
22
+ def add_join(association, join_type, &block)
23
+ dsl = ConjunctionDSL.new(@model_class, Conjunction.new(@model_class, :all_of))
24
+ dsl.instance_eval(&block) if block
25
+ @steps << Join.new(association, join_type, dsl.conjunction)
26
+ dsl
27
+ end
28
+
29
+ def add_class_join(clazz, join_type, table_alias, &block)
30
+ dsl = JoinDSL.new(@model_class, Conjunction.new(@model_class, :all_of))
33
31
  dsl.instance_eval(&block) if block
34
- @steps << (join = Join.new(association, dsl.conjunction))
35
- join
32
+ @steps << ClassJoin.new(clazz, join_type, table_alias, dsl.conjunction, dsl.conditions)
33
+ dsl
36
34
  end
37
35
 
38
36
  def add_limit(limit, offset)
@@ -4,18 +4,14 @@ module RecordFilter
4
4
 
5
5
  attr_reader :conjunction
6
6
 
7
- def initialize(conjunction=Conjunction.new(:all_of))
7
+ def initialize(model_class, conjunction)
8
+ @model_class = model_class
8
9
  @conjunction = conjunction
9
10
  end
10
11
 
11
12
  # restriction
12
- def with(column, value=Conjunction::DEFAULT_VALUE)
13
- return @conjunction.add_restriction(column, value, false) # using return just to make it explicit
14
- end
15
-
16
- # restriction
17
- def without(column, value=Conjunction::DEFAULT_VALUE)
18
- return @conjunction.add_restriction(column, value, true) # using return just to make it explicit
13
+ def with(column, value=Restriction::DEFAULT_VALUE)
14
+ return @conjunction.add_restriction(column, value)
19
15
  end
20
16
 
21
17
  # conjunction
@@ -41,9 +37,21 @@ module RecordFilter
41
37
  end
42
38
 
43
39
  # join
44
- def having(association, &block)
45
- join = @conjunction.add_join(association, &block)
46
- ConjunctionDSL.new(join.conjunction)
40
+ def having(join_type_or_association, association=nil, &block)
41
+ if association.nil?
42
+ association, join_type = join_type_or_association, nil
43
+ else
44
+ join_type = join_type_or_association
45
+ end
46
+ @conjunction.add_join(association, join_type, &block)
47
+ end
48
+
49
+ def join(clazz, join_type, table_alias=nil, &block)
50
+ @conjunction.add_class_join(clazz, join_type, table_alias, &block)
51
+ end
52
+
53
+ def filter_class
54
+ @model_class
47
55
  end
48
56
  end
49
57
  end
@@ -8,7 +8,7 @@ module RecordFilter
8
8
 
9
9
  class << self
10
10
  def create(clazz)
11
- subclass(clazz).new
11
+ subclass(clazz).new(clazz, Conjunction.new(clazz, :all_of))
12
12
  end
13
13
 
14
14
  def subclass(clazz)
@@ -2,10 +2,10 @@ module RecordFilter
2
2
  module DSL
3
3
  class Join
4
4
 
5
- attr_reader :association, :conjunction
5
+ attr_reader :association, :join_type, :conjunction
6
6
 
7
- def initialize(association, conjunction)
8
- @association, @conjunction = association, conjunction
7
+ def initialize(association, join_type, conjunction)
8
+ @association, @join_type, @conjunction = association, join_type, conjunction
9
9
  end
10
10
  end
11
11
  end
@@ -0,0 +1,21 @@
1
+ module RecordFilter
2
+ module DSL
3
+ class JoinCondition
4
+
5
+ attr_reader :restriction
6
+
7
+ def initialize(column, value)
8
+ @column = column
9
+ if column.is_a?(Hash) && value == Restriction::DEFAULT_VALUE
10
+ @condition = column
11
+ else
12
+ @restriction = Restriction.new(column, value)
13
+ end
14
+ end
15
+
16
+ def condition
17
+ @condition || restriction
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module RecordFilter
2
+ module DSL
3
+ class JoinDSL < ConjunctionDSL
4
+
5
+ attr_reader :conditions
6
+
7
+ def on(column, value=Restriction::DEFAULT_VALUE)
8
+ @conditions ||= []
9
+ @conditions << (condition = JoinCondition.new(column, value))
10
+ return condition.restriction
11
+ end
12
+ end
13
+ end
14
+ end
@@ -4,8 +4,17 @@ module RecordFilter
4
4
 
5
5
  attr_reader :column, :negated, :operator, :value
6
6
 
7
- def initialize(column, negated)
8
- @column, @negated, @operator = column, negated, nil
7
+ DEFAULT_VALUE = Object.new
8
+
9
+ def initialize(column, value=DEFAULT_VALUE)
10
+ @column, @negated, @operator = column, false, nil
11
+ take_value(value)
12
+ end
13
+
14
+ def not(value=DEFAULT_VALUE)
15
+ @negated = true
16
+ take_value(value)
17
+ self
9
18
  end
10
19
 
11
20
  [:equal_to, :is_null, :less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to, :in, :like].each do |operator|
@@ -32,6 +41,16 @@ module RecordFilter
32
41
  alias_method :lte, :less_than_or_equal_to
33
42
  alias_method :null, :is_null
34
43
  alias_method :nil, :is_null
44
+
45
+ protected
46
+
47
+ def take_value(value)
48
+ if value.nil?
49
+ is_null
50
+ elsif value != DEFAULT_VALUE
51
+ equal_to(value)
52
+ end
53
+ end
35
54
  end
36
55
  end
37
56
  end
@@ -19,6 +19,8 @@ module RecordFilter
19
19
  def method_missing(method, *args, &block)
20
20
  if @clazz.named_filters.include?(method)
21
21
  Filter.new(@clazz, method, @dsl.conjunction, *args)
22
+ elsif [:size, :count, :length].include?(method)
23
+ loaded_count_data.send(method, *args, &block)
22
24
  else
23
25
  loaded_data.send(method, *args, &block)
24
26
  end
@@ -33,6 +35,13 @@ module RecordFilter
33
35
  end
34
36
  end
35
37
 
38
+ def loaded_count_data
39
+ @loaded_count_data ||= begin
40
+ query = Query.new(@clazz, @dsl.conjunction)
41
+ @clazz.scoped(query.to_find_params(true))
42
+ end
43
+ end
44
+
36
45
  def dsl_for_named_filter(clazz, named_filter)
37
46
  return DSL::DSL.create(clazz) if named_filter.blank?
38
47
  while (clazz)
@@ -17,7 +17,7 @@ module RecordFilter
17
17
  raise ColumnNotFoundException.new("The column #{column} was not found in #{table.table_name}.")
18
18
  end
19
19
 
20
- "#{table.table_name}.#{column}"
20
+ "#{table.table_alias}.#{column}"
21
21
  end
22
22
  end
23
23
  end
@@ -2,16 +2,64 @@ module RecordFilter
2
2
  class Join
3
3
  attr_reader :left_table, :right_table
4
4
 
5
- def initialize(left_table, right_table, join_predicate)
6
- @left_table, @right_table, @join_predicate =
7
- left_table, right_table, join_predicate
5
+ def initialize(left_table, right_table, join_conditions, join_type=nil)
6
+ @left_table, @right_table, @join_conditions, @join_type =
7
+ left_table, right_table, join_conditions, join_type
8
+ @join_type ||= :inner
8
9
  end
9
10
 
10
11
  def to_sql
11
- predicate_sql = @join_predicate.map do |left_column, right_column|
12
- "#{@left_table.table_alias}.#{left_column} = #{@right_table.table_alias}.#{right_column}"
12
+ if @join_conditions.blank?
13
+ raise InvalidJoinException.new("Conditions must be provided for explicit joins using the 'on' method");
14
+ end
15
+ if join_type_string.nil?
16
+ raise ArgumentError.new("The provided join type '#{@join_type}' is invalid.")
17
+ end
18
+ predicate_sql = @join_conditions.map do |condition|
19
+ condition_to_predicate_part(condition)
13
20
  end * ' AND '
14
- "INNER JOIN #{@right_table.table_name} AS #{@right_table.table_alias} ON #{predicate_sql}"
21
+ "#{join_type_string} JOIN #{@right_table.table_name} AS #{@right_table.table_alias} ON #{predicate_sql}"
22
+ end
23
+
24
+ def requires_distinct_select?
25
+ [:left, :outer, :left_outer].include?(@join_type)
26
+ end
27
+
28
+ protected
29
+
30
+ def condition_to_predicate_part(condition)
31
+ if condition.is_a?(Hash)
32
+ "#{column_join_alias(@left_table, condition.keys[0])} = #{column_join_alias(@right_table, condition.values[0])}"
33
+ elsif condition.is_a?(RecordFilter::DSL::Restriction)
34
+ restriction_join_alias(condition)
35
+ end
36
+ end
37
+
38
+ def column_join_alias(table, column)
39
+ unless table.has_column(column)
40
+ raise ColumnNotFoundException.new("The column #{column} was not found in the table #{table.table_name}")
41
+ end
42
+ "#{table.table_alias}.#{column}"
43
+ end
44
+
45
+ def restriction_join_alias(dsl_restriction)
46
+ unless @right_table.has_column(dsl_restriction.column)
47
+ raise ColumnNotFoundException.new("The column #{dsl_restriction.column} was not found in the table #{@right_table.table_name}")
48
+ end
49
+ restriction_class = RecordFilter::Restrictions::Base.class_from_operator(dsl_restriction.operator)
50
+ restriction = restriction_class.new(
51
+ "#{@right_table.table_alias}.#{dsl_restriction.column}", dsl_restriction.value, :negated => dsl_restriction.negated)
52
+ @right_table.model_class.merge_conditions(restriction.to_conditions)
53
+ end
54
+
55
+ def join_type_string
56
+ @join_type_string ||= case(@join_type)
57
+ when :inner then 'INNER'
58
+ when :left then 'LEFT'
59
+ when :left_outer then 'LEFT OUTER'
60
+ when :outer then 'OUTER'
61
+ else nil
62
+ end
15
63
  end
16
64
  end
17
65
  end
@@ -22,7 +22,7 @@ module RecordFilter
22
22
  raise ColumnNotFoundException.new("The column #{column} was not found in #{table.table_name}.")
23
23
  end
24
24
 
25
- "#{table.table_name}.#{column} #{dir}"
25
+ "#{table.table_alias}.#{column} #{dir}"
26
26
  end
27
27
  end
28
28
  end
@@ -6,10 +6,17 @@ module RecordFilter
6
6
  @conjunction = RecordFilter::Conjunctions::Base.create_from(dsl_conjunction, @table)
7
7
  end
8
8
 
9
- def to_find_params
9
+ def to_find_params(count_query=false)
10
10
  params = { :conditions => @conjunction.to_conditions }
11
11
  joins = @table.all_joins
12
12
  params[:joins] = joins.map { |join| join.to_sql } * ' ' unless joins.empty?
13
+ if (joins.any? { |j| j.requires_distinct_select? })
14
+ if count_query
15
+ params[:select] = "DISTINCT #{@table.model_class.quoted_table_name}.#{@table.model_class.primary_key}"
16
+ else
17
+ params[:select] = "DISTINCT #{@table.model_class.quoted_table_name}.*"
18
+ end
19
+ end
13
20
  orders = @table.orders
14
21
  params[:order] = orders.map { |order| order.to_sql } * ', ' unless orders.empty?
15
22
  group_bys = @table.group_bys
@@ -15,7 +15,11 @@ module RecordFilter
15
15
  end
16
16
 
17
17
  def to_negative_sql
18
- "!(#{to_positive_sql})"
18
+ "NOT (#{to_positive_sql})"
19
+ end
20
+
21
+ def self.class_from_operator(operator)
22
+ "RecordFilter::Restrictions::#{operator.to_s.camelize}".constantize
19
23
  end
20
24
  end
21
25
 
@@ -25,7 +29,7 @@ module RecordFilter
25
29
  end
26
30
 
27
31
  def to_negative_sql
28
- "#{@column_name} != ?"
32
+ "#{@column_name} <> ?"
29
33
  end
30
34
  end
31
35
 
@@ -1,6 +1,6 @@
1
1
  module RecordFilter
2
2
  class Table
3
- attr_reader :table_alias, :orders, :group_bys
3
+ attr_reader :table_alias, :orders, :group_bys, :model_class
4
4
 
5
5
  def initialize(model_class, table_alias = nil)
6
6
  @model_class = model_class
@@ -16,7 +16,7 @@ module RecordFilter
16
16
  @model_class.quoted_table_name
17
17
  end
18
18
 
19
- def join_association(association_name)
19
+ def join_association(association_name, join_type=nil)
20
20
  @joins_cache[association_name] ||=
21
21
  begin
22
22
  association = @model_class.reflect_on_association(association_name)
@@ -25,13 +25,22 @@ module RecordFilter
25
25
  end
26
26
  case association.macro
27
27
  when :belongs_to, :has_many, :has_one
28
- simple_join(association)
28
+ simple_join(association, join_type)
29
29
  when :has_and_belongs_to_many
30
- compound_join(association)
30
+ compound_join(association, join_type)
31
31
  end
32
32
  end
33
33
  end
34
34
 
35
+ def join_class(clazz, join_type, table_alias, conditions)
36
+ @joins_cache[clazz] ||=
37
+ begin
38
+ join_table = Table.new(clazz, table_alias || alias_for_class(clazz))
39
+ @joins << (join = Join.new(self, join_table, conditions, join_type))
40
+ join
41
+ end
42
+ end
43
+
35
44
  def all_joins
36
45
  @joins + @joins.inject([]) do |child_joins, join|
37
46
  child_joins.concat(join.right_table.all_joins)
@@ -53,27 +62,31 @@ module RecordFilter
53
62
 
54
63
  private
55
64
 
56
- def simple_join(association)
65
+ def simple_join(association, join_type)
57
66
  join_predicate =
58
67
  case association.macro
59
68
  when :belongs_to
60
- { association.primary_key_name.to_sym => :id }
69
+ [{ association.primary_key_name.to_sym => :id }]
61
70
  when :has_many, :has_one
62
- { :id => association.primary_key_name.to_sym }
71
+ [{ :id => association.primary_key_name.to_sym }]
63
72
  end
73
+
74
+ if association.options[:as]
75
+ join_predicate << DSL::Restriction.new(association.options[:as].to_s + '_type').equal_to(@model_class.name)
76
+ end
64
77
  join_table = Table.new(association.klass, alias_for_association(association))
65
- @joins << join = Join.new(self, join_table, join_predicate)
78
+ @joins << join = Join.new(self, join_table, join_predicate, join_type)
66
79
  join
67
80
  end
68
81
 
69
- def compound_join(association)
70
- pivot_join_predicate = { :id => association.primary_key_name.to_sym }
82
+ def compound_join(association, join_type)
83
+ pivot_join_predicate = [{ :id => association.primary_key_name.to_sym }]
71
84
  table_name = @model_class.connection.quote_table_name(association.options[:join_table])
72
- pivot_table = PivotTable.new(table_name, "__#{alias_for_association(association)}")
73
- pivot_join = Join.new(self, pivot_table, pivot_join_predicate)
74
- join_predicate = { association.association_foreign_key => :id }
85
+ pivot_table = PivotTable.new(table_name, association, "__#{alias_for_association(association)}")
86
+ pivot_join = Join.new(self, pivot_table, pivot_join_predicate, join_type)
87
+ join_predicate = [{ association.association_foreign_key.to_sym => :id }]
75
88
  join_table = Table.new(association.klass, alias_for_association(association))
76
- pivot_table.joins << join = Join.new(pivot_table, join_table, join_predicate)
89
+ pivot_table.joins << join = Join.new(pivot_table, join_table, join_predicate, join_type)
77
90
  @joins << pivot_join
78
91
  join
79
92
  end
@@ -83,17 +96,25 @@ module RecordFilter
83
96
  def alias_for_association(association)
84
97
  "#{@aliased ? @table_alias.to_s : @model_class.table_name}__#{association.name}"
85
98
  end
99
+
100
+ alias_method :alias_for_class, :alias_for_association
86
101
  end
87
102
 
88
103
  class PivotTable < Table
89
104
  attr_reader :table_name, :joins
90
105
 
91
- def initialize(table_name, table_alias = table_name)
106
+ def initialize(table_name, association, table_alias = table_name)
92
107
  @table_name, @table_alias = table_name, table_alias
93
108
  @joins_cache = {}
94
109
  @joins = []
95
110
  @orders = []
96
111
  @group_bys = []
112
+ @primary_key = association.primary_key_name.to_sym
113
+ @foreign_key = association.association_foreign_key.to_sym
114
+ end
115
+
116
+ def has_column(column_name)
117
+ [@primary_key, @foreign_key].include?(column_name.to_sym)
97
118
  end
98
119
  end
99
120
  end
@@ -23,10 +23,10 @@ describe 'raising exceptions' do
23
23
  }.should raise_error(RecordFilter::ColumnNotFoundException)
24
24
  end
25
25
 
26
- it 'should get ColumnNotFoundException for without' do
26
+ it 'should get ColumnNotFoundException for with.not' do
27
27
  lambda {
28
28
  Post.filter do
29
- without(:this_is_not_there, 2)
29
+ with(:this_is_not_there).not.equal_to(2)
30
30
  end.inspect
31
31
  }.should raise_error(RecordFilter::ColumnNotFoundException)
32
32
  end
@@ -46,5 +46,57 @@ describe 'raising exceptions' do
46
46
  end.inspect
47
47
  }.should raise_error(RecordFilter::AssociationNotFoundException)
48
48
  end
49
+
50
+ it 'should raise ColumnNotFoundException for explicit joins on bad column names for the right table' do
51
+ lambda {
52
+ Review.filter do
53
+ join(Feature, :left) do
54
+ on(:reviewable_id => :ftrable_id)
55
+ on(:reviewable_type => :ftrable_type)
56
+ with(:priority, 5)
57
+ end
58
+ end.inspect
59
+ }.should raise_error(RecordFilter::ColumnNotFoundException)
60
+ end
61
+
62
+ it 'should raise ColumnNotFoundException for explicit joins on bad column names for the left table' do
63
+ lambda {
64
+ Review.filter do
65
+ join(Feature, :inner) do
66
+ on(:rvwable_id => :featurable_id)
67
+ on(:rvwable_type => :featurable_type)
68
+ with(:priority, 5)
69
+ end
70
+ end.inspect
71
+ }.should raise_error(RecordFilter::ColumnNotFoundException)
72
+ end
73
+
74
+ it 'should raise ColumnNotFoundException for explicit joins on bad column names in conditions' do
75
+ lambda {
76
+ Review.filter do
77
+ join(Feature, :inner) do
78
+ on(:reviewable_id).gt(12)
79
+ end
80
+ end.inspect
81
+ }.should raise_error(RecordFilter::ColumnNotFoundException)
82
+ end
83
+
84
+ it 'should raise an ArgumentError if an invalid join type is specified' do
85
+ lambda {
86
+ Review.filter do
87
+ join(Feature, :crazy) do
88
+ on(:reviewable_type => :featurable_type)
89
+ end
90
+ end.inspect
91
+ }.should raise_error(ArgumentError)
92
+ end
93
+
94
+ it 'should raise an InvalidJoinException if no columns are specified for the join' do
95
+ lambda {
96
+ Review.filter do
97
+ join(Feature, :inner)
98
+ end.inspect
99
+ }.should raise_error(RecordFilter::InvalidJoinException)
100
+ end
49
101
  end
50
102
  end
@@ -0,0 +1,106 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'explicit joins' do
4
+ before do
5
+ TestModel.extended_models.each { |model| model.last_find = {} }
6
+ end
7
+
8
+ describe 'specifying a simple join' do
9
+ before do
10
+ Post.filter do
11
+ join(Blog, :left, :posts_blogs) do
12
+ on(:blog_id => :id)
13
+ with(:name, 'Test Name')
14
+ end
15
+ end.inspect
16
+ end
17
+
18
+ it 'should add correct join' do
19
+ Post.last_find[:joins].should == %q(LEFT JOIN "blogs" AS posts_blogs ON "posts".blog_id = posts_blogs.id)
20
+ end
21
+
22
+ it 'should query against condition on join table' do
23
+ Post.last_find[:conditions].should == ['posts_blogs.name = ?', 'Test Name']
24
+ end
25
+ end
26
+
27
+ describe 'specifying a complex join through polymorphic associations' do
28
+ before do
29
+ Review.filter do
30
+ join(Feature, :left, :reviews_features) do
31
+ on(:reviewable_id => :featurable_id)
32
+ on(:reviewable_type => :featurable_type)
33
+ with(:priority, 5)
34
+ end
35
+ end.inspect
36
+ end
37
+
38
+ it 'should add correct join' do
39
+ Review.last_find[:joins].should == %q(LEFT JOIN "features" AS reviews_features ON "reviews".reviewable_id = reviews_features.featurable_id AND "reviews".reviewable_type = reviews_features.featurable_type)
40
+ end
41
+
42
+ it 'should query against condition on join table' do
43
+ Review.last_find[:conditions].should == ['reviews_features.priority = ?', 5]
44
+ end
45
+ end
46
+
47
+ describe 'should use values as join parameters instead of columns if given' do
48
+ before do
49
+ Review.filter do
50
+ join(Feature, :left) do
51
+ on(:reviewable_id => :featurable_id)
52
+ on(:reviewable_type => :featurable_type)
53
+ on(:featurable_type, 'SomeType')
54
+ with(:priority, 5)
55
+ end
56
+ end.inspect
57
+ end
58
+
59
+ it 'should add correct join' do
60
+ Review.last_find[:joins].should == %q(LEFT JOIN "features" AS reviews__Feature ON "reviews".reviewable_id = reviews__Feature.featurable_id AND "reviews".reviewable_type = reviews__Feature.featurable_type AND (reviews__Feature.featurable_type = 'SomeType'))
61
+ end
62
+ end
63
+
64
+ describe 'using restrictions on join conditions' do
65
+ before do
66
+ Review.filter do
67
+ join(Feature, :left) do
68
+ on(:featurable_type, nil)
69
+ on(:featurable_id).gte(12)
70
+ on(:priority).not(6)
71
+ end
72
+ end.inspect
73
+ end
74
+
75
+ it 'should add the correct join' do
76
+ Review.last_find[:joins].should == %q(LEFT JOIN "features" AS reviews__Feature ON (reviews__Feature.featurable_type IS NULL) AND (reviews__Feature.featurable_id >= 12) AND (reviews__Feature.priority <> 6))
77
+ end
78
+ end
79
+
80
+ describe 'using implicit and explicit joins together with conditions' do
81
+ before do
82
+ Blog.named_filter :somethings do
83
+ having(:ads) do
84
+ with(:content, nil)
85
+ end
86
+ join(Post, :left) do
87
+ on(:id => :blog_id)
88
+ join(Comment, :inner) do
89
+ on(:id => :post_id)
90
+ on(:offensive, true)
91
+ end
92
+ end
93
+ group_by(:id)
94
+ end
95
+ Blog.somethings.inspect
96
+ end
97
+
98
+ it 'should produce the correct conditions' do
99
+ Blog.last_find[:conditions].should == [%q((blogs__ads.content IS NULL))]
100
+ end
101
+
102
+ it 'should produce the correct join' do
103
+ Blog.last_find[:joins].should == %q(INNER JOIN "ads" AS blogs__ads ON "blogs".id = blogs__ads.blog_id LEFT JOIN "posts" AS blogs__Post ON "blogs".id = blogs__Post.blog_id INNER JOIN "comments" AS blogs__Post__Comment ON blogs__Post.id = blogs__Post__Comment.post_id AND (blogs__Post__Comment.offensive = 't'))
104
+ end
105
+ end
106
+ end
@@ -186,19 +186,19 @@ describe 'implicit joins' do
186
186
  describe 'with negated conditions' do
187
187
  before do
188
188
  Comment.filter do
189
- without :offensive, false
189
+ with(:offensive).not(false)
190
190
  end.inspect
191
191
  end
192
192
 
193
193
  it 'should create the correct condition' do
194
- Comment.last_find[:conditions].should == [%q("comments".offensive != ?), false]
194
+ Comment.last_find[:conditions].should == [%q("comments".offensive <> ?), false]
195
195
  end
196
196
  end
197
197
 
198
198
  describe 'with negated nil conditions' do
199
199
  before do
200
200
  Comment.filter do
201
- without :contents, nil
201
+ with(:contents).not(nil)
202
202
  with :offensive, true
203
203
  end.inspect
204
204
  end
@@ -207,4 +207,71 @@ describe 'implicit joins' do
207
207
  Comment.last_find[:conditions].should == [%q(("comments".contents IS NOT NULL) AND ("comments".offensive = ?)), true]
208
208
  end
209
209
  end
210
+
211
+ describe 'with negated nil conditions using is_null' do
212
+ before do
213
+ Comment.filter do
214
+ with(:contents).not.is_null
215
+ with :offensive, true
216
+ end.inspect
217
+ end
218
+
219
+ it 'should create the correct IS NOT NULL condition' do
220
+ Comment.last_find[:conditions].should == [%q(("comments".contents IS NOT NULL) AND ("comments".offensive = ?)), true]
221
+ end
222
+ end
223
+
224
+ describe 'passing the join type to having' do
225
+ before do
226
+ Blog.filter do
227
+ having(:left_outer, :posts) do
228
+ with(:permalink, 'ack')
229
+ end
230
+ end.inspect
231
+ end
232
+
233
+ it 'should create the correct condition' do
234
+ Blog.last_find[:conditions].should == [%q(blogs__posts.permalink = ?), 'ack']
235
+ end
236
+
237
+ it 'should create the correct join' do
238
+ Blog.last_find[:joins].should == %q(LEFT OUTER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id)
239
+ end
240
+ end
241
+
242
+ describe 'passing the join type to having with multiple joins' do
243
+ before do
244
+ Blog.filter do
245
+ having(:left_outer, :posts => :comments) do
246
+ with(:offensive, true)
247
+ end
248
+ end.inspect
249
+ end
250
+
251
+ it 'should create the correct condition' do
252
+ Blog.last_find[:conditions].should == [%q(blogs__posts__comments.offensive = ?), true]
253
+ end
254
+
255
+ it 'should create the correct join' do
256
+ Blog.last_find[:joins].should == %q(LEFT OUTER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id LEFT OUTER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id)
257
+ end
258
+ end
259
+
260
+ describe 'on polymorphic associations' do
261
+ before do
262
+ Post.filter do
263
+ having(:reviews) do
264
+ with(:stars_count, 3)
265
+ end
266
+ end.inspect
267
+ end
268
+
269
+ it 'should create the correct condition' do
270
+ Post.last_find[:conditions].should == [%q(posts__reviews.stars_count = ?), 3]
271
+ end
272
+
273
+ it 'should create the correct join' do
274
+ Post.last_find[:joins].should == %q(INNER JOIN "reviews" AS posts__reviews ON "posts".id = posts__reviews.reviewable_id AND (posts__reviews.reviewable_type = 'Post'))
275
+ end
276
+ end
210
277
  end
@@ -115,7 +115,7 @@ describe 'filter qualifiers' do
115
115
  end
116
116
 
117
117
  it 'should add the limit to the parameters' do
118
- Post.last_find[:order].should == %q("photos".path DESC, "posts".permalink ASC)
118
+ Post.last_find[:order].should == %q(posts__photo.path DESC, "posts".permalink ASC)
119
119
  end
120
120
  end
121
121
 
@@ -184,7 +184,7 @@ describe 'filter qualifiers' do
184
184
  group_by(:created_at)
185
185
  group_by(:photo => :format)
186
186
  end.inspect
187
- Post.last_find[:group].should == %q("posts".created_at, "photos".format)
187
+ Post.last_find[:group].should == %q("posts".created_at, posts__photo.format)
188
188
  end
189
189
  end
190
190
  end
data/spec/models/blog.rb CHANGED
@@ -2,4 +2,5 @@ class Blog < ActiveRecord::Base
2
2
  extend TestModel
3
3
 
4
4
  has_many :posts
5
+ has_many :ads
5
6
  end
@@ -0,0 +1,5 @@
1
+ class Feature < ActiveRecord::Base
2
+ extend TestModel
3
+
4
+ belongs_to :featurable, :polymorphic => true
5
+ end
data/spec/models/post.rb CHANGED
@@ -5,4 +5,6 @@ class Post < ActiveRecord::Base
5
5
  has_many :comments
6
6
  has_one :photo
7
7
  has_and_belongs_to_many :tags
8
+ has_many :features, :as => :featurable
9
+ has_many :reviews, :as => :reviewable
8
10
  end
@@ -0,0 +1,5 @@
1
+ class Review < ActiveRecord::Base
2
+ extend TestModel
3
+
4
+ belongs_to :reviewable, :polymorphic => true
5
+ end
@@ -72,7 +72,7 @@ describe 'RecordFilter restrictions' do
72
72
  with(:permalink, 'eek')
73
73
  end
74
74
  end.inspect
75
- Post.last_find.should == { :conditions => [%q{!(("posts".blog_id = ?) OR ("posts".permalink = ?))}, 1, 'eek'] }
75
+ Post.last_find.should == { :conditions => [%q{NOT (("posts".blog_id = ?) OR ("posts".permalink = ?))}, 1, 'eek'] }
76
76
  end
77
77
 
78
78
  it 'should filter by not_all_of' do
@@ -82,7 +82,7 @@ describe 'RecordFilter restrictions' do
82
82
  with(:permalink, 'eek')
83
83
  end
84
84
  end.inspect
85
- Post.last_find.should == { :conditions => [%q{!(("posts".blog_id = ?) AND ("posts".permalink = ?))}, 1, 'eek'] }
85
+ Post.last_find.should == { :conditions => [%q{NOT (("posts".blog_id = ?) AND ("posts".permalink = ?))}, 1, 'eek'] }
86
86
  end
87
87
 
88
88
  it 'should filter by disjunction' do
@@ -128,8 +128,15 @@ describe 'RecordFilter restrictions' do
128
128
 
129
129
  it 'should support NOT LIKE' do
130
130
  Post.filter do
131
- without(:permalink).like('%ostriches%')
131
+ with(:permalink).not.like('%ostriches%')
132
132
  end.inspect
133
133
  Post.last_find.should == { :conditions => [%q("posts".permalink NOT LIKE ?), '%ostriches%'] }
134
134
  end
135
+
136
+ it 'should provide access to the filter class in the filter' do
137
+ Post.filter do
138
+ with(:permalink).not.equal_to(filter_class.name)
139
+ end.inspect
140
+ Post.last_find.should == { :conditions => [%q("posts".permalink <> ?), 'Post'] }
141
+ end
135
142
  end
data/spec/spec_helper.rb CHANGED
@@ -15,6 +15,7 @@ module TestModel
15
15
 
16
16
  def scoped(params = {})
17
17
  @last_find = params
18
+ super
18
19
  end
19
20
 
20
21
  def self.extended(base)
data/spec/test.db CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: outoftime-record_filter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mat Brown
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-04-20 00:00:00 -07:00
13
+ date: 2009-04-22 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -29,11 +29,14 @@ files:
29
29
  - lib/record_filter/active_record.rb
30
30
  - lib/record_filter/conjunctions.rb
31
31
  - lib/record_filter/dsl.rb
32
+ - lib/record_filter/dsl/class_join.rb
32
33
  - lib/record_filter/dsl/conjunction.rb
33
34
  - lib/record_filter/dsl/conjunction_dsl.rb
34
35
  - lib/record_filter/dsl/dsl.rb
35
36
  - lib/record_filter/dsl/group_by.rb
36
37
  - lib/record_filter/dsl/join.rb
38
+ - lib/record_filter/dsl/join_condition.rb
39
+ - lib/record_filter/dsl/join_dsl.rb
37
40
  - lib/record_filter/dsl/limit.rb
38
41
  - lib/record_filter/dsl/order.rb
39
42
  - lib/record_filter/dsl/restriction.rb
@@ -46,12 +49,15 @@ files:
46
49
  - lib/record_filter/restrictions.rb
47
50
  - lib/record_filter/table.rb
48
51
  - spec/exception_spec.rb
52
+ - spec/explicit_join_spec.rb
49
53
  - spec/implicit_join_spec.rb
50
54
  - spec/limits_and_ordering_spec.rb
51
55
  - spec/models/blog.rb
52
56
  - spec/models/comment.rb
57
+ - spec/models/feature.rb
53
58
  - spec/models/photo.rb
54
59
  - spec/models/post.rb
60
+ - spec/models/review.rb
55
61
  - spec/models/tag.rb
56
62
  - spec/named_filter_spec.rb
57
63
  - spec/restrictions_spec.rb
@@ -85,12 +91,15 @@ specification_version: 3
85
91
  summary: Pure-ruby criteria API for building complex queries in ActiveRecord
86
92
  test_files:
87
93
  - spec/exception_spec.rb
94
+ - spec/explicit_join_spec.rb
88
95
  - spec/implicit_join_spec.rb
89
96
  - spec/limits_and_ordering_spec.rb
90
97
  - spec/models/blog.rb
91
98
  - spec/models/comment.rb
99
+ - spec/models/feature.rb
92
100
  - spec/models/photo.rb
93
101
  - spec/models/post.rb
102
+ - spec/models/review.rb
94
103
  - spec/models/tag.rb
95
104
  - spec/named_filter_spec.rb
96
105
  - spec/restrictions_spec.rb