require "spec_helper" describe PgSearch do context "joining to another table" do context "without an :against" do with_model :AssociatedModel do table do |t| t.string "title" end end with_model :ModelWithoutAgainst do table do |t| t.string "title" t.belongs_to :another_model end model do include PgSearch belongs_to :another_model, :class_name => 'AssociatedModel' pg_search_scope :with_another, :associated_against => {:another_model => :title} end end it "returns rows that match the query in the columns of the associated model only" do associated = AssociatedModel.create!(:title => 'abcdef') included = [ ModelWithoutAgainst.create!(:title => 'abcdef', :another_model => associated), ModelWithoutAgainst.create!(:title => 'ghijkl', :another_model => associated) ] excluded = [ ModelWithoutAgainst.create!(:title => 'abcdef') ] results = ModelWithoutAgainst.with_another('abcdef') results.map(&:title).should =~ included.map(&:title) results.should_not include(excluded) end end context "through a belongs_to association" do with_model :AssociatedModel do table do |t| t.string 'title' end end with_model :ModelWithBelongsTo do table do |t| t.string 'title' t.belongs_to 'another_model' end model do include PgSearch belongs_to :another_model, :class_name => 'AssociatedModel' pg_search_scope :with_associated, :against => :title, :associated_against => {:another_model => :title} end end it "returns rows that match the query in either its own columns or the columns of the associated model" do associated = AssociatedModel.create!(:title => 'abcdef') included = [ ModelWithBelongsTo.create!(:title => 'ghijkl', :another_model => associated), ModelWithBelongsTo.create!(:title => 'abcdef') ] excluded = ModelWithBelongsTo.create!(:title => 'mnopqr', :another_model => AssociatedModel.create!(:title => 'stuvwx')) results = ModelWithBelongsTo.with_associated('abcdef') results.map(&:title).should =~ included.map(&:title) results.should_not include(excluded) end end context "through a has_many association" do with_model :AssociatedModelWithHasMany do table do |t| t.string 'title' t.belongs_to 'ModelWithHasMany' end end with_model :ModelWithHasMany do table do |t| t.string 'title' end model do include PgSearch has_many :other_models, :class_name => 'AssociatedModelWithHasMany', :foreign_key => 'ModelWithHasMany_id' pg_search_scope :with_associated, :against => [:title], :associated_against => {:other_models => :title} end end it "returns rows that match the query in either its own columns or the columns of the associated model" do included = [ ModelWithHasMany.create!(:title => 'abcdef', :other_models => [ AssociatedModelWithHasMany.create!(:title => 'foo'), AssociatedModelWithHasMany.create!(:title => 'bar') ]), ModelWithHasMany.create!(:title => 'ghijkl', :other_models => [ AssociatedModelWithHasMany.create!(:title => 'foo bar'), AssociatedModelWithHasMany.create!(:title => 'mnopqr') ]), ModelWithHasMany.create!(:title => 'foo bar') ] excluded = ModelWithHasMany.create!(:title => 'stuvwx', :other_models => [ AssociatedModelWithHasMany.create!(:title => 'abcdef') ]) results = ModelWithHasMany.with_associated('foo bar') results.map(&:title).should =~ included.map(&:title) results.should_not include(excluded) end it "uses an unscoped relation of the assocated model" do excluded = ModelWithHasMany.create!(:title => 'abcdef', :other_models => [ AssociatedModelWithHasMany.create!(:title => 'abcdef') ]) included = [ ModelWithHasMany.create!(:title => 'abcdef', :other_models => [ AssociatedModelWithHasMany.create!(:title => 'foo'), AssociatedModelWithHasMany.create!(:title => 'bar') ]) ] results = ModelWithHasMany.limit(1).order("id ASC").with_associated('foo bar') results.map(&:title).should =~ included.map(&:title) results.should_not include(excluded) end end context "across multiple associations" do context "on different tables" do with_model :FirstAssociatedModel do table do |t| t.string 'title' t.belongs_to 'ModelWithManyAssociations' end model {} end with_model :SecondAssociatedModel do table do |t| t.string 'title' end model {} end with_model :ModelWithManyAssociations do table do |t| t.string 'title' t.belongs_to 'model_of_second_type' end model do include PgSearch has_many :models_of_first_type, :class_name => 'FirstAssociatedModel', :foreign_key => 'ModelWithManyAssociations_id' belongs_to :model_of_second_type, :class_name => 'SecondAssociatedModel' pg_search_scope :with_associated, :against => :title, :associated_against => {:models_of_first_type => :title, :model_of_second_type => :title} end end it "returns rows that match the query in either its own columns or the columns of the associated model" do matching_second = SecondAssociatedModel.create!(:title => "foo bar") unmatching_second = SecondAssociatedModel.create!(:title => "uiop") included = [ ModelWithManyAssociations.create!(:title => 'abcdef', :models_of_first_type => [ FirstAssociatedModel.create!(:title => 'foo'), FirstAssociatedModel.create!(:title => 'bar') ]), ModelWithManyAssociations.create!(:title => 'ghijkl', :models_of_first_type => [ FirstAssociatedModel.create!(:title => 'foo bar'), FirstAssociatedModel.create!(:title => 'mnopqr') ]), ModelWithManyAssociations.create!(:title => 'foo bar'), ModelWithManyAssociations.create!(:title => 'qwerty', :model_of_second_type => matching_second) ] excluded = [ ModelWithManyAssociations.create!(:title => 'stuvwx', :models_of_first_type => [ FirstAssociatedModel.create!(:title => 'abcdef') ]), ModelWithManyAssociations.create!(:title => 'qwerty', :model_of_second_type => unmatching_second) ] results = ModelWithManyAssociations.with_associated('foo bar') results.map(&:title).should =~ included.map(&:title) excluded.each { |object| results.should_not include(object) } end end context "on the same table" do with_model :DoublyAssociatedModel do table do |t| t.string 'title' t.belongs_to 'ModelWithDoubleAssociation' t.belongs_to 'ModelWithDoubleAssociation_again' end model {} end with_model :ModelWithDoubleAssociation do table do |t| t.string 'title' end model do include PgSearch has_many :things, :class_name => 'DoublyAssociatedModel', :foreign_key => 'ModelWithDoubleAssociation_id' has_many :thingamabobs, :class_name => 'DoublyAssociatedModel', :foreign_key => 'ModelWithDoubleAssociation_again_id' pg_search_scope :with_associated, :against => :title, :associated_against => {:things => :title, :thingamabobs => :title} end end it "returns rows that match the query in either its own columns or the columns of the associated model" do included = [ ModelWithDoubleAssociation.create!(:title => 'abcdef', :things => [ DoublyAssociatedModel.create!(:title => 'foo'), DoublyAssociatedModel.create!(:title => 'bar') ]), ModelWithDoubleAssociation.create!(:title => 'ghijkl', :things => [ DoublyAssociatedModel.create!(:title => 'foo bar'), DoublyAssociatedModel.create!(:title => 'mnopqr') ]), ModelWithDoubleAssociation.create!(:title => 'foo bar'), ModelWithDoubleAssociation.create!(:title => 'qwerty', :thingamabobs => [ DoublyAssociatedModel.create!(:title => "foo bar") ]) ] excluded = [ ModelWithDoubleAssociation.create!(:title => 'stuvwx', :things => [ DoublyAssociatedModel.create!(:title => 'abcdef') ]), ModelWithDoubleAssociation.create!(:title => 'qwerty', :thingamabobs => [ DoublyAssociatedModel.create!(:title => "uiop") ]) ] results = ModelWithDoubleAssociation.with_associated('foo bar') results.map(&:title).should =~ included.map(&:title) excluded.each { |object| results.should_not include(object) } end end end context "against multiple attributes on one association" do with_model :AssociatedModel do table do |t| t.string 'title' t.text 'author' end end with_model :ModelWithAssociation do table do |t| t.belongs_to 'another_model' end model do include PgSearch belongs_to :another_model, :class_name => 'AssociatedModel' pg_search_scope :with_associated, :associated_against => {:another_model => [:title, :author]} end end it "should only do one join" do included = [ ModelWithAssociation.create!( :another_model => AssociatedModel.create!( :title => "foo", :author => "bar" ) ), ModelWithAssociation.create!( :another_model => AssociatedModel.create!( :title => "foo bar", :author => "baz" ) ) ] excluded = [ ModelWithAssociation.create!( :another_model => AssociatedModel.create!( :title => "foo", :author => "baz" ) ) ] results = ModelWithAssociation.with_associated('foo bar') results.to_sql.scan("INNER JOIN").length.should == 1 included.each { |object| results.should include(object) } excluded.each { |object| results.should_not include(object) } end end context "against non-text columns" do with_model :AssociatedModel do table do |t| t.integer 'number' end end with_model :Model do table do |t| t.integer 'number' t.belongs_to 'another_model' end model do include PgSearch belongs_to :another_model, :class_name => 'AssociatedModel' pg_search_scope :with_associated, :associated_against => {:another_model => :number} end end it "should cast the columns to text" do associated = AssociatedModel.create!(:number => 123) included = [ Model.create!(:number => 123, :another_model => associated), Model.create!(:number => 456, :another_model => associated) ] excluded = [ Model.create!(:number => 123) ] results = Model.with_associated('123') results.map(&:number).should =~ included.map(&:number) results.should_not include(excluded) end end context "when including the associated model" do with_model :Parent do table do |t| t.text :name end model do has_many :children include PgSearch pg_search_scope :search_name, :against => :name end end with_model :Child do table do |t| t.belongs_to :parent end model do belongs_to :parent end end # https://github.com/Casecommons/pg_search/issues/14 it "supports queries with periods" do included = Parent.create!(:name => 'bar.foo') excluded = Parent.create!(:name => 'foo.bar') results = Parent.search_name('bar.foo').includes(:children) results.to_a results.should include(included) results.should_not include(excluded) end end end context "merging a pg_search_scope into another model's scope" do with_model :ModelWithAssociation do model do has_many :associated_models end end with_model :AssociatedModel do table do |t| t.string :content t.belongs_to :model_with_association end model do include PgSearch belongs_to :model_with_association pg_search_scope :search_content, :against => :content end end it "should find records of the other model" do included_associated_1 = AssociatedModel.create(:content => "foo bar") included_associated_2 = AssociatedModel.create(:content => "foo baz") excluded_associated_1 = AssociatedModel.create(:content => "baz quux") excluded_associated_2 = AssociatedModel.create(:content => "baz bar") included = [ ModelWithAssociation.create(:associated_models => [included_associated_1]), ModelWithAssociation.create(:associated_models => [included_associated_2, excluded_associated_1]) ] excluded = [ ModelWithAssociation.create(:associated_models => [excluded_associated_2]), ModelWithAssociation.create(:associated_models => []) ] relation = AssociatedModel.search_content("foo") results = ModelWithAssociation.joins(:associated_models).merge(relation) results.should include(*included) results.should_not include(*excluded) end end context "chained onto a has_many association" do with_model :Company do model do has_many :positions end end with_model :Position do table do |t| t.string :title t.belongs_to :company end model do include PgSearch pg_search_scope :search, :against => :title, :using => [:tsearch, :trigram] end end # https://github.com/Casecommons/pg_search/issues/106 it "should handle numbers in a trigram query properly" do company = Company.create! another_company = Company.create! included = [ Position.create!(:company_id => company.id, :title => "teller 1") ] excluded = [ Position.create!(:company_id => nil, :title => "teller 1"), Position.create!(:company_id => another_company.id, :title => "teller 1"), Position.create!(:company_id => company.id, :title => "penn 1") ] results = company.positions.search('teller 1') results.should include(*included) results.should_not include(*excluded) end end end