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