textacular 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +164 -0
- data/Gemfile +3 -0
- data/README.md +143 -0
- data/Rakefile +154 -0
- data/lib/textacular.rb +227 -0
- data/lib/textacular/full_text_indexer.rb +79 -0
- data/lib/textacular/postgres_module_installer.rb +57 -0
- data/lib/textacular/rails.rb +10 -0
- data/lib/textacular/searchable.rb +20 -0
- data/lib/textacular/tasks.rb +18 -0
- data/lib/textacular/version.rb +7 -0
- data/spec/config.yml.example +4 -0
- data/spec/fixtures/character.rb +0 -0
- data/spec/fixtures/game.rb +9 -0
- data/spec/fixtures/webcomic.rb +9 -0
- data/spec/spec_helper.rb +78 -0
- data/spec/textacular/searchable_spec.rb +92 -0
- data/spec/textacular_spec.rb +216 -0
- metadata +178 -0
data/CHANGELOG.md
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
## 3.0.0
|
4
|
+
|
5
|
+
* All deprecations have been resolved. This breaks backwards compatibility.
|
6
|
+
* Rename gem to Textacular.
|
7
|
+
|
8
|
+
## 2.2.0
|
9
|
+
|
10
|
+
* 1 DEPRECATION
|
11
|
+
* The whole gem is being renamed and will no longer be maintained as Texticle.
|
12
|
+
The new name it Textacular.
|
13
|
+
* 1 new feature
|
14
|
+
* Expand gemspec to allow Rails 4.
|
15
|
+
|
16
|
+
## 2.1.1
|
17
|
+
|
18
|
+
* 1 bugfix
|
19
|
+
* Include `lib/textacular/version.rb` in the gemspec so the gem will load. Sorry!
|
20
|
+
|
21
|
+
|
22
|
+
## 2.1.0
|
23
|
+
|
24
|
+
* 1 DEPRECATION
|
25
|
+
* `search` aliases new `advanced_search` method (same functionality as before), but will
|
26
|
+
alias `basic_search` in 3.0! Should print warnings.
|
27
|
+
* 3 new features
|
28
|
+
* Generate full text search indexes from a rake task (sort of like in 1.x). Supply a specific
|
29
|
+
model name.
|
30
|
+
* New search methods: `basic_search`, `advanced_search` and `fuzzy_search`. Basic allows special
|
31
|
+
characters like &, and % in search terms. Fuzzy is based on Postgres's trigram matching extension
|
32
|
+
pg_trgm. Advanced is the same functionality from `search` previously.
|
33
|
+
* Rake task that installs pg_trgm now works on Postgres 9.1 and up.
|
34
|
+
* 2 dev improvements
|
35
|
+
* Test database configuration not automatically generated from a rake task and ignored by git.
|
36
|
+
* New interactive developer console (powered by pry).
|
37
|
+
|
38
|
+
|
39
|
+
## 2.0.3
|
40
|
+
|
41
|
+
* 1 new feature
|
42
|
+
* Allow searching through relations. Model.join(:relation).search(:relation => {:column => "query"})
|
43
|
+
works, and reduces the need for multi-model tables. Huge thanks to Ben Hamill for the pull request.
|
44
|
+
* Allow searching through all model columns irrespective of the column's type; we cast all columns to text
|
45
|
+
in the search query. Performance may degrade when searching through anything but a string column.
|
46
|
+
* 2 bugfixes
|
47
|
+
* Fix exceptions when adding Textacular to a table-less model.
|
48
|
+
* Column names in a search query are now scoped to the current table.
|
49
|
+
* 1 dev improvement
|
50
|
+
* Running `rake` from the project root will setup the test environment by creating a test database
|
51
|
+
and running the necessary migrations. `rake` can also be used to run all the project tests.
|
52
|
+
|
53
|
+
|
54
|
+
## 2.0.2
|
55
|
+
|
56
|
+
* 1 bugfix
|
57
|
+
* Our #respond_to? overwritten method was causing failures when a model didn't have
|
58
|
+
a table (e.g. if migrations hadn't been run yet). Not the case anymore.
|
59
|
+
|
60
|
+
|
61
|
+
## 2.0.1
|
62
|
+
|
63
|
+
* 1 new feature
|
64
|
+
* Can now define #searchable_language to specify the language used for the query. This changes
|
65
|
+
what's considered a stop word on Postgres' side. 'english' is the default language.
|
66
|
+
* 1 bugfix
|
67
|
+
* We were only specifying a language in to_tsvector() and not in to_tsquery(), which could
|
68
|
+
cause queries to fail if the default database language wasn't set to 'english'.
|
69
|
+
|
70
|
+
|
71
|
+
## 2.0.pre4
|
72
|
+
|
73
|
+
* 1 new feature
|
74
|
+
* Searchable is now available to specify which columns you want searched:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
require 'textacular/searchable'
|
78
|
+
class Game
|
79
|
+
extend Searchable(:title)
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
This also allows Textacular use in Rails without having #search available to all models:
|
84
|
+
|
85
|
+
```
|
86
|
+
gem 'textacular', '~> 2.0.pre4', :require => 'textacular/searchable'
|
87
|
+
```
|
88
|
+
* 1 bugfix
|
89
|
+
* ActiveRecord::Base.extend(Textacular) doesn't break #method_missing and #respond_to? anymore
|
90
|
+
|
91
|
+
|
92
|
+
## 2.0.pre3
|
93
|
+
|
94
|
+
* 1 new feature
|
95
|
+
* #select calls now limit the columns that are searched
|
96
|
+
* 1 bugfix
|
97
|
+
* #search calls without an argument assume an empty string as a search term (it errored out previously)
|
98
|
+
|
99
|
+
|
100
|
+
## 2.0.pre2
|
101
|
+
|
102
|
+
* 1 bugfix
|
103
|
+
* #respond_to? wasn't overwritten correctly
|
104
|
+
|
105
|
+
## 2.0.pre
|
106
|
+
|
107
|
+
* Complete refactoring of Textacular
|
108
|
+
* For users:
|
109
|
+
* Textacular should only be used for its simplicity; if you need to deeply configure your text search, please give `gem install pg_search` a try.
|
110
|
+
* `#search` method is now included in all ActiveRecord models by default, and searches across a model's :string columns.
|
111
|
+
* `#search_by_<column>` dynamic methods are now available.
|
112
|
+
* `#search` can now be chained; `Game.search_by_title("Street Fighter").search_by_system("PS3")` works.
|
113
|
+
* `#search` now accepts a hash to specify columns to be searched, e.g. `Game.search(:name => "Mario")`
|
114
|
+
* No more access to `#rank` values for results (though they're still ordered by rank).
|
115
|
+
* No way to give different weights to different columns in this release.
|
116
|
+
* For devs:
|
117
|
+
* We now have actual tests to run against; this will make accepting pull requests much more enjoyable.
|
118
|
+
|
119
|
+
|
120
|
+
## HEAD (unreleased)
|
121
|
+
|
122
|
+
* 1 minor bugfix
|
123
|
+
* Multiple named indices are now supported.
|
124
|
+
|
125
|
+
|
126
|
+
## 1.0.4 / 2010-08-19
|
127
|
+
|
128
|
+
* 2 major enhancements
|
129
|
+
* use Rails.root instead of RAILS_ROOT
|
130
|
+
* refactored tasks to ease maintainance and patchability
|
131
|
+
* 3 minor enhancements
|
132
|
+
* fix timestamp for migrationfile
|
133
|
+
* fixed deprecation warning for rails3 (dropping rails2-support)
|
134
|
+
* prevented warning about defined constant
|
135
|
+
|
136
|
+
|
137
|
+
## 1.0.3 / 2010-07-07
|
138
|
+
|
139
|
+
* 1 major enhancement
|
140
|
+
* Added Rails 3 support.
|
141
|
+
* 1 bugfix
|
142
|
+
* Model names that end in double 's's (like Address) don't choke the rake tasks anymore.
|
143
|
+
|
144
|
+
|
145
|
+
## 1.0.2 / 2009-10-17
|
146
|
+
|
147
|
+
* 1 bugfix
|
148
|
+
* Generated migration now uses UTC time rather than local time.
|
149
|
+
|
150
|
+
|
151
|
+
## 1.0.1 / 2009-04-14
|
152
|
+
|
153
|
+
* 1 minor enhancement
|
154
|
+
* Textical adds a rake task to generate FTS index migrations. Just run:
|
155
|
+
|
156
|
+
```
|
157
|
+
rake textical:migration
|
158
|
+
```
|
159
|
+
|
160
|
+
|
161
|
+
## 1.0.0 / 2009-04-14
|
162
|
+
|
163
|
+
* 1 major enhancement
|
164
|
+
* Birthday!
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# textacular
|
2
|
+
|
3
|
+
Further documentation available at http://textacular.github.com/textacular.
|
4
|
+
|
5
|
+
|
6
|
+
## DESCRIPTION:
|
7
|
+
|
8
|
+
Textacular exposes full text search capabilities from PostgreSQL,
|
9
|
+
extending ActiveRecord with scopes making search easy and fun!
|
10
|
+
|
11
|
+
|
12
|
+
## FEATURES/PROBLEMS:
|
13
|
+
|
14
|
+
* Only works with PostgreSQL
|
15
|
+
|
16
|
+
|
17
|
+
## SYNOPSIS:
|
18
|
+
|
19
|
+
### Quick Start
|
20
|
+
|
21
|
+
#### Rails 3 (or 4!)
|
22
|
+
|
23
|
+
In the project's Gemfile add
|
24
|
+
|
25
|
+
gem 'textacular', '~> 3.0', require: 'textacular/rails'
|
26
|
+
|
27
|
+
|
28
|
+
#### ActiveRecord outside of Rails
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
require 'textacular'
|
32
|
+
|
33
|
+
ActiveRecord::Base.extend(Textacular)
|
34
|
+
```
|
35
|
+
|
36
|
+
|
37
|
+
### Usage
|
38
|
+
|
39
|
+
Your models now have access to search methods:
|
40
|
+
|
41
|
+
The `#basic_search` method is what you might expect: it looks literally for what
|
42
|
+
you send to it, doing nothing fancy with the input:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Game.basic_search('Sonic') # will search through the model's :string columns
|
46
|
+
Game.basic_search(title: 'Mario', system: 'Nintendo')
|
47
|
+
```
|
48
|
+
|
49
|
+
The `#advanced_search` method lets you use Postgres's search syntax like '|',
|
50
|
+
'&' and '!' ('or', 'and', and 'not') as well as some other craziness. Check [the
|
51
|
+
Postgres
|
52
|
+
docs](http://www.postgresql.org/docs/9.2/static/datatype-textsearch.html) for more:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
Game.advanced_search(title: 'Street|Fantasy')
|
56
|
+
Game.advanced_search(system: '!PS2')
|
57
|
+
```
|
58
|
+
|
59
|
+
Finally, the `#fuzzy_search` method lets you use Postgres's trigram search
|
60
|
+
funcionality:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
Comic.fuzzy_search(title: 'Questio') # matches Questionable Content
|
64
|
+
```
|
65
|
+
|
66
|
+
Searches are also chainable:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
Game.fuzzy_search(title: 'tree').basic_search(system: 'SNES')
|
70
|
+
```
|
71
|
+
|
72
|
+
|
73
|
+
### Setting Language
|
74
|
+
|
75
|
+
To set proper searching dictionary just override class method on your model:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
def self.searchable_language
|
79
|
+
'russian'
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
And all your queries would go right! And don`t forget to change the migration for indexes, like shown below.
|
84
|
+
|
85
|
+
|
86
|
+
### Creating Indexes for Super Speed
|
87
|
+
You can have Postgresql use an index for the full-text search. To declare a full-text index, in a
|
88
|
+
migration add code like the following:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
execute "
|
92
|
+
create index on email_logs using gin(to_tsvector('english', subject));
|
93
|
+
create index on email_logs using gin(to_tsvector('english', email_address));"
|
94
|
+
```
|
95
|
+
|
96
|
+
In the above example, the table email_logs has two text columns that we search against, subject and email_address.
|
97
|
+
You will need to add an index for every text/string column you query against, or else Postgresql will revert to a
|
98
|
+
full table scan instead of using the indexes.
|
99
|
+
|
100
|
+
If you create these indexes, you should also switch to sql for your schema_format in `config/application.rb`:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
config.active_record.schema_format = :sql
|
104
|
+
```
|
105
|
+
|
106
|
+
|
107
|
+
## REQUIREMENTS:
|
108
|
+
|
109
|
+
* ActiveRecord
|
110
|
+
* Ruby 1.9.2
|
111
|
+
|
112
|
+
|
113
|
+
## INSTALL:
|
114
|
+
|
115
|
+
```
|
116
|
+
$ gem install textacular
|
117
|
+
```
|
118
|
+
|
119
|
+
|
120
|
+
## LICENSE:
|
121
|
+
|
122
|
+
(The MIT License)
|
123
|
+
|
124
|
+
Copyright (c) 2011 Aaron Patterson
|
125
|
+
|
126
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
127
|
+
a copy of this software and associated documentation files (the
|
128
|
+
'Software'), to deal in the Software without restriction, including
|
129
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
130
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
131
|
+
permit persons to whom the Software is furnished to do so, subject to
|
132
|
+
the following conditions:
|
133
|
+
|
134
|
+
The above copyright notice and this permission notice shall be
|
135
|
+
included in all copies or substantial portions of the Software.
|
136
|
+
|
137
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
138
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
139
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
140
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
141
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
142
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
143
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'yaml'
|
5
|
+
require 'pg'
|
6
|
+
require 'active_record'
|
7
|
+
require 'benchmark'
|
8
|
+
|
9
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/spec')
|
10
|
+
|
11
|
+
task :default do
|
12
|
+
Rake::Task["db:setup"].invoke
|
13
|
+
Rake::Task["test"].invoke
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Fire up an interactive terminal to play with"
|
17
|
+
task :console do
|
18
|
+
require 'pry'
|
19
|
+
require File.expand_path(File.dirname(__FILE__) + '/lib/textacular')
|
20
|
+
|
21
|
+
config = YAML.load_file File.expand_path(File.dirname(__FILE__) + '/spec/config.yml')
|
22
|
+
ActiveRecord::Base.establish_connection config.merge(:adapter => :postgresql)
|
23
|
+
|
24
|
+
class Character < ActiveRecord::Base
|
25
|
+
belongs_to :web_comic
|
26
|
+
end
|
27
|
+
|
28
|
+
class WebComic < ActiveRecord::Base
|
29
|
+
has_many :characters
|
30
|
+
end
|
31
|
+
|
32
|
+
class Game < ActiveRecord::Base
|
33
|
+
end
|
34
|
+
|
35
|
+
# add ability to reload console
|
36
|
+
def reload
|
37
|
+
reload_msg = '# Reloading the console...'
|
38
|
+
puts CodeRay.scan(reload_msg, :ruby).term
|
39
|
+
Pry.save_history
|
40
|
+
exec('rake console')
|
41
|
+
end
|
42
|
+
|
43
|
+
# start the console! :-)
|
44
|
+
welcome = <<-EOS
|
45
|
+
Welcome to the Textacular devloper console. You have some classes you can play with:
|
46
|
+
|
47
|
+
class Character < ActiveRecord::Base
|
48
|
+
# string :name
|
49
|
+
# string :description
|
50
|
+
# integer :web_comic_id
|
51
|
+
|
52
|
+
belongs_to :web_comic
|
53
|
+
end
|
54
|
+
|
55
|
+
class WebComic < ActiveRecord::Base
|
56
|
+
# string :name
|
57
|
+
# string :author
|
58
|
+
# integer :id
|
59
|
+
|
60
|
+
has_many :characters
|
61
|
+
end
|
62
|
+
|
63
|
+
class Game < ActiveRecord::Base
|
64
|
+
# string :system
|
65
|
+
# string :title
|
66
|
+
# text :description
|
67
|
+
end
|
68
|
+
EOS
|
69
|
+
|
70
|
+
puts CodeRay.scan(welcome, :ruby).term
|
71
|
+
Pry.start
|
72
|
+
end
|
73
|
+
|
74
|
+
task :test do
|
75
|
+
require 'textacular_spec'
|
76
|
+
require 'textacular/searchable_spec'
|
77
|
+
require 'textacular/full_text_indexer_spec'
|
78
|
+
end
|
79
|
+
|
80
|
+
namespace :db do
|
81
|
+
desc 'Create and configure the test database'
|
82
|
+
task :setup do
|
83
|
+
spec_directory = "#{File.expand_path(File.dirname(__FILE__))}/spec"
|
84
|
+
|
85
|
+
STDOUT.puts "Detecting database configuration..."
|
86
|
+
|
87
|
+
if File.exists?("#{spec_directory}/config.yml")
|
88
|
+
STDOUT.puts "Configuration detected. Skipping confguration."
|
89
|
+
else
|
90
|
+
STDOUT.puts "Would you like to create and configure the test database? y/N"
|
91
|
+
continue = STDIN.gets.chomp
|
92
|
+
|
93
|
+
unless continue =~ /^[y]$/i
|
94
|
+
STDOUT.puts "Done."
|
95
|
+
exit 0
|
96
|
+
end
|
97
|
+
|
98
|
+
STDOUT.puts "Creating database..."
|
99
|
+
`createdb textacular`
|
100
|
+
|
101
|
+
STDOUT.puts "Writing configuration file..."
|
102
|
+
|
103
|
+
config_example = File.read("#{spec_directory}/config.yml.example")
|
104
|
+
|
105
|
+
File.open("#{spec_directory}/config.yml", "w") do |config|
|
106
|
+
config << config_example.sub(/<username>/, `whoami`.chomp)
|
107
|
+
end
|
108
|
+
|
109
|
+
STDOUT.puts "Running migrations..."
|
110
|
+
Rake::Task["db:migrate"].invoke
|
111
|
+
|
112
|
+
STDOUT.puts 'Done.'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
desc 'Run migrations for test database'
|
117
|
+
task :migrate do
|
118
|
+
config = YAML.load_file File.expand_path(File.dirname(__FILE__) + '/spec/config.yml')
|
119
|
+
ActiveRecord::Base.establish_connection config.merge(:adapter => :postgresql)
|
120
|
+
|
121
|
+
ActiveRecord::Migration.instance_eval do
|
122
|
+
create_table :games do |table|
|
123
|
+
table.string :system
|
124
|
+
table.string :title
|
125
|
+
table.text :description
|
126
|
+
end
|
127
|
+
create_table :web_comics do |table|
|
128
|
+
|
129
|
+
table.string :name
|
130
|
+
table.string :author
|
131
|
+
table.text :review
|
132
|
+
table.integer :id
|
133
|
+
end
|
134
|
+
|
135
|
+
create_table :characters do |table|
|
136
|
+
table.string :name
|
137
|
+
table.string :description
|
138
|
+
table.integer :web_comic_id
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
desc 'Drop tables from test database'
|
144
|
+
task :drop do
|
145
|
+
config = YAML.load_file File.expand_path(File.dirname(__FILE__) + '/spec/config.yml')
|
146
|
+
ActiveRecord::Base.establish_connection config.merge(:adapter => :postgresql)
|
147
|
+
|
148
|
+
ActiveRecord::Migration.instance_eval do
|
149
|
+
drop_table :games
|
150
|
+
drop_table :web_comics
|
151
|
+
drop_table :characters
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|