require File.join(File.dirname(__FILE__), 'spec_helper') describe 'named filters' do before do TestModel.extended_models.each { |model| model.last_find = {} } end describe 'defining a simple filter' do before do @blog = Class.new(Blog) @blog.named_filter(:with_test_name) do with :name, 'Test Name' end end it 'should call the filter through the filter method' do @blog.with_test_name.inspect @blog.last_find[:conditions].should == [%q("blogs".name = ?), 'Test Name'] end it 'should call the filter within the block' do @blog.filter do with_test_name end.inspect @blog.last_find[:conditions].should == [%q("blogs".name = ?), 'Test Name'] end end describe 'defining a filter with arguments' do before do @blog = Class.new(Blog) @blog.named_filter(:with_name) do |name| with :name, name end end it 'should call the filter with the passed argument' do @blog.with_name('nice name').inspect @blog.last_find[:conditions].should == [%q("blogs".name = ?), 'nice name'] end end describe 'defining a filter that passes arguments down several levels' do before do @blog = Class.new(Blog) @blog.named_filter(:with_name_and_post_with_permalink) do |name, permalink| with :name, name having :posts do with :permalink, permalink end end end it 'should call the filter passing all of the arguments' do @blog.with_name_and_post_with_permalink('booya', 'ftw').inspect @blog.last_find[:conditions].should == [%q(("blogs".name = ?) AND (blogs__posts.permalink = ?)), 'booya', 'ftw'] end end describe 'taking active_record objects as arguments' do it 'should use the id of the object as the actual parameter' do post = Class.new(Post) post.named_filter(:with_ar_arg) do |blog| with(:blog_id, blog) end blog = Blog.create post.with_ar_arg(blog).inspect post.last_find[:conditions].should == [%q("posts".blog_id = ?), blog.id] end end describe 'using filters in subclasses' do before do @comment = Class.new(Comment) @comment.named_filter(:with_contents) do |*args| with :contents, args[0] end @nice_comment = Class.new(@comment) do extend TestModel named_filter(:offensive) do with :offensive, true end end end it 'should execute the parent class filters correctly' do @nice_comment.with_contents('test contents').inspect @nice_comment.last_find[:conditions].should == [%q("comments".contents = ?), 'test contents'] end it 'should not have the subclass filters in the parent class' do @comment.respond_to?(:offensive).should == false end it 'should have parent class filters in the subclass' do @nice_comment.offensive.with_contents('something').inspect @nice_comment.last_find[:conditions].should == %q(("comments".contents = 'something') AND ("comments".offensive = 't')) end it 'should provide access to the named filters' do @nice_comment.named_filters.sort_by { |i| i.to_s }.should == (@comment.named_filters << :offensive).sort_by { |i| i.to_s } end end describe 'using compound filters' do before(:all) do Comment.named_filter(:offensive_or_not) do |state| with(:offensive, state) end end it 'should concatenate the filters correctly' do Post.filter do having(:comments).offensive_or_not(true) end.inspect Post.last_find[:conditions].should == [%q(posts__comments.offensive = ?), true] Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)] end it 'should work correctly with the named filter called within the having block' do Post.filter do having(:comments) do offensive_or_not(false) end end.inspect Post.last_find[:conditions].should == [%q(posts__comments.offensive = ?), false] Post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)] end end describe 'chaining filters' do before do @post = Class.new(Post) @post.named_filter(:for_blog) do |*args| having(:blog).with :id, args[0] end @post.named_filter(:with_offensive_comments) do having(:comments).with :offensive, true end @post.named_filter(:with_interesting_comments) do having(:comments).with :offensive, false end end it 'should chain the filters into a single query' do @post.for_blog(1).with_offensive_comments.inspect @post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1)) @post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)] end it 'should remove duplicate joins' do @post.for_blog(1).with_offensive_comments.with_interesting_comments.inspect @post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)] end it 'should allow for filtering a named_filter' do @post.for_blog(1).filter { having(:comments).with :offensive, true }.inspect @post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1)) @post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)] end it 'should allow for applying a named filter to a filter' do @post.filter { having(:comments).with :offensive, false }.for_blog(1).inspect @post.last_find[:conditions].should == %q((posts__blog.id = 1) AND (posts__comments.offensive = 'f')) @post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id), %q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id)] end it 'should not change the inner filter conditions when chaining filters' do base = @post.for_blog(1) base.with_offensive_comments base.inspect @post.last_find[:conditions].should == [%q(posts__blog.id = ?), 1] end it 'should not change the inner filter joins when chaining filters' do base = @post.for_blog(1) base.with_offensive_comments base.inspect @post.last_find[:joins].should == [%q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)] end it 'should not change an original filter when reusing it' do base = @post.for_blog(1) level1 = base.with_offensive_comments.inspect level2 = base.with_interesting_comments @post.last_find[:conditions].should == %q((posts__comments.offensive = 't') AND (posts__blog.id = 1)) @post.last_find[:joins].should == [%q(INNER JOIN "comments" AS posts__comments ON "posts".id = posts__comments.post_id), %q(INNER JOIN "blogs" AS posts__blog ON "posts".blog_id = posts__blog.id)] end end describe 'chaining named filters with regular AR associations' do before do Post.named_filter(:published) do with(:published, true) end @blog = Blog.create @blog.posts.published.inspect end it 'should combine the conditions from the association with the named filter' do Post.last_find[:conditions].should == "(\"posts\".published = 't') AND (\"posts\".blog_id = #{@blog.id})" end end describe 'chaining named filters with AR associations that involve joins' do before(:all) do Comment.named_filter(:with_user_named) do |name| having(:user).with(:first_name, name) end end before(:each) do @blog = Blog.create @blog.comments.with_user_named('Bob').inspect end it 'should combine the joins from the association with the named filter' do Comment.last_find[:joins].should == [%q(INNER JOIN "users" AS comments__user ON "comments".user_id = comments__user.id), %q(INNER JOIN "posts" ON "comments".post_id = "posts".id)] end it 'should combine the conditions from the association with the named filter' do Comment.last_find[:conditions].should == "(comments__user.first_name = 'Bob') AND ((\"posts\".blog_id = #{@blog.id}))" end end describe 'chaining multiple named filters with an AR association' do before do Comment.named_filter(:offensive) { with(:offensive, true) } Comment.named_filter(:with_fun_in_contents) { with(:contents).like('%fun%') } @post = Post.create @post.comments.offensive.with_fun_in_contents.inspect end it 'should combine the conditions correctly' do Comment.last_find[:conditions].should == "(\"comments\".contents LIKE '%fun%') AND ((\"comments\".offensive = 't') AND (\"comments\".post_id = #{@post.id}))" end end describe 'chaining multiple named filters with different joins' do before do @blog = Class.new(Blog) @blog.named_filter(:with_offensive_comments) { having(:comments).with(:offensive, true) } @blog.named_filter(:with_ads_with_content) { |content| having(:ads).with(:content, content) } end it 'compile the joins correctly' do @blog.with_offensive_comments.with_ads_with_content('ack').inspect @blog.last_find[:joins].should == [%q(INNER JOIN "ads" AS blogs__ads ON "blogs".id = blogs__ads.blog_id), %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)] end end describe 'with named filters that only include orders' do it 'should have an empty conditions hash' do blog = Class.new(Blog) blog.named_filter(:ordered_by_id) { order(:id, :desc) } blog.ordered_by_id.proxy_options.should == { :readonly => false, :order => %q("blogs".id DESC) } end end end