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 +1 -1
- data/lib/record_filter.rb +3 -2
- data/lib/record_filter/active_record.rb +6 -0
- data/lib/record_filter/conjunctions.rb +49 -21
- data/lib/record_filter/dsl.rb +1 -1
- data/lib/record_filter/dsl/class_join.rb +16 -0
- data/lib/record_filter/dsl/conjunction.rb +16 -18
- data/lib/record_filter/dsl/conjunction_dsl.rb +23 -10
- data/lib/record_filter/dsl/dsl.rb +1 -1
- data/lib/record_filter/dsl/join_condition.rb +21 -0
- data/lib/record_filter/dsl/join_dsl.rb +14 -0
- data/lib/record_filter/dsl/restriction.rb +32 -3
- data/lib/record_filter/filter.rb +1 -1
- data/lib/record_filter/join.rb +47 -6
- data/lib/record_filter/restrictions.rb +15 -0
- data/lib/record_filter/table.rb +24 -7
- data/spec/exception_spec.rb +54 -2
- data/spec/explicit_join_spec.rb +79 -0
- data/spec/implicit_join_spec.rb +34 -2
- data/spec/models/feature.rb +5 -0
- data/spec/models/post.rb +2 -0
- data/spec/models/review.rb +5 -0
- data/spec/named_filter_spec.rb +21 -8
- data/spec/restrictions_spec.rb +55 -0
- data/spec/test.db +0 -0
- metadata +11 -2
data/VERSION.yml
CHANGED
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 <
|
11
|
-
class ColumnNotFoundException <
|
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 =
|
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
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
@restrictions.
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
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
|
-
|
108
|
-
|
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
|
data/lib/record_filter/dsl.rb
CHANGED
@@ -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
|
-
|
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
|
14
|
-
@steps << (restriction = Restriction.new(column,
|
15
|
-
|
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 << (
|
35
|
-
|
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
|
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=
|
13
|
-
return @conjunction.add_restriction(column, value
|
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
|
-
|
36
|
-
|
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
|
@@ -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
|
-
|
8
|
-
|
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, :
|
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
|
data/lib/record_filter/filter.rb
CHANGED
@@ -17,7 +17,7 @@ module RecordFilter
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def method_missing(method, *args, &block)
|
20
|
-
if @clazz.
|
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)
|
data/lib/record_filter/join.rb
CHANGED
@@ -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,
|
6
|
-
@left_table, @right_table, @
|
7
|
-
left_table, right_table,
|
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
|
-
|
12
|
-
"
|
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
|
-
"
|
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
|
data/lib/record_filter/table.rb
CHANGED
@@ -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
|
data/spec/exception_spec.rb
CHANGED
@@ -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
|
26
|
+
it 'should get ColumnNotFoundException for with.not' do
|
27
27
|
lambda {
|
28
28
|
Post.filter do
|
29
|
-
|
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
|
data/spec/implicit_join_spec.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/spec/models/post.rb
CHANGED
data/spec/named_filter_spec.rb
CHANGED
@@ -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
|
-
|
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)
|
data/spec/restrictions_spec.rb
CHANGED
@@ -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.
|
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-
|
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
|