aub-record_filter 0.1.2 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 2
2
+ :patch: 4
3
3
  :major: 0
4
4
  :minor: 1
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
@@ -7,6 +7,8 @@ module RecordFilter
7
7
  end
8
8
 
9
9
  def named_filter(name, &block)
10
+ return if named_filters.include?(name.to_sym)
11
+ named_filters << name.to_sym
10
12
  DSL::DSL::subclass(self).module_eval do
11
13
  define_method(name, &block)
12
14
  end
@@ -17,6 +19,10 @@ module RecordFilter
17
19
  end
18
20
  end
19
21
  end
22
+
23
+ def named_filters
24
+ read_inheritable_attribute(:named_filters) || write_inheritable_attribute(:named_filters, [])
25
+ end
20
26
  end
21
27
  end
22
28
  end
@@ -7,6 +7,8 @@ module RecordFilter
7
7
  result = case dsl_conjunction.type
8
8
  when :any_of then AnyOf.new(table)
9
9
  when :all_of then AllOf.new(table)
10
+ when :none_of then NoneOf.new(table)
11
+ when :not_all_of then NotAllOf.new(table)
10
12
  end
11
13
 
12
14
  dsl_conjunction.steps.each do |step|
@@ -18,6 +20,10 @@ module RecordFilter
18
20
  when DSL::Join
19
21
  join = result.add_join_on_association(step.association)
20
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)
26
+ result.add_conjunction(create_from(step.conjunction, join.right_table))
21
27
  when DSL::Limit
22
28
  result.add_limit_and_offset(step.limit, step.offset)
23
29
  when DSL::Order
@@ -38,7 +44,7 @@ module RecordFilter
38
44
 
39
45
  def add_restriction(column_name, operator, value, options={})
40
46
  check_column_exists!(column_name)
41
- restriction_class = "RecordFilter::Restrictions::#{operator.to_s.camelize}".constantize
47
+ restriction_class = RecordFilter::Restrictions::Base.class_from_operator(operator)
42
48
  restriction = restriction_class.new("#{@table_name}.#{column_name}", value, options)
43
49
  self << restriction
44
50
  restriction
@@ -50,9 +56,19 @@ module RecordFilter
50
56
  end
51
57
 
52
58
  def add_join_on_association(association_name)
53
- @table.join_association(association_name)
59
+ table = @table
60
+ while association_name.is_a?(Hash)
61
+ result = table.join_association(association_name.keys[0])
62
+ table = result.right_table
63
+ association_name = association_name.values[0]
64
+ end
65
+ table.join_association(association_name)
54
66
  end
55
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
+
56
72
  def add_order(column_name, direction)
57
73
  @table.order_column(column_name, direction)
58
74
  end
@@ -70,21 +86,25 @@ module RecordFilter
70
86
  end
71
87
 
72
88
  def to_conditions
73
- if @restrictions.empty?
74
- nil
75
- elsif @restrictions.length == 1
76
- @restrictions.first.to_conditions
77
- else
78
- @restrictions.map do |restriction|
79
- conditions = restriction.to_conditions
80
- conditions[0] = "(#{conditions[0]})"
81
- conditions
82
- end.inject do |conditions, new_conditions|
83
- conditions.first << " #{conjunctor} #{new_conditions.shift}"
84
- conditions.concat(new_conditions)
85
- conditions
89
+ result = begin
90
+ if @restrictions.empty?
91
+ nil
92
+ elsif @restrictions.length == 1
93
+ @restrictions.first.to_conditions
94
+ else
95
+ @restrictions.map do |restriction|
96
+ conditions = restriction.to_conditions
97
+ conditions[0] = "(#{conditions[0]})"
98
+ conditions
99
+ end.inject do |conditions, new_conditions|
100
+ conditions.first << " #{conjunctor} #{new_conditions.shift}"
101
+ conditions.concat(new_conditions)
102
+ conditions
103
+ end
86
104
  end
87
105
  end
106
+ result[0] = "!(#{result[0]})" if (negated && !result.nil? && !result[0].nil?)
107
+ result
88
108
  end
89
109
 
90
110
  protected
@@ -97,15 +117,23 @@ module RecordFilter
97
117
  end
98
118
 
99
119
  class AnyOf < Base
100
- def conjunctor
101
- 'OR'
102
- end
120
+ def conjunctor; 'OR'; end
121
+ def negated; false; end
103
122
  end
104
123
 
105
124
  class AllOf < Base
106
- def conjunctor
107
- 'AND'
108
- end
125
+ def conjunctor; 'AND'; end
126
+ def negated; false; end
127
+ end
128
+
129
+ class NoneOf < Base
130
+ def conjunctor; 'OR'; end
131
+ def negated; true; end
132
+ end
133
+
134
+ class NotAllOf < Base
135
+ def conjunctor; 'AND'; end
136
+ def negated; true; end
109
137
  end
110
138
  end
111
139
  end
@@ -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
22
  def add_join(association, &block)
32
- dsl = ConjunctionDSL.new
23
+ dsl = ConjunctionDSL.new(@model_class, Conjunction.new(@model_class, :all_of))
24
+ dsl.instance_eval(&block) if block
25
+ @steps << Join.new(association, 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
@@ -30,10 +26,27 @@ module RecordFilter
30
26
  nil
31
27
  end
32
28
 
29
+ def none_of(&block)
30
+ @conjunction.add_conjunction(:none_of, &block)
31
+ nil
32
+ end
33
+
34
+ def not_all_of(&block)
35
+ @conjunction.add_conjunction(:not_all_of, &block)
36
+ nil
37
+ end
38
+
33
39
  # join
34
40
  def having(association, &block)
35
- join = @conjunction.add_join(association, &block)
36
- ConjunctionDSL.new(join.conjunction)
41
+ @conjunction.add_join(association, &block)
42
+ end
43
+
44
+ def join(clazz, join_type, table_alias=nil, &block)
45
+ @conjunction.add_class_join(clazz, join_type, table_alias, &block)
46
+ end
47
+
48
+ def filter_class
49
+ @model_class
37
50
  end
38
51
  end
39
52
  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)
@@ -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,11 +4,20 @@ 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
- [:equal_to, :is_null, :less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to, :in, :between].each do |operator|
20
+ [:equal_to, :is_null, :less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to, :in, :like].each do |operator|
12
21
  define_method(operator) do |*args|
13
22
  @value = args[0]
14
23
  @operator = operator
@@ -16,12 +25,32 @@ module RecordFilter
16
25
  end
17
26
  end
18
27
 
28
+ # Between can take either a tuple of [start, finish], a range, or two values.
29
+ def between(start, finish=nil)
30
+ @operator = :between
31
+ if !finish.nil?
32
+ @value = [start, finish]
33
+ else
34
+ @value = start
35
+ end
36
+ end
37
+
19
38
  alias_method :gt, :greater_than
20
39
  alias_method :gte, :greater_than_or_equal_to
21
40
  alias_method :lt, :less_than
22
41
  alias_method :lte, :less_than_or_equal_to
23
42
  alias_method :null, :is_null
24
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
25
54
  end
26
55
  end
27
56
  end
@@ -17,7 +17,7 @@ module RecordFilter
17
17
  end
18
18
 
19
19
  def method_missing(method, *args, &block)
20
- if @clazz.respond_to?(method) # UGLY, we need to only pass through things that are named filters.
20
+ if @clazz.named_filters.include?(method)
21
21
  Filter.new(@clazz, method, @dsl.conjunction, *args)
22
22
  else
23
23
  loaded_data.send(method, *args, &block)
@@ -2,16 +2,57 @@ 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=:inner)
6
+ @left_table, @right_table, @join_conditions, @join_type =
7
+ left_table, right_table, join_conditions, join_type
8
8
  end
9
9
 
10
10
  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}"
11
+ if @join_conditions.blank?
12
+ raise InvalidJoinException.new("Conditions must be provided for explicit joins using the 'on' method");
13
+ end
14
+ if join_type_string.nil?
15
+ raise ArgumentError.new("The provided join type '#{@join_type}' is invalid.")
16
+ end
17
+ predicate_sql = @join_conditions.map do |condition|
18
+ condition_to_predicate_part(condition)
13
19
  end * ' AND '
14
- "INNER JOIN #{@right_table.table_name} AS #{@right_table.table_alias} ON #{predicate_sql}"
20
+ "#{join_type_string} JOIN #{@right_table.table_name} AS #{@right_table.table_alias} ON #{predicate_sql}"
21
+ end
22
+
23
+ protected
24
+
25
+ def condition_to_predicate_part(condition)
26
+ if condition.is_a?(Hash)
27
+ "#{column_join_alias(@left_table, condition.keys[0])} = #{column_join_alias(@right_table, condition.values[0])}"
28
+ elsif condition.is_a?(RecordFilter::DSL::Restriction)
29
+ restriction_join_alias(condition)
30
+ end
31
+ end
32
+
33
+ def column_join_alias(table, column)
34
+ unless table.has_column(column)
35
+ raise ColumnNotFoundException.new("The column #{column} was not found in the table #{table.table_name}")
36
+ end
37
+ "#{table.table_alias}.#{column}"
38
+ end
39
+
40
+ def restriction_join_alias(dsl_restriction)
41
+ unless @right_table.has_column(dsl_restriction.column)
42
+ raise ColumnNotFoundException.new("The column #{dsl_restriction.column} was not found in the table #{@right_table.table_name}")
43
+ end
44
+ restriction_class = RecordFilter::Restrictions::Base.class_from_operator(dsl_restriction.operator)
45
+ restriction = restriction_class.new(
46
+ "#{@right_table.table_alias}.#{dsl_restriction.column}", dsl_restriction.value, :negated => dsl_restriction.negated)
47
+ @right_table.model_class.merge_conditions(restriction.to_conditions)
48
+ end
49
+
50
+ def join_type_string
51
+ @join_type_string ||= case(@join_type)
52
+ when :inner then 'INNER'
53
+ when :left then 'LEFT'
54
+ else nil
55
+ end
15
56
  end
16
57
  end
17
58
  end
@@ -3,6 +3,7 @@ module RecordFilter
3
3
  class Base
4
4
  def initialize(column_name, value, options={})
5
5
  @column_name, @value, @negated = column_name, value, !!options.delete(:negated)
6
+ @value = @value.id if @value.kind_of?(ActiveRecord::Base)
6
7
  end
7
8
 
8
9
  def to_conditions
@@ -16,6 +17,10 @@ module RecordFilter
16
17
  def to_negative_sql
17
18
  "!(#{to_positive_sql})"
18
19
  end
20
+
21
+ def self.class_from_operator(operator)
22
+ "RecordFilter::Restrictions::#{operator.to_s.camelize}".constantize
23
+ end
19
24
  end
20
25
 
21
26
  class EqualTo < Base
@@ -77,5 +82,15 @@ module RecordFilter
77
82
  ["#{@column_name} #{'NOT ' if @negated}BETWEEN ? AND ?", @value.first, @value.last]
78
83
  end
79
84
  end
85
+
86
+ class Like < Base
87
+ def to_positive_sql
88
+ "#{@column_name} LIKE ?"
89
+ end
90
+
91
+ def to_negative_sql
92
+ "#{@column_name} NOT LIKE ?"
93
+ end
94
+ end
80
95
  end
81
96
  end
@@ -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
@@ -32,6 +32,15 @@ module RecordFilter
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)
@@ -57,9 +66,9 @@ module RecordFilter
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
64
73
  join_table = Table.new(association.klass, alias_for_association(association))
65
74
  @joins << join = Join.new(self, join_table, join_predicate)
@@ -67,11 +76,11 @@ module RecordFilter
67
76
  end
68
77
 
69
78
  def compound_join(association)
70
- pivot_join_predicate = { :id => association.primary_key_name.to_sym }
79
+ pivot_join_predicate = [{ :id => association.primary_key_name.to_sym }]
71
80
  table_name = @model_class.connection.quote_table_name(association.options[:join_table])
72
- pivot_table = PivotTable.new(table_name, "__#{alias_for_association(association)}")
81
+ pivot_table = PivotTable.new(table_name, association, "__#{alias_for_association(association)}")
73
82
  pivot_join = Join.new(self, pivot_table, pivot_join_predicate)
74
- join_predicate = { association.association_foreign_key => :id }
83
+ join_predicate = [{ association.association_foreign_key.to_sym => :id }]
75
84
  join_table = Table.new(association.klass, alias_for_association(association))
76
85
  pivot_table.joins << join = Join.new(pivot_table, join_table, join_predicate)
77
86
  @joins << pivot_join
@@ -83,17 +92,25 @@ module RecordFilter
83
92
  def alias_for_association(association)
84
93
  "#{@aliased ? @table_alias.to_s : @model_class.table_name}__#{association.name}"
85
94
  end
95
+
96
+ alias_method :alias_for_class, :alias_for_association
86
97
  end
87
98
 
88
99
  class PivotTable < Table
89
100
  attr_reader :table_name, :joins
90
101
 
91
- def initialize(table_name, table_alias = table_name)
102
+ def initialize(table_name, association, table_alias = table_name)
92
103
  @table_name, @table_alias = table_name, table_alias
93
104
  @joins_cache = {}
94
105
  @joins = []
95
106
  @orders = []
96
107
  @group_bys = []
108
+ @primary_key = association.primary_key_name.to_sym
109
+ @foreign_key = association.association_foreign_key.to_sym
110
+ end
111
+
112
+ def has_column(column_name)
113
+ [@primary_key, @foreign_key].include?(column_name.to_sym)
97
114
  end
98
115
  end
99
116
  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,79 @@
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
+ end
@@ -93,6 +93,25 @@ describe 'implicit joins' do
93
93
  end
94
94
  end
95
95
 
96
+ describe 'with one having statement expressing multiple joins' do
97
+ before do
98
+ Blog.filter do
99
+ having(:posts => :comments) do
100
+ with :offensive, true
101
+ end
102
+ end.inspect
103
+ end
104
+
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)
108
+ end
109
+
110
+ it 'should query against both conditions' do
111
+ Blog.last_find[:conditions].should == [%q(blogs__posts__comments.offensive = ?), true]
112
+ end
113
+ end
114
+
96
115
  describe 'on has_one' do
97
116
  before do
98
117
  Post.filter do
@@ -167,7 +186,7 @@ describe 'implicit joins' do
167
186
  describe 'with negated conditions' do
168
187
  before do
169
188
  Comment.filter do
170
- without :offensive, false
189
+ with(:offensive).not(false)
171
190
  end.inspect
172
191
  end
173
192
 
@@ -179,7 +198,20 @@ describe 'implicit joins' do
179
198
  describe 'with negated nil conditions' do
180
199
  before do
181
200
  Comment.filter do
182
- without :contents, nil
201
+ with(:contents).not(nil)
202
+ with :offensive, true
203
+ end.inspect
204
+ end
205
+
206
+ it 'should create the correct IS NOT NULL condition' do
207
+ Comment.last_find[:conditions].should == [%q(("comments".contents IS NOT NULL) AND ("comments".offensive = ?)), true]
208
+ end
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
183
215
  with :offensive, true
184
216
  end.inspect
185
217
  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
@@ -54,8 +54,22 @@ describe 'named filters' do
54
54
  end
55
55
  end
56
56
 
57
+ describe 'taking active_record objects as arguments' do
58
+ it 'should use the id of the object as the actual parameter' do
59
+ Post.named_filter(:with_ar_arg) do |blog|
60
+ with(:blog_id, blog)
61
+ end
62
+ blog = Blog.create
63
+ Post.with_ar_arg(blog).inspect
64
+ Post.last_find[:conditions].should == [%q("posts".blog_id = ?), blog.id]
65
+ end
66
+ end
67
+
57
68
  describe 'using filters in subclasses' do
58
69
  before do
70
+ Comment.named_filter(:with_contents) do |*args|
71
+ with :contents, args[0]
72
+ end
59
73
  class NiceComment < Comment
60
74
  extend TestModel
61
75
 
@@ -63,9 +77,6 @@ describe 'named filters' do
63
77
  with :offensive, true
64
78
  end
65
79
  end
66
- Comment.named_filter(:with_contents) do |*args|
67
- with :contents, args[0]
68
- end
69
80
  end
70
81
 
71
82
  it 'should execute the parent class filters correctly' do
@@ -81,17 +92,19 @@ describe 'named filters' do
81
92
  NiceComment.offensive.with_contents('something').inspect
82
93
  NiceComment.last_find[:conditions].should == [%q(("comments".offensive = ?) AND ("comments".contents = ?)), true, 'something']
83
94
  end
95
+
96
+ it 'should provide access to the named filters' do
97
+ Comment.named_filters.should == [:with_contents]
98
+ NiceComment.named_filters.sort_by { |i| i.to_s }.should == [:offensive, :with_contents]
99
+ end
84
100
  end
85
101
 
86
102
  describe 'using compound filters' do
87
- before do
103
+ it 'should concatenate the filters correctly' do
104
+ pending 'nested chaining'
88
105
  Post.named_filter(:with_offensive_comments) do
89
106
  having(:comments).offensive(true)
90
107
  end
91
- end
92
-
93
- it 'should concatenate the filters correctly' do
94
- pending 'nested chaining'
95
108
  Post.with_offensive_comments.inspect
96
109
  Post.last_find[:conditions].should == [%q(posts__comments.offensive = ?), true]
97
110
  Post.last_find[:joins].should == %q(INNER JOIN "comments" AS posts__comments ON "comments".post_id = posts__blog.id)
@@ -51,6 +51,40 @@ describe 'RecordFilter restrictions' do
51
51
  Post.last_find.should == { :conditions => [%q{"posts".created_at BETWEEN ? AND ?}, time1, time2] }
52
52
  end
53
53
 
54
+ it 'should filter for between with two arguments passed' do
55
+ Post.filter do
56
+ with(:id).between(1, 5)
57
+ end.inspect
58
+ Post.last_find.should == { :conditions => [%q("posts".id BETWEEN ? AND ?), 1, 5] }
59
+ end
60
+
61
+ it 'should filter for between with an array passed' do
62
+ Post.filter do
63
+ with(:id).between([2, 6])
64
+ end.inspect
65
+ Post.last_find.should == { :conditions => [%q("posts".id BETWEEN ? AND ?), 2, 6] }
66
+ end
67
+
68
+ it 'should filter by none_of' do
69
+ Post.filter do
70
+ none_of do
71
+ with(:blog_id, 1)
72
+ with(:permalink, 'eek')
73
+ end
74
+ end.inspect
75
+ Post.last_find.should == { :conditions => [%q{!(("posts".blog_id = ?) OR ("posts".permalink = ?))}, 1, 'eek'] }
76
+ end
77
+
78
+ it 'should filter by not_all_of' do
79
+ Post.filter do
80
+ not_all_of do
81
+ with(:blog_id, 1)
82
+ with(:permalink, 'eek')
83
+ end
84
+ end.inspect
85
+ Post.last_find.should == { :conditions => [%q{!(("posts".blog_id = ?) AND ("posts".permalink = ?))}, 1, 'eek'] }
86
+ end
87
+
54
88
  it 'should filter by disjunction' do
55
89
  Post.filter do
56
90
  any_of do
@@ -84,4 +118,25 @@ describe 'RecordFilter restrictions' do
84
118
  Post.last_find.should == { :conditions => [%q("posts".permalink IS NULL)] }
85
119
  end
86
120
  end
121
+
122
+ it 'should support like' do
123
+ Post.filter do
124
+ with(:permalink).like('%piglets%')
125
+ end.inspect
126
+ Post.last_find.should == { :conditions => [%q("posts".permalink LIKE ?), '%piglets%'] }
127
+ end
128
+
129
+ it 'should support NOT LIKE' do
130
+ Post.filter do
131
+ with(:permalink).not.like('%ostriches%')
132
+ end.inspect
133
+ Post.last_find.should == { :conditions => [%q("posts".permalink NOT LIKE ?), '%ostriches%'] }
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
87
142
  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.1.2
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-17 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