record_filter 0.9.12
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/CHANGELOG +232 -0
- data/README.rdoc +354 -0
- data/Rakefile +92 -0
- data/TODO +3 -0
- data/VERSION.yml +4 -0
- data/config/roodi.yml +14 -0
- data/lib/record_filter/active_record.rb +108 -0
- data/lib/record_filter/column_parser.rb +14 -0
- data/lib/record_filter/conjunctions.rb +169 -0
- data/lib/record_filter/dsl/class_join.rb +16 -0
- data/lib/record_filter/dsl/conjunction.rb +57 -0
- data/lib/record_filter/dsl/conjunction_dsl.rb +317 -0
- data/lib/record_filter/dsl/dsl.rb +143 -0
- data/lib/record_filter/dsl/dsl_factory.rb +19 -0
- data/lib/record_filter/dsl/group_by.rb +11 -0
- data/lib/record_filter/dsl/join.rb +12 -0
- data/lib/record_filter/dsl/join_condition.rb +21 -0
- data/lib/record_filter/dsl/join_dsl.rb +49 -0
- data/lib/record_filter/dsl/limit.rb +12 -0
- data/lib/record_filter/dsl/named_filter.rb +12 -0
- data/lib/record_filter/dsl/order.rb +12 -0
- data/lib/record_filter/dsl/restriction.rb +314 -0
- data/lib/record_filter/dsl.rb +21 -0
- data/lib/record_filter/filter.rb +105 -0
- data/lib/record_filter/group_by.rb +21 -0
- data/lib/record_filter/join.rb +66 -0
- data/lib/record_filter/order.rb +27 -0
- data/lib/record_filter/query.rb +60 -0
- data/lib/record_filter/restriction_factory.rb +21 -0
- data/lib/record_filter/restrictions.rb +97 -0
- data/lib/record_filter/table.rb +172 -0
- data/lib/record_filter.rb +35 -0
- data/record_filter.gemspec +108 -0
- data/script/console +8 -0
- data/spec/active_record_spec.rb +211 -0
- data/spec/exception_spec.rb +208 -0
- data/spec/explicit_join_spec.rb +132 -0
- data/spec/implicit_join_spec.rb +403 -0
- data/spec/limits_and_ordering_spec.rb +230 -0
- data/spec/models.rb +109 -0
- data/spec/named_filter_spec.rb +264 -0
- data/spec/proxying_spec.rb +63 -0
- data/spec/restrictions_spec.rb +251 -0
- data/spec/select_spec.rb +79 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/test.db +0 -0
- data/tasks/db.rake +106 -0
- data/tasks/rcov.rake +9 -0
- data/tasks/spec.rake +10 -0
- data/test/performance_test.rb +39 -0
- data/test/test.db +0 -0
- metadata +137 -0
@@ -0,0 +1,97 @@
|
|
1
|
+
module RecordFilter
|
2
|
+
module Restrictions # :nodoc: all
|
3
|
+
class Base
|
4
|
+
def initialize(column_name, value, options={})
|
5
|
+
@column_name, @value, @negated = column_name, value, !!options.delete(:negated)
|
6
|
+
@value = @value.id if @value.kind_of?(ActiveRecord::Base)
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_conditions
|
10
|
+
@value.nil? ? [to_sql] : [to_sql, @value]
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_sql
|
14
|
+
@negated ? to_negative_sql : to_positive_sql
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_negative_sql
|
18
|
+
"NOT (#{to_positive_sql})"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class EqualTo < Base
|
23
|
+
def to_positive_sql
|
24
|
+
"#{@column_name} = ?"
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_negative_sql
|
28
|
+
"#{@column_name} <> ?"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class IsNull < Base
|
33
|
+
def to_positive_sql
|
34
|
+
"#{@column_name} IS NULL"
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_negative_sql
|
38
|
+
"#{@column_name} IS NOT NULL"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class LessThan < Base
|
43
|
+
def to_positive_sql
|
44
|
+
"#{@column_name} < #{@value.nil? ? 'NULL' : '?'}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class LessThanOrEqualTo < Base
|
49
|
+
def to_positive_sql
|
50
|
+
"#{@column_name} <= #{@value.nil? ? 'NULL' : '?'}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class GreaterThan < Base
|
55
|
+
def to_positive_sql
|
56
|
+
"#{@column_name} > #{@value.nil? ? 'NULL' : '?'}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class GreaterThanOrEqualTo < Base
|
61
|
+
def to_positive_sql
|
62
|
+
"#{@column_name} >= #{@value.nil? ? 'NULL' : '?'}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class In < Base
|
67
|
+
def to_positive_sql
|
68
|
+
"#{@column_name} IN (?)"
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_negative_sql
|
72
|
+
"#{@column_name} NOT IN (?)"
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_conditions
|
76
|
+
# Need to put in the value even if it's null in this case.
|
77
|
+
[to_sql, @value]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Between < Base
|
82
|
+
def to_conditions
|
83
|
+
["#{@column_name} #{'NOT ' if @negated}BETWEEN ? AND ?", @value.first, @value.last]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Like < Base
|
88
|
+
def to_positive_sql
|
89
|
+
"#{@column_name} LIKE ?"
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_negative_sql
|
93
|
+
"#{@column_name} NOT LIKE ?"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,172 @@
|
|
1
|
+
module RecordFilter
|
2
|
+
class Table # :nodoc: all
|
3
|
+
attr_reader :table_alias, :orders, :group_bys, :model_class
|
4
|
+
|
5
|
+
def initialize(model_class, table_alias = nil)
|
6
|
+
@model_class = model_class
|
7
|
+
@aliased = !table_alias.nil?
|
8
|
+
@table_alias = table_alias || table_name
|
9
|
+
@joins_cache = {}
|
10
|
+
@joins = []
|
11
|
+
@orders = []
|
12
|
+
@group_bys = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def table_name
|
16
|
+
@table_name ||= @model_class.quoted_table_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def join_association(association_name, options={})
|
20
|
+
association_name = association_name.to_sym
|
21
|
+
join_type = options[:join_type] || :inner
|
22
|
+
cache_key = options[:alias] || association_name
|
23
|
+
@joins_cache[cache_key] ||= begin
|
24
|
+
association = @model_class.reflect_on_association(association_name)
|
25
|
+
if association.nil?
|
26
|
+
raise AssociationNotFoundException.new("The association #{association_name} was not found on #{@model_class.name}.")
|
27
|
+
end
|
28
|
+
if (association.options[:through])
|
29
|
+
through_association = @model_class.reflect_on_association(association.options[:through])
|
30
|
+
|
31
|
+
through_join = join_association(
|
32
|
+
association.options[:through],
|
33
|
+
:join_type => join_type,
|
34
|
+
:type_restriction => association.options[:source_type],
|
35
|
+
:source => association.options[:source])
|
36
|
+
|
37
|
+
through_join.right_table.join_association(
|
38
|
+
association.options[:source] || association_name,
|
39
|
+
:join_type => join_type,
|
40
|
+
:alias => options[:alias],
|
41
|
+
:join_class => association.options[:source_type])
|
42
|
+
else
|
43
|
+
case association.macro
|
44
|
+
when :belongs_to, :has_many, :has_one
|
45
|
+
simple_join(association, join_type, options)
|
46
|
+
when :has_and_belongs_to_many
|
47
|
+
compound_join(association, join_type, options)
|
48
|
+
else raise InvalidJoinException.new("I don't know how to join on an association of type #{association.macro}.")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def join_class(clazz, join_type, table_alias, conditions)
|
55
|
+
use_alias = table_alias || alias_for_class(clazz)
|
56
|
+
@joins_cache[use_alias] ||=
|
57
|
+
begin
|
58
|
+
join_table = Table.new(clazz, use_alias)
|
59
|
+
@joins << (join = Join.new(self, join_table, conditions, join_type))
|
60
|
+
join
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def join_key_for(key)
|
65
|
+
case key
|
66
|
+
when Class then alias_for_class(key)
|
67
|
+
when String, Symbol then key
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_join(key)
|
72
|
+
@joins_cache[join_key_for(key)]
|
73
|
+
end
|
74
|
+
|
75
|
+
def find_join!(key)
|
76
|
+
join = find_join(key)
|
77
|
+
raise InvalidJoinException.new("The join #{key} was not found.") unless join
|
78
|
+
join
|
79
|
+
end
|
80
|
+
|
81
|
+
def all_joins
|
82
|
+
@joins + @joins.inject([]) do |child_joins, join|
|
83
|
+
child_joins.concat(join.right_table.all_joins)
|
84
|
+
child_joins
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def order_column(column, direction)
|
89
|
+
@orders << Order.new(column, direction, self)
|
90
|
+
end
|
91
|
+
|
92
|
+
def group_by_column(column)
|
93
|
+
@group_bys << GroupBy.new(column, self)
|
94
|
+
end
|
95
|
+
|
96
|
+
def has_column(column_name)
|
97
|
+
@model_class.column_names.include?(column_name.to_s)
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def simple_join(association, join_type, options)
|
103
|
+
join_predicate = simple_join_predicate(association)
|
104
|
+
|
105
|
+
if association.options[:as]
|
106
|
+
join_predicate << DSL::Restriction.new(association.options[:as].to_s + '_type').equal_to(association.active_record.base_class.name)
|
107
|
+
end
|
108
|
+
|
109
|
+
if options[:type_restriction] && options[:source]
|
110
|
+
foreign_type = association.klass.reflect_on_association(options[:source]).options[:foreign_type]
|
111
|
+
join_predicate << DSL::Restriction.new(foreign_type).equal_to(options[:type_restriction])
|
112
|
+
end
|
113
|
+
|
114
|
+
clazz = options[:join_class].nil? ? association.klass : options[:join_class].constantize
|
115
|
+
|
116
|
+
join_table = Table.new(clazz, options[:alias] || alias_for_association(association))
|
117
|
+
@joins << join = Join.new(self, join_table, join_predicate, join_type)
|
118
|
+
join
|
119
|
+
end
|
120
|
+
|
121
|
+
def simple_join_predicate(association)
|
122
|
+
join_predicate =
|
123
|
+
case association.macro
|
124
|
+
when :belongs_to
|
125
|
+
[{ association.options[:foreign_key] || association.primary_key_name.to_sym => @model_class.primary_key }]
|
126
|
+
when :has_many, :has_one
|
127
|
+
[{ association.options[:primary_key] || @model_class.primary_key => association.primary_key_name.to_sym }]
|
128
|
+
else raise InvalidJoinException.new("I don't know how to do a simple join on an association of type #{association.macro}.")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def compound_join(association, join_type, options)
|
133
|
+
pivot_join_predicate = [{ @model_class.primary_key => association.primary_key_name.to_sym }]
|
134
|
+
table_name = @model_class.connection.quote_table_name(association.options[:join_table])
|
135
|
+
pivot_table = PivotTable.new(table_name, association, "__#{options[:alias] || alias_for_association(association)}")
|
136
|
+
pivot_join = Join.new(self, pivot_table, pivot_join_predicate, join_type)
|
137
|
+
join_predicate = [{ association.association_foreign_key.to_sym => @model_class.primary_key }]
|
138
|
+
join_table = Table.new(association.klass, options[:alias] || alias_for_association(association))
|
139
|
+
pivot_table.joins << join = Join.new(pivot_table, join_table, join_predicate, join_type)
|
140
|
+
@joins << pivot_join
|
141
|
+
join
|
142
|
+
end
|
143
|
+
|
144
|
+
protected
|
145
|
+
|
146
|
+
def alias_for_association(association)
|
147
|
+
@alias_cache ||= {}
|
148
|
+
@alias_cache[association.name] ||=
|
149
|
+
"#{@aliased ? @table_alias.to_s : @model_class.table_name}__#{association.name.to_s.underscore}"
|
150
|
+
end
|
151
|
+
|
152
|
+
alias_method :alias_for_class, :alias_for_association
|
153
|
+
end
|
154
|
+
|
155
|
+
class PivotTable < Table # :nodoc: all
|
156
|
+
attr_reader :table_name, :joins
|
157
|
+
|
158
|
+
def initialize(table_name, association, table_alias = table_name)
|
159
|
+
@table_name, @table_alias = table_name, table_alias
|
160
|
+
@joins_cache = {}
|
161
|
+
@joins = []
|
162
|
+
@orders = []
|
163
|
+
@group_bys = []
|
164
|
+
@primary_key = association.primary_key_name.to_sym
|
165
|
+
@foreign_key = association.association_foreign_key.to_sym
|
166
|
+
end
|
167
|
+
|
168
|
+
def has_column(column_name)
|
169
|
+
[@primary_key, @foreign_key].include?(column_name.to_sym)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
%w(active_record column_parser query table conjunctions restrictions restriction_factory filter join order group_by dsl).each do |file|
|
4
|
+
require File.join(File.dirname(__FILE__), 'record_filter', file)
|
5
|
+
end
|
6
|
+
|
7
|
+
# The base-level namespace for the record_filter code. See RecordFilter::ActiveRecordExtension::ClassMethods
|
8
|
+
# for a description of the public API.
|
9
|
+
module RecordFilter
|
10
|
+
|
11
|
+
# An exception that is raised when an implicit join is attempted on an association
|
12
|
+
# that does not exist.
|
13
|
+
class AssociationNotFoundException < StandardError; end
|
14
|
+
|
15
|
+
# An exception that is raised when attempting to place restrictions or specify an
|
16
|
+
# explicit join on a column that doesn't exist.
|
17
|
+
class ColumnNotFoundException < StandardError; end
|
18
|
+
|
19
|
+
# An exception that is raised when operations such as limit, order, group_by, or
|
20
|
+
# on are called out of context.
|
21
|
+
class InvalidFilterException < StandardError; end
|
22
|
+
|
23
|
+
# An exception that is raised when attempting to create a named filter with a name that
|
24
|
+
# already exists in the class it is created on or one of its superclasses.
|
25
|
+
class InvalidFilterNameException < StandardError; end
|
26
|
+
|
27
|
+
# An exception that is raised when no columns are privided to specify an explicit join
|
28
|
+
# or when the join names are incorrect.
|
29
|
+
class InvalidJoinException < StandardError; end
|
30
|
+
|
31
|
+
# An exception raised in the case where a named filter is called from within a filter
|
32
|
+
# and the named filter does not exist.
|
33
|
+
class NamedFilterNotFoundException < StandardError; end
|
34
|
+
end
|
35
|
+
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{record_filter}
|
8
|
+
s.version = "0.9.12"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Aubrey Holland", "Mat Brown"]
|
12
|
+
s.date = %q{2009-09-13}
|
13
|
+
s.description = %q{RecordFilter is a Pure-ruby criteria API for building complex queries in ActiveRecord. It supports queries that are built on the fly as well as named filters that can be added to objects and chained to create complex queries. It also gets rid of the nasty hard-coded SQL that shows up in most ActiveRecord code with a clean API that makes queries simple and intuitive to build.}
|
14
|
+
s.email = %q{aubreyholland@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.rdoc"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"CHANGELOG",
|
21
|
+
"README.rdoc",
|
22
|
+
"Rakefile",
|
23
|
+
"TODO",
|
24
|
+
"VERSION.yml",
|
25
|
+
"config/roodi.yml",
|
26
|
+
"lib/record_filter.rb",
|
27
|
+
"lib/record_filter/active_record.rb",
|
28
|
+
"lib/record_filter/column_parser.rb",
|
29
|
+
"lib/record_filter/conjunctions.rb",
|
30
|
+
"lib/record_filter/dsl.rb",
|
31
|
+
"lib/record_filter/dsl/class_join.rb",
|
32
|
+
"lib/record_filter/dsl/conjunction.rb",
|
33
|
+
"lib/record_filter/dsl/conjunction_dsl.rb",
|
34
|
+
"lib/record_filter/dsl/dsl.rb",
|
35
|
+
"lib/record_filter/dsl/dsl_factory.rb",
|
36
|
+
"lib/record_filter/dsl/group_by.rb",
|
37
|
+
"lib/record_filter/dsl/join.rb",
|
38
|
+
"lib/record_filter/dsl/join_condition.rb",
|
39
|
+
"lib/record_filter/dsl/join_dsl.rb",
|
40
|
+
"lib/record_filter/dsl/limit.rb",
|
41
|
+
"lib/record_filter/dsl/named_filter.rb",
|
42
|
+
"lib/record_filter/dsl/order.rb",
|
43
|
+
"lib/record_filter/dsl/restriction.rb",
|
44
|
+
"lib/record_filter/filter.rb",
|
45
|
+
"lib/record_filter/group_by.rb",
|
46
|
+
"lib/record_filter/join.rb",
|
47
|
+
"lib/record_filter/order.rb",
|
48
|
+
"lib/record_filter/query.rb",
|
49
|
+
"lib/record_filter/restriction_factory.rb",
|
50
|
+
"lib/record_filter/restrictions.rb",
|
51
|
+
"lib/record_filter/table.rb",
|
52
|
+
"record_filter.gemspec",
|
53
|
+
"script/console",
|
54
|
+
"spec/active_record_spec.rb",
|
55
|
+
"spec/exception_spec.rb",
|
56
|
+
"spec/explicit_join_spec.rb",
|
57
|
+
"spec/implicit_join_spec.rb",
|
58
|
+
"spec/limits_and_ordering_spec.rb",
|
59
|
+
"spec/models.rb",
|
60
|
+
"spec/named_filter_spec.rb",
|
61
|
+
"spec/proxying_spec.rb",
|
62
|
+
"spec/restrictions_spec.rb",
|
63
|
+
"spec/select_spec.rb",
|
64
|
+
"spec/spec_helper.rb",
|
65
|
+
"spec/test.db",
|
66
|
+
"tasks/db.rake",
|
67
|
+
"tasks/rcov.rake",
|
68
|
+
"tasks/spec.rake",
|
69
|
+
"test/performance_test.rb",
|
70
|
+
"test/test.db"
|
71
|
+
]
|
72
|
+
s.homepage = %q{http://github.com/aub/record_filter}
|
73
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
74
|
+
s.require_paths = ["lib"]
|
75
|
+
s.rubyforge_project = %q{record-filter}
|
76
|
+
s.rubygems_version = %q{1.3.3}
|
77
|
+
s.summary = %q{An ActiveRecord query API for replacing SQL with awesome}
|
78
|
+
s.test_files = [
|
79
|
+
"spec/active_record_spec.rb",
|
80
|
+
"spec/exception_spec.rb",
|
81
|
+
"spec/explicit_join_spec.rb",
|
82
|
+
"spec/implicit_join_spec.rb",
|
83
|
+
"spec/limits_and_ordering_spec.rb",
|
84
|
+
"spec/models.rb",
|
85
|
+
"spec/named_filter_spec.rb",
|
86
|
+
"spec/proxying_spec.rb",
|
87
|
+
"spec/restrictions_spec.rb",
|
88
|
+
"spec/select_spec.rb",
|
89
|
+
"spec/spec_helper.rb",
|
90
|
+
"test/performance_test.rb"
|
91
|
+
]
|
92
|
+
|
93
|
+
if s.respond_to? :specification_version then
|
94
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
95
|
+
s.specification_version = 3
|
96
|
+
|
97
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
98
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 0"])
|
99
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
100
|
+
else
|
101
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
102
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
103
|
+
end
|
104
|
+
else
|
105
|
+
s.add_dependency(%q<activerecord>, [">= 0"])
|
106
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
107
|
+
end
|
108
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe 'active record options' do
|
4
|
+
before do
|
5
|
+
TestModel.extended_models.each { |model| model.last_find = {} }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'for has_many' do
|
9
|
+
describe 'using class_name' do
|
10
|
+
before do
|
11
|
+
Blog.filter do
|
12
|
+
having(:stories).with(:permalink, 'ack')
|
13
|
+
end.inspect
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should create the correct condition' do
|
17
|
+
Blog.last_find[:conditions].should == [%q(blogs__stories.permalink = ?), 'ack']
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should create the correct join' do
|
21
|
+
Blog.last_find[:joins].should == [%q(INNER JOIN "news_stories" AS blogs__stories ON "blogs".id = blogs__stories.blog_id)]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'using foreign_key' do
|
26
|
+
before do
|
27
|
+
Blog.filter do
|
28
|
+
having(:special_posts).with(:permalink, 'eek')
|
29
|
+
end.inspect
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should create the correct condition' do
|
33
|
+
Blog.last_find[:conditions].should == [%q(blogs__special_posts.permalink = ?), 'eek']
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should create the correct join' do
|
37
|
+
Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__special_posts ON "blogs".id = blogs__special_posts.special_blog_id)]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe 'using primary_key' do
|
42
|
+
before do
|
43
|
+
Blog.filter do
|
44
|
+
having(:special_public_posts).with(:permalink, 'oooh')
|
45
|
+
end.inspect
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should create the correct condition' do
|
49
|
+
Blog.last_find[:conditions].should == [%q(blogs__special_public_posts.permalink = ?), 'oooh']
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should create the correct join' do
|
53
|
+
Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__special_public_posts ON "blogs".special_id = blogs__special_public_posts.blog_id)]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'using source' do
|
58
|
+
before do
|
59
|
+
Blog.filter do
|
60
|
+
having(:nasty_comments).with(:contents, 'blammo')
|
61
|
+
end.inspect
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'should create the correct condition' do
|
65
|
+
Blog.last_find[:conditions].should == [%q(blogs__posts__bad_comments.contents = ?), 'blammo']
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should create the correct join' do
|
69
|
+
Blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id), %q(INNER JOIN "comments" AS blogs__posts__bad_comments ON blogs__posts.id = blogs__posts__bad_comments.post_id)]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'using source_type' do
|
74
|
+
before do
|
75
|
+
Blog.filter do
|
76
|
+
having(:featured_posts).with(:permalink, 'slam dunk')
|
77
|
+
end.inspect
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should create the correct condition' do
|
81
|
+
Blog.last_find[:conditions].should == [%q(blogs__features__featurable.permalink = ?), 'slam dunk']
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'should create the correct join' do
|
85
|
+
Blog.last_find[:joins].should == [%q(INNER JOIN "features" AS blogs__features ON "blogs".id = blogs__features.blog_id AND (blogs__features.featurable_type = 'Post')), %q(INNER JOIN "posts" AS blogs__features__featurable ON blogs__features.featurable_id = blogs__features__featurable.id)]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe 'for belongs_to' do
|
91
|
+
|
92
|
+
describe 'using class_name' do
|
93
|
+
before do
|
94
|
+
Post.filter do
|
95
|
+
having(:publication).with(:name, 'NYT')
|
96
|
+
end.inspect
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should create the correct condition' do
|
100
|
+
Post.last_find[:conditions].should == [%q(posts__publication.name = ?), 'NYT']
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should create the correct join' do
|
104
|
+
Post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__publication ON "posts".blog_id = posts__publication.id)]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'using class_name when the association has a different name than the class name' do
|
109
|
+
before do
|
110
|
+
AssetAttachment.filter { having(:from).with(:id).gt(1) }.inspect
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should create the correct condition' do
|
114
|
+
AssetAttachment.last_find[:conditions].should == [%q(asset_attachments__from.id > ?), 1]
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should create the correct join' do
|
118
|
+
AssetAttachment.last_find[:joins].should == [%q(INNER JOIN "assets" AS asset_attachments__from ON "asset_attachments".from_id = asset_attachments__from.id)]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe 'using foreign_key' do
|
123
|
+
before do
|
124
|
+
Post.filter do
|
125
|
+
having(:special_blog).with(:name, 'Larry')
|
126
|
+
end.inspect
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should create the correct condition' do
|
130
|
+
Post.last_find[:conditions].should == [%q(posts__special_blog.name = ?), 'Larry']
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should create the correct join' do
|
134
|
+
Post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__special_blog ON "posts".special_blog_id = posts__special_blog.id)]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe 'working with named scopes' do
|
140
|
+
before do
|
141
|
+
@blog = Class.new(Blog)
|
142
|
+
@blog.named_scope :with_high_id, { :conditions => ['id > 100'] }
|
143
|
+
@blog.named_filter(:published) { with(:published, true) }
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should concatenate the filter with the scope correctly' do
|
147
|
+
@blog.with_high_id.published.inspect
|
148
|
+
@blog.last_find[:conditions].should == %q(("blogs".published = 't') AND (id > 100))
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'should concatenate correctly when called in the other order' do
|
152
|
+
@blog.published.with_high_id.inspect
|
153
|
+
@blog.last_find[:conditions].should == %q((id > 100) AND ("blogs".published = 't'))
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe 'working with named scopes when there are a number of joins' do
|
158
|
+
before do
|
159
|
+
@blog = Class.new(Blog)
|
160
|
+
@blog.named_scope :ads_with_sale, { :joins => :ads, :conditions => ["'ads'.content LIKE ?", '%sale%'] }
|
161
|
+
@blog.named_filter(:with_permalinked_posts) { having(:posts).with(:permalink).is_not_null }
|
162
|
+
@blog.named_filter(:with_offensive_comments) { having(:comments).with(:offensive, true) }
|
163
|
+
@blog.with_permalinked_posts.ads_with_sale.with_offensive_comments.inspect
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'should concatenate the conditions correctly' do
|
167
|
+
@blog.last_find[:conditions].should == %q((blogs__posts__comments.offensive = 't') AND (('ads'.content LIKE '%sale%') AND (blogs__posts.permalink IS NOT NULL)))
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'should concatenate the joins correctly and not throw away my joins like AR usually does' do
|
171
|
+
@blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id), %q(INNER JOIN "comments" AS blogs__posts__comments ON blogs__posts.id = blogs__posts__comments.post_id), %q(INNER JOIN "ads" ON ads.blog_id = blogs.id)]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe 'working with named scopes that join to the same table' do
|
176
|
+
before do
|
177
|
+
@blog = Class.new(Blog)
|
178
|
+
@blog.named_scope :with_crazy_post_permalinks, { :joins => :posts, :conditions => ["'posts'.permalink = ?", 'crazy'] }
|
179
|
+
@blog.named_filter(:with_empty_permalinks) { having(:posts).with(:permalink, nil) }
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'should concatenate the conditions correctly' do
|
183
|
+
@blog.with_crazy_post_permalinks.with_empty_permalinks.inspect
|
184
|
+
@blog.last_find[:conditions].should == %q((blogs__posts.permalink IS NULL) AND ('posts'.permalink = 'crazy'))
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'should concatenate the joins correctly' do
|
188
|
+
@blog.with_crazy_post_permalinks.with_empty_permalinks.inspect
|
189
|
+
@blog.last_find[:joins].should == [%q(INNER JOIN "posts" AS blogs__posts ON "blogs".id = blogs__posts.blog_id), %q(INNER JOIN "posts" ON posts.blog_id = blogs.id)]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe 'working with default scopes' do
|
194
|
+
describe 'with a simple filter' do
|
195
|
+
before do
|
196
|
+
Article.filter do
|
197
|
+
with(:contents, 'something')
|
198
|
+
end.inspect
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'should use the correct order' do
|
202
|
+
Article.last_find[:order].should == %q(created_at DESC)
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'should use the correct conditions' do
|
206
|
+
pending 'currently the IS NULL condition is added twice.'
|
207
|
+
Article.last_find[:conditions].should == %q((("articles"."created_at" IS NULL) AND ("articles".contents = 'something')))
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|