pg_search 0.5 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc CHANGED
@@ -3,79 +3,50 @@
3
3
  # This is an RVM Project .rvmrc file, used to automatically load the ruby
4
4
  # development environment upon cd'ing into the directory
5
5
 
6
- # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
7
- environment_id="ree-1.8.7-2011.12@pg_search"
6
+ # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
7
+ # Only full ruby name is supported here, for short names use:
8
+ # echo "rvm use 1.9.3" > .rvmrc
9
+ environment_id="ruby-1.9.3-p194@pg_search"
8
10
 
9
- #
10
11
  # Uncomment the following lines if you want to verify rvm version per project
11
- #
12
- # rvmrc_rvm_version="1.10.2" # 1.10.1 seams as a safe start
12
+ # rvmrc_rvm_version="1.12.4 ()" # 1.10.1 seams as a safe start
13
13
  # eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
14
14
  # echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
15
15
  # return 1
16
16
  # }
17
- #
18
17
 
19
- #
20
- # Uncomment following line if you want options to be set only for given project.
21
- #
22
- # PROJECT_JRUBY_OPTS=( --1.9 )
23
- #
24
- # The variable PROJECT_JRUBY_OPTS requires the following to be run in shell:
25
- #
26
- # chmod +x ${rvm_path}/hooks/after_use_jruby_opts
27
- #
28
-
29
- #
30
18
  # First we attempt to load the desired environment directly from the environment
31
19
  # file. This is very fast and efficient compared to running through the entire
32
20
  # CLI and selector. If you want feedback on which environment was used then
33
21
  # insert the word 'use' after --create as this triggers verbose mode.
34
- #
35
- if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
22
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
36
23
  && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
37
24
  then
38
25
  \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
39
-
40
- if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
41
- then
42
- . "${rvm_path:-$HOME/.rvm}/hooks/after_use"
26
+ [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
27
+ \. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
28
+ if [[ $- == *i* ]] # check for interactive shells
29
+ then echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
30
+ else echo "Using: $GEM_HOME" # don't use colors in non-interactive shells
43
31
  fi
44
32
  else
45
33
  # If the environment file has not yet been created, use the RVM CLI to select.
46
- if ! rvm --create use "$environment_id"
47
- then
34
+ rvm --create use "$environment_id" || {
48
35
  echo "Failed to create RVM environment '${environment_id}'."
49
36
  return 1
50
- fi
37
+ }
51
38
  fi
52
39
 
53
- #
54
- # If you use an RVM gemset file to install a list of gems (*.gems), you can have
55
- # it be automatically loaded. Uncomment the following and adjust the filename if
56
- # necessary.
57
- #
58
- # filename=".gems"
59
- # if [[ -s "$filename" ]]
60
- # then
61
- # rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
62
- # fi
63
-
64
40
  # If you use bundler, this might be useful to you:
65
- # if [[ -s Gemfile ]] && ! command -v bundle >/dev/null
66
- # then
67
- # printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
68
- # gem install bundler
69
- # fi
70
- # if [[ -s Gemfile ]] && command -v bundle
71
- # then
72
- # bundle install
73
- # fi
74
-
75
- if [[ $- == *i* ]] # check for interactive shells
41
+ if [[ -s Gemfile ]] && {
42
+ ! builtin command -v bundle >/dev/null ||
43
+ builtin command -v bundle | grep $rvm_path/bin/bundle >/dev/null
44
+ }
76
45
  then
77
- echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
78
- else
79
- echo "Using: $GEM_HOME" # don't use colors in interactive shells
46
+ printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
47
+ gem install bundler
48
+ fi
49
+ if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null
50
+ then
51
+ bundle install | grep -vE '^Using|Your bundle is complete'
80
52
  fi
81
-
data/.travis.yml CHANGED
@@ -1,2 +1,14 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.8.7
5
+ - 1.9.2
6
+ - 1.9.3
7
+ - jruby-18mode
8
+ - jruby-19mode
9
+ - rbx-18mode
10
+ - rbx-19mode
11
+ - ree
12
+
1
13
  before_script:
2
14
  - "psql -c 'create database pg_search_test;' -U postgres >/dev/null"
data/CHANGELOG.rdoc CHANGED
@@ -1,5 +1,9 @@
1
1
  = PgSearch
2
2
 
3
+ == 0.5.1
4
+
5
+ * Add ability to override multisearch rebuild SQL
6
+
3
7
  == 0.5
4
8
 
5
9
  * Convert migration rake tasks into generators.
data/Gemfile CHANGED
@@ -4,7 +4,15 @@ gemspec
4
4
 
5
5
  gem "rake"
6
6
  gem "rdoc"
7
- gem "pg"
7
+
8
+ platforms :ruby do
9
+ gem 'pg'
10
+ end
11
+
12
+ platforms :jruby do
13
+ gem "activerecord-jdbcpostgresql-adapter"
14
+ end
15
+
8
16
  gem "rspec"
9
17
  gem "with_model"
10
18
 
data/README.rdoc CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  * http://github.com/casecommons/pg_search/
4
4
 
5
+ {<img src="https://secure.travis-ci.org/Casecommons/pg_search.png?branch=master" alt="Build Status" />}[http://travis-ci.org/Casecommons/pg_search] {<img src="https://gemnasium.com/Casecommons/pg_search.png" alt="Dependency Status" />}[https://gemnasium.com/Casecommons/pg_search] {<img src="https://codeclimate.com/badge.png" />}[https://codeclimate.com/github/Casecommons/pg_search]
6
+
5
7
  == DESCRIPTION
6
8
 
7
9
  PgSearch builds named scopes that take advantage of PostgreSQL's full text search
@@ -95,7 +97,7 @@ PgSearch.multisearch returns an ActiveRecord::Relation, just like scopes do, so
95
97
  PgSearch.multisearch("Bertha").limit(10)
96
98
  PgSearch.multisearch("Juggler").where(:searchable_type => "Occupation")
97
99
  PgSearch.multisearch("Alamo").page(3).per_page(30)
98
- PgSearch.mulitsearch("Diagonal").find_each do |document|
100
+ PgSearch.multisearch("Diagonal").find_each do |document|
99
101
  puts document.searchable.updated_at
100
102
  end
101
103
 
@@ -127,6 +129,39 @@ Currently this is only supported for :against methods that directly map to Activ
127
129
  PgSearch::Document.delete_all(:searchable_type => "Ingredient")
128
130
  Ingredient.find_each { |record| record.update_pg_search_document }
129
131
 
132
+
133
+ You can also provide a custom implementation for rebuilding the documents by adding a class method called `rebuild_pg_search_documents` to your model.
134
+
135
+ class Movie < ActiveRecord::Base
136
+ belongs_to :director
137
+
138
+ def director_name
139
+ director.name
140
+ end
141
+
142
+ multisearchable against: [:name, :director_name]
143
+
144
+ # Naive approach
145
+ def self.rebuild_pg_search_documents
146
+ find_each { |record| record.update_pg_search_document }
147
+ end
148
+
149
+ # More sophisticated approach
150
+ def self.rebuild_pg_search_documents
151
+ connection.execute <<-SQL
152
+ INSERT INTO pg_search_documents (searchable_type, searchable_id, content, created_at, updated_at)
153
+ SELECT 'Movie' AS searchable_type,
154
+ movies.id AS searchable_id,
155
+ (movies.name || ' ' || directors.name) AS content,
156
+ now() AS created_at,
157
+ now() AS updated_at
158
+ FROM movies
159
+ LEFT JOIN directors
160
+ ON directors.id = movies.director_id
161
+ SQL
162
+ end
163
+ end
164
+
130
165
  ==== Disabling multi-search indexing temporarily
131
166
 
132
167
  If you have a large bulk operation to perform, such as importing a lot of records from an external source, you might want to speed things up by turning off indexing temporarily. You could then use one of the techniques above to rebuild the search documents off-line.
@@ -490,7 +525,7 @@ Please note that the :against column is only used when the tsvector_column is no
490
525
 
491
526
  * ActiveRecord 3
492
527
  * PostgreSQL
493
- * PostgreSQL contrib packages for certain features
528
+ * {PostgreSQL contrib packages for certain features}[https://github.com/Casecommons/pg_search/wiki/Installing-Postgres-Contrib-Modules]
494
529
 
495
530
  == ATTRIBUTIONS
496
531
 
@@ -63,7 +63,8 @@ module PgSearch
63
63
 
64
64
  def tsdocument
65
65
  if @options[:tsvector_column]
66
- @options[:tsvector_column].to_s
66
+ column_name = connection.quote_column_name(@options[:tsvector_column])
67
+ "#{quoted_table_name}.#{column_name}"
67
68
  else
68
69
  @columns.map do |search_column|
69
70
  tsvector = "to_tsvector(:dictionary, #{@normalizer.add_normalization(search_column.to_sql)})"
@@ -13,10 +13,14 @@ INSERT INTO :documents_table (searchable_type, searchable_id, content, created_a
13
13
  SQL
14
14
 
15
15
  class << self
16
- def rebuild(model)
16
+ def rebuild(model, clean_up=true)
17
17
  model.transaction do
18
- PgSearch::Document.where(:searchable_type => model.name).delete_all
19
- model.connection.execute(rebuild_sql(model))
18
+ PgSearch::Document.where(:searchable_type => model.name).delete_all if clean_up
19
+ if model.respond_to?(:rebuild_pg_search_documents)
20
+ model.rebuild_pg_search_documents
21
+ else
22
+ model.connection.execute(rebuild_sql(model))
23
+ end
20
24
  end
21
25
  end
22
26
 
@@ -1,3 +1,3 @@
1
1
  module PgSearch
2
- VERSION = "0.5"
2
+ VERSION = "0.5.1"
3
3
  end
@@ -14,8 +14,96 @@ describe PgSearch::Multisearch do
14
14
  end
15
15
  end
16
16
 
17
+ let(:model) { MultisearchableModel }
18
+ let(:connection) { model.connection }
19
+ let(:documents) { double(:documents) }
20
+
17
21
  describe ".rebuild" do
18
- it "should fetch the proper columns from the model" do
22
+ before do
23
+ model.multisearchable :against => :title
24
+ end
25
+
26
+ it "should operate inside a transaction" do
27
+ model.should_receive(:transaction).once
28
+
29
+ PgSearch::Multisearch.rebuild(model)
30
+ end
31
+
32
+ describe "cleaning up search documents for this model" do
33
+ before do
34
+ connection.execute <<-SQL
35
+ INSERT INTO pg_search_documents
36
+ (searchable_type, searchable_id, content, created_at, updated_at)
37
+ VALUES
38
+ ('#{model.name}', 123, 'foo', now(), now());
39
+ INSERT INTO pg_search_documents
40
+ (searchable_type, searchable_id, content, created_at, updated_at)
41
+ VALUES
42
+ ('Bar', 123, 'foo', now(), now());
43
+ SQL
44
+ PgSearch::Document.count.should == 2
45
+ end
46
+
47
+ context "when clean_up is not passed" do
48
+ it "should delete the document for the model" do
49
+ PgSearch::Multisearch.rebuild(model)
50
+ PgSearch::Document.count.should == 1
51
+ PgSearch::Document.first.searchable_type.should == "Bar"
52
+ end
53
+ end
54
+
55
+ context "when clean_up is true" do
56
+ let(:clean_up) { true }
57
+
58
+ it "should delete the document for the model" do
59
+ PgSearch::Multisearch.rebuild(model, clean_up)
60
+ PgSearch::Document.count.should == 1
61
+ PgSearch::Document.first.searchable_type.should == "Bar"
62
+ end
63
+ end
64
+
65
+ context "when clean_up is false" do
66
+ let(:clean_up) { false }
67
+
68
+ it "should not delete the document for the model" do
69
+ PgSearch::Multisearch.rebuild(model, clean_up)
70
+ PgSearch::Document.count.should == 2
71
+ end
72
+ end
73
+
74
+ context "when the model implements .rebuild_pg_search_documents" do
75
+ before do
76
+ def model.rebuild_pg_search_documents
77
+ connection.execute <<-SQL
78
+ INSERT INTO pg_search_documents
79
+ (searchable_type, searchable_id, content, created_at, updated_at)
80
+ VALUES
81
+ ('Baz', 789, 'baz', now(), now());
82
+ SQL
83
+ end
84
+ end
85
+
86
+ it "should call .rebuild_pg_search_documents and skip the default behavior" do
87
+ PgSearch::Multisearch.should_not_receive(:rebuild_sql)
88
+ PgSearch::Multisearch.rebuild(model)
89
+
90
+ record = PgSearch::Document.find_by_searchable_type_and_searchable_id("Baz", 789)
91
+ record.content.should == "baz"
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "inserting the new documents" do
97
+ let!(:new_models) { [] }
98
+ before do
99
+ new_models << model.create!(:title => "Foo", :content => "Bar")
100
+ new_models << model.create!(:title => "Baz", :content => "Bar")
101
+ end
102
+
103
+ it "should create new documents for the two models" do
104
+ PgSearch::Multisearch.rebuild(model)
105
+ PgSearch::Document.last(2).map(&:searchable).map(&:title).should =~ new_models.map(&:title)
106
+ end
19
107
  end
20
108
  end
21
109
 
@@ -27,12 +115,11 @@ describe PgSearch::Multisearch do
27
115
  end
28
116
 
29
117
  context "with one attribute" do
30
- it "should generate the proper SQL code" do
31
- model = MultisearchableModel
32
- connection = model.connection
33
-
34
- model.multisearchable :against => :title
118
+ before do
119
+ model.multisearchable :against => [:title]
120
+ end
35
121
 
122
+ it "should generate the proper SQL code" do
36
123
  expected_sql = <<-SQL
37
124
  INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable_id, content, created_at, updated_at)
38
125
  SELECT #{connection.quote(model.name)} AS searchable_type,
@@ -43,19 +130,18 @@ INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable
43
130
  #{connection.quote(connection.quoted_date(now))} AS created_at,
44
131
  #{connection.quote(connection.quoted_date(now))} AS updated_at
45
132
  FROM #{model.quoted_table_name}
46
- SQL
133
+ SQL
47
134
 
48
- PgSearch::Multisearch.rebuild_sql(MultisearchableModel).should == expected_sql
135
+ PgSearch::Multisearch.rebuild_sql(model).should == expected_sql
49
136
  end
50
137
  end
51
138
 
52
139
  context "with multiple attributes" do
53
- it "should generate the proper SQL code" do
54
- model = MultisearchableModel
55
- connection = model.connection
56
-
140
+ before do
57
141
  model.multisearchable :against => [:title, :content]
142
+ end
58
143
 
144
+ it "should generate the proper SQL code" do
59
145
  expected_sql = <<-SQL
60
146
  INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable_id, content, created_at, updated_at)
61
147
  SELECT #{connection.quote(model.name)} AS searchable_type,
@@ -66,11 +152,10 @@ INSERT INTO #{PgSearch::Document.quoted_table_name} (searchable_type, searchable
66
152
  #{connection.quote(connection.quoted_date(now))} AS created_at,
67
153
  #{connection.quote(connection.quoted_date(now))} AS updated_at
68
154
  FROM #{model.quoted_table_name}
69
- SQL
155
+ SQL
70
156
 
71
- PgSearch::Multisearch.rebuild_sql(MultisearchableModel).should == expected_sql
157
+ PgSearch::Multisearch.rebuild_sql(model).should == expected_sql
72
158
  end
73
159
  end
74
-
75
160
  end
76
161
  end
@@ -610,7 +610,7 @@ describe "an ActiveRecord model which includes PgSearch" do
610
610
  end
611
611
 
612
612
  context "using a tsvector column" do
613
- with_model :ModelWithPgSearchUsingTsVectorColumn do
613
+ with_model :ModelWithTsvector do
614
614
  table do |t|
615
615
  t.text 'content'
616
616
  t.tsvector 'content_tsvector'
@@ -619,16 +619,16 @@ describe "an ActiveRecord model which includes PgSearch" do
619
619
  model { include PgSearch }
620
620
  end
621
621
 
622
- let!(:expected) { ModelWithPgSearchUsingTsVectorColumn.create!(:content => 'tiling is grouty') }
623
- let!(:unexpected) { ModelWithPgSearchUsingTsVectorColumn.create!(:content => 'longcat is looooooooong') }
622
+ let!(:expected) { ModelWithTsvector.create!(:content => 'tiling is grouty') }
623
+ let!(:unexpected) { ModelWithTsvector.create!(:content => 'longcat is looooooooong') }
624
624
 
625
625
  before do
626
626
  ActiveRecord::Base.connection.execute <<-SQL
627
- UPDATE #{ModelWithPgSearchUsingTsVectorColumn.table_name}
628
- SET content_tsvector = to_tsvector('english'::regconfig, "#{ModelWithPgSearchUsingTsVectorColumn.table_name}"."content")
627
+ UPDATE #{ModelWithTsvector.table_name}
628
+ SET content_tsvector = to_tsvector('english'::regconfig, "#{ModelWithTsvector.table_name}"."content")
629
629
  SQL
630
630
 
631
- ModelWithPgSearchUsingTsVectorColumn.pg_search_scope :search_by_content_with_tsvector,
631
+ ModelWithTsvector.pg_search_scope :search_by_content_with_tsvector,
632
632
  :against => :content,
633
633
  :using => {
634
634
  :tsearch => {
@@ -639,11 +639,30 @@ describe "an ActiveRecord model which includes PgSearch" do
639
639
  end
640
640
 
641
641
  it "should not use to_tsvector in the query" do
642
- ModelWithPgSearchUsingTsVectorColumn.search_by_content_with_tsvector("tiles").to_sql.should_not =~ /to_tsvector/
642
+ ModelWithTsvector.search_by_content_with_tsvector("tiles").to_sql.should_not =~ /to_tsvector/
643
643
  end
644
644
 
645
645
  it "should find the expected result" do
646
- ModelWithPgSearchUsingTsVectorColumn.search_by_content_with_tsvector("tiles").map(&:id).should == [expected.id]
646
+ ModelWithTsvector.search_by_content_with_tsvector("tiles").map(&:id).should == [expected.id]
647
+ end
648
+
649
+ context "when joining to a table with a column of the same name" do
650
+ with_model :AnotherModel do
651
+ table do |t|
652
+ t.string :content_tsvector # the type of the column doesn't matter
653
+ t.belongs_to :model_with_tsvector
654
+ end
655
+ end
656
+
657
+ before do
658
+ ModelWithTsvector.has_many :another_models
659
+ end
660
+
661
+ it "should refer to the tsvector column in the query unambiguously" do
662
+ expect {
663
+ ModelWithTsvector.joins(:another_models).search_by_content_with_tsvector("test").all
664
+ }.not_to raise_exception
665
+ end
647
666
  end
648
667
  end
649
668
 
data/spec/spec_helper.rb CHANGED
@@ -2,14 +2,32 @@ require "bundler/setup"
2
2
  require "pg_search"
3
3
 
4
4
  begin
5
+ require "pg"
6
+ error_class = PGError
7
+ rescue
8
+ begin
9
+ require "arjdbc/jdbc/core_ext"
10
+ error_class = ActiveRecord::JDBCError
11
+ rescue LoadError, StandardError
12
+ raise "I don't know what database adapter you're using, sorry."
13
+ end
14
+ end
15
+
16
+ begin
17
+ database_user = if ENV["TRAVIS"]
18
+ "postgres"
19
+ else
20
+ ENV["USER"]
21
+ end
22
+
5
23
  ActiveRecord::Base.establish_connection(:adapter => 'postgresql',
6
24
  :database => 'pg_search_test',
7
- :username => ('postgres' if ENV["TRAVIS"]),
25
+ :username => database_user,
8
26
  :min_messages => 'warning')
9
27
  connection = ActiveRecord::Base.connection
10
28
  postgresql_version = connection.send(:postgresql_version)
11
29
  connection.execute("SELECT 1")
12
- rescue PGError => e
30
+ rescue error_class => e
13
31
  puts "-" * 80
14
32
  puts "Unable to connect to database. Please run:"
15
33
  puts
@@ -70,6 +88,7 @@ RSpec::Matchers::OperatorMatcher.register(ActiveRecord::Relation, '=~', RSpec::M
70
88
  DOCUMENTS_SCHEMA = lambda do |t|
71
89
  t.belongs_to :searchable, :polymorphic => true
72
90
  t.text :content
91
+ t.timestamps
73
92
  end
74
93
 
75
94
  require 'irb'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_search
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.5'
4
+ version: 0.5.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-13 00:00:00.000000000 Z
12
+ date: 2012-08-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -109,12 +109,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
109
  - - ! '>='
110
110
  - !ruby/object:Gem::Version
111
111
  version: '0'
112
+ segments:
113
+ - 0
114
+ hash: -1351986722427483921
112
115
  required_rubygems_version: !ruby/object:Gem::Requirement
113
116
  none: false
114
117
  requirements:
115
118
  - - ! '>='
116
119
  - !ruby/object:Gem::Version
117
120
  version: '0'
121
+ segments:
122
+ - 0
123
+ hash: -1351986722427483921
118
124
  requirements: []
119
125
  rubyforge_project:
120
126
  rubygems_version: 1.8.24