aub-record_filter 0.1.2 → 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: 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