record_filter 0.9.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gitignore +9 -0
  2. data/CHANGELOG +232 -0
  3. data/README.rdoc +354 -0
  4. data/Rakefile +92 -0
  5. data/TODO +3 -0
  6. data/VERSION.yml +4 -0
  7. data/config/roodi.yml +14 -0
  8. data/lib/record_filter/active_record.rb +108 -0
  9. data/lib/record_filter/column_parser.rb +14 -0
  10. data/lib/record_filter/conjunctions.rb +169 -0
  11. data/lib/record_filter/dsl/class_join.rb +16 -0
  12. data/lib/record_filter/dsl/conjunction.rb +57 -0
  13. data/lib/record_filter/dsl/conjunction_dsl.rb +317 -0
  14. data/lib/record_filter/dsl/dsl.rb +143 -0
  15. data/lib/record_filter/dsl/dsl_factory.rb +19 -0
  16. data/lib/record_filter/dsl/group_by.rb +11 -0
  17. data/lib/record_filter/dsl/join.rb +12 -0
  18. data/lib/record_filter/dsl/join_condition.rb +21 -0
  19. data/lib/record_filter/dsl/join_dsl.rb +49 -0
  20. data/lib/record_filter/dsl/limit.rb +12 -0
  21. data/lib/record_filter/dsl/named_filter.rb +12 -0
  22. data/lib/record_filter/dsl/order.rb +12 -0
  23. data/lib/record_filter/dsl/restriction.rb +314 -0
  24. data/lib/record_filter/dsl.rb +21 -0
  25. data/lib/record_filter/filter.rb +105 -0
  26. data/lib/record_filter/group_by.rb +21 -0
  27. data/lib/record_filter/join.rb +66 -0
  28. data/lib/record_filter/order.rb +27 -0
  29. data/lib/record_filter/query.rb +60 -0
  30. data/lib/record_filter/restriction_factory.rb +21 -0
  31. data/lib/record_filter/restrictions.rb +97 -0
  32. data/lib/record_filter/table.rb +172 -0
  33. data/lib/record_filter.rb +35 -0
  34. data/record_filter.gemspec +108 -0
  35. data/script/console +8 -0
  36. data/spec/active_record_spec.rb +211 -0
  37. data/spec/exception_spec.rb +208 -0
  38. data/spec/explicit_join_spec.rb +132 -0
  39. data/spec/implicit_join_spec.rb +403 -0
  40. data/spec/limits_and_ordering_spec.rb +230 -0
  41. data/spec/models.rb +109 -0
  42. data/spec/named_filter_spec.rb +264 -0
  43. data/spec/proxying_spec.rb +63 -0
  44. data/spec/restrictions_spec.rb +251 -0
  45. data/spec/select_spec.rb +79 -0
  46. data/spec/spec_helper.rb +39 -0
  47. data/spec/test.db +0 -0
  48. data/tasks/db.rake +106 -0
  49. data/tasks/rcov.rake +9 -0
  50. data/tasks/spec.rake +10 -0
  51. data/test/performance_test.rb +39 -0
  52. data/test/test.db +0 -0
  53. 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,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'spec', 'spec_helper')
4
+
5
+ require 'logger'
6
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
7
+
8
+ IRB.start
@@ -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