From bd779834e51dde2bdf4535a8484f05023203ea5a Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 24 May 2013 14:03:45 -0400 Subject: [PATCH] Use search context for filtering search results by current category or user --- app/controllers/search_controller.rb | 35 ++++++++++++++-- lib/search.rb | 14 +++++++ spec/components/search_spec.rb | 24 +++++++++++ spec/controllers/search_controller_spec.rb | 47 +++++++++++++++++++++- 4 files changed, 115 insertions(+), 5 deletions(-) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 9d43075ab5e..8bf2b338846 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -2,12 +2,39 @@ require_dependency 'search' class SearchController < ApplicationController - def query - search = Search.new(params[:term], - guardian: guardian, - type_filter: params[:type_filter]) + def self.valid_context_types + %w{user topic category} + end + def query + requires_parameter(:term) + + search_args = {guardian: guardian} + search_args[:type_filter] = params[:type_filter] if params[:type_filter].present? + + search_context = params[:search_context] + if search_context.present? + raise Discourse::InvalidParameters.new(:search_context) unless SearchController.valid_context_types.include?(search_context[:type]) + raise Discourse::InvalidParameters.new(:search_context) if search_context[:id].blank? + + klass = search_context[:type].classify.constantize + + # A user is found by username + context_obj = nil + if search_context[:type] == 'user' + context_obj = klass.where(username: params[:search_context][:id]).first + else + context_obj = klass.where(id: params[:search_context][:id]).first + end + + guardian.ensure_can_see!(context_obj) + search_args[:search_context] = context_obj + end + + search = Search.new(params[:term], search_args.symbolize_keys) render_json_dump(search.execute.as_json) end + + end diff --git a/lib/search.rb b/lib/search.rb index 660233f23d1..ac3d61f8187 100644 --- a/lib/search.rb +++ b/lib/search.rb @@ -35,6 +35,7 @@ class Search @opts = opts || {} @guardian = @opts[:guardian] || Guardian.new + @search_context = @opts[:search_context] @limit = Search.per_facet * Search.facets.size @results = GroupedSearchResults.new(@opts[:type_filter]) end @@ -142,6 +143,19 @@ class Search .order("topics.bumped_at DESC") .limit(limit) + # Search context post results + if @search_context.present? + + if @search_context.is_a?(User) + # If the context is a user, restrict posts to that user + posts = posts.where(user_id: @search_context.id) + elsif @search_context.is_a?(Category) + # If the context is a category, restrict posts to that category + posts = posts.where('topics.category_id' => @search_context.id) + end + + end + if secure_category_ids.present? posts = posts.where("(categories.id IS NULL) OR (NOT categories.secure) OR (categories.id IN (?))", secure_category_ids) else diff --git a/spec/components/search_spec.rb b/spec/components/search_spec.rb index 920c7033237..4ae9e42cca6 100644 --- a/spec/components/search_spec.rb +++ b/spec/components/search_spec.rb @@ -233,6 +233,30 @@ describe Search do end + end + + context 'search_context' do + + context 'user as a search context' do + let(:search_user) { Search.new('hello', search_context: post.user).execute } + let(:search_coding_horror) { Search.new('hello', search_context: Fabricate(:coding_horror)).execute } + + Given!(:post) { Fabricate(:post) } + Then { first_of_type(search_user, 'topic')['id'] == post.topic_id } + And { first_of_type(search_coding_horror, 'topic').should be_blank } + end + + context 'category as a search context' do + let(:category) { Fabricate(:category) } + let(:search_cat) { Search.new('hello', search_context: category).execute } + let(:search_other_cat) { Search.new('hello', search_context: Fabricate(:category) ).execute } + let(:topic) { Fabricate(:topic, category: category) } + + Given!(:post) { Fabricate(:post, topic: topic, user: topic.user ) } + Then { first_of_type(search_cat, 'topic')['id'] == post.topic_id } + Then { first_of_type(search_other_cat, 'topic').should be_blank } + + end end diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb index 9299e97347d..0fb8442bec4 100644 --- a/spec/controllers/search_controller_spec.rb +++ b/spec/controllers/search_controller_spec.rb @@ -2,12 +2,14 @@ require 'spec_helper' describe SearchController do + let(:search_context) { {type: 'user', id: 'eviltrout'} } + it 'performs the query' do guardian = Guardian.new Guardian.stubs(:new).returns(guardian) search = mock() - Search.expects(:new).with('test', guardian: guardian, type_filter: nil).returns(search) + Search.expects(:new).with('test', guardian: guardian).returns(search) search.expects(:execute) xhr :get, :query, term: 'test' @@ -24,4 +26,47 @@ describe SearchController do xhr :get, :query, term: 'test', type_filter: 'topic' end + context "search context" do + + it "raises an error with an invalid context type" do + lambda { + xhr :get, :query, term: 'test', search_context: {type: 'security', id: 'hole'} + }.should raise_error(Discourse::InvalidParameters) + end + + it "raises an error with a missing id" do + lambda { + xhr :get, :query, term: 'test', search_context: {type: 'user'} + }.should raise_error(Discourse::InvalidParameters) + end + + context "with a user" do + + let(:user) { Fabricate(:user) } + + it "raises an error if the user can't see the context" do + Guardian.any_instance.expects(:can_see?).with(user).returns(false) + xhr :get, :query, term: 'test', search_context: {type: 'user', id: user.username} + response.should_not be_success + end + + + it 'performs the query with a search context' do + guardian = Guardian.new + Guardian.stubs(:new).returns(guardian) + + search = mock() + Search.expects(:new).with('test', guardian: guardian, search_context: user).returns(search) + search.expects(:execute) + + xhr :get, :query, term: 'test', search_context: {type: 'user', id: user.username} + end + + end + + + end + + + end