textacular 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,78 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'yaml'
4
+ require 'textacular'
5
+ require 'shoulda'
6
+ require 'pry'
7
+ require 'active_record'
8
+ require 'textacular'
9
+ require 'textacular/searchable'
10
+
11
+ config = YAML.load_file File.expand_path(File.dirname(__FILE__) + '/config.yml')
12
+ ActiveRecord::Base.establish_connection config.merge(:adapter => :postgresql)
13
+
14
+ class ARStandIn < ActiveRecord::Base;
15
+ self.abstract_class = true
16
+ extend Textacular
17
+ end
18
+
19
+ class NotThere < ARStandIn; end
20
+
21
+ class TextacularWebComic < ARStandIn;
22
+ has_many :characters, :foreign_key => :web_comic_id
23
+ self.table_name = :web_comics
24
+ end
25
+
26
+
27
+ class WebComic < ActiveRecord::Base
28
+ # string :name
29
+ # string :author
30
+ # integer :id
31
+
32
+ has_many :characters
33
+ end
34
+
35
+ class WebComicWithSearchable < WebComic
36
+ extend Searchable
37
+ end
38
+
39
+ class WebComicWithSearchableName < WebComic
40
+ extend Searchable(:name)
41
+ end
42
+
43
+ class WebComicWithSearchableNameAndAuthor < WebComic
44
+ extend Searchable(:name, :author)
45
+ end
46
+
47
+
48
+ class Character < ActiveRecord::Base
49
+ # string :name
50
+ # string :description
51
+ # integer :web_comic_id
52
+
53
+ belongs_to :web_comic
54
+ end
55
+
56
+
57
+ class Game < ActiveRecord::Base
58
+ # string :system
59
+ # string :title
60
+ # text :description
61
+ end
62
+
63
+ class GameExtendedWithTextacular < Game
64
+ extend Textacular
65
+ end
66
+
67
+ class GameExtendedWithTextacularAndCustomLanguage < GameExtendedWithTextacular
68
+ def searchable_language
69
+ 'spanish'
70
+ end
71
+ end
72
+
73
+
74
+ class GameFail < Game; end
75
+
76
+ class GameFailExtendedWithTextacular < GameFail
77
+ extend Textacular
78
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+ require 'textacular/searchable'
3
+
4
+ class SearchableTest < Test::Unit::TestCase
5
+ context "when extending an ActiveRecord::Base subclass" do
6
+ context "with no parameters" do
7
+ setup do
8
+ @qcont = WebComicWithSearchable.create :name => "Questionable Content", :author => "Jeph Jaques"
9
+ @jhony = WebComicWithSearchable.create :name => "Johnny Wander", :author => "Ananth & Yuko"
10
+ @ddeeg = WebComicWithSearchable.create :name => "Dominic Deegan", :author => "Mookie"
11
+ @penny = WebComicWithSearchable.create :name => "Penny Arcade", :author => "Tycho & Gabe"
12
+ end
13
+
14
+ teardown do
15
+ WebComicWithSearchable.delete_all
16
+ end
17
+
18
+ should "search across all columns" do
19
+ assert_equal [@penny], WebComicWithSearchable.advanced_search("Penny")
20
+ assert_equal [@ddeeg], WebComicWithSearchable.advanced_search("Dominic")
21
+ end
22
+ end
23
+
24
+ context "with one column as parameter" do
25
+ setup do
26
+ @qcont = WebComicWithSearchableName.create :name => "Questionable Content", :author => "Jeph Jaques"
27
+ @jhony = WebComicWithSearchableName.create :name => "Johnny Wander", :author => "Ananth & Yuko"
28
+ @ddeeg = WebComicWithSearchableName.create :name => "Dominic Deegan", :author => "Mookie"
29
+ @penny = WebComicWithSearchableName.create :name => "Penny Arcade", :author => "Tycho & Gabe"
30
+ end
31
+
32
+ teardown do
33
+ WebComicWithSearchableName.delete_all
34
+ end
35
+
36
+ should "only search across the given column" do
37
+ assert_equal [@penny], WebComicWithSearchableName.advanced_search("Penny")
38
+ assert_empty WebComicWithSearchableName.advanced_search("Tycho")
39
+ end
40
+
41
+ ["hello \\", "tebow!" , "food &"].each do |search_term|
42
+ should "be fine with searching for crazy character #{search_term} with plain search" do
43
+ # Uses plainto_tsquery
44
+ assert_equal [], WebComicWithSearchableName.basic_search(search_term)
45
+ end
46
+
47
+ should "be not fine with searching for crazy character #{search_term} with advanced search" do
48
+ # Uses to_tsquery
49
+ assert_raise(ActiveRecord::StatementInvalid) do
50
+ WebComicWithSearchableName.advanced_search(search_term).all
51
+ end
52
+ end
53
+ end
54
+
55
+ should "fuzzy search stuff" do
56
+ assert_equal [@qcont], WebComicWithSearchableName.fuzzy_search('Questio')
57
+ end
58
+
59
+ should "define :searchable_columns as private" do
60
+ assert_raise(NoMethodError) { WebComicWithSearchableName.searchable_columns }
61
+ begin
62
+ WebComicWithSearchableName.searchable_columns
63
+ rescue NoMethodError => error
64
+ assert_match error.message, /private method/
65
+ end
66
+ end
67
+
68
+ should "define #indexable_columns which returns a write-proof Enumerable" do
69
+ assert_equal(Enumerator, WebComicWithSearchableName.indexable_columns.class)
70
+ assert_raise(NoMethodError) { WebComicWithSearchableName.indexable_columns[0] = 'foo' }
71
+ end
72
+ end
73
+
74
+ context "with two columns as parameters" do
75
+ setup do
76
+ @qcont = WebComicWithSearchableNameAndAuthor.create :name => "Questionable Content", :author => "Jeph Jaques"
77
+ @jhony = WebComicWithSearchableNameAndAuthor.create :name => "Johnny Wander", :author => "Ananth & Yuko"
78
+ @ddeeg = WebComicWithSearchableNameAndAuthor.create :name => "Dominic Deegan", :author => "Mookie"
79
+ @penny = WebComicWithSearchableNameAndAuthor.create :name => "Penny Arcade", :author => "Tycho & Gabe"
80
+ end
81
+
82
+ teardown do
83
+ WebComicWithSearchableNameAndAuthor.delete_all
84
+ end
85
+
86
+ should "only search across the given column" do
87
+ assert_equal [@penny], WebComicWithSearchableNameAndAuthor.advanced_search("Penny")
88
+ assert_equal [@penny], WebComicWithSearchableNameAndAuthor.advanced_search("Tycho")
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,216 @@
1
+ # coding: utf-8
2
+ require 'spec_helper'
3
+
4
+ class TextacularTest < Test::Unit::TestCase
5
+ context "after extending ActiveRecord::Base" do
6
+ should "not break #respond_to?" do
7
+ assert_nothing_raised do
8
+ ARStandIn.respond_to? :abstract_class?
9
+ end
10
+ end
11
+
12
+ should "not break #respond_to? for table-less classes" do
13
+ assert !NotThere.table_exists?
14
+ assert_nothing_raised do
15
+ NotThere.respond_to? :system
16
+ end
17
+ end
18
+
19
+ should "not break #method_missing" do
20
+ assert_raise(NoMethodError) { ARStandIn.random }
21
+ begin
22
+ ARStandIn.random
23
+ rescue NoMethodError => error
24
+ assert_match error.message, /undefined method `random'/
25
+ end
26
+ end
27
+
28
+ should "not break #method_missing for table-less classes" do
29
+ assert !NotThere.table_exists?
30
+ assert_raise(NoMethodError) { NotThere.random }
31
+ begin
32
+ NotThere.random
33
+ rescue NoMethodError => error
34
+ assert_match error.message, /undefined method `random'/
35
+ end
36
+ end
37
+
38
+ context "when finding models based on searching a related model" do
39
+ setup do
40
+ @qc = TextacularWebComic.create :name => "Questionable Content", :author => "Jeph Jaques"
41
+ @jw = TextacularWebComic.create :name => "Johnny Wander", :author => "Ananth & Yuko"
42
+ @pa = TextacularWebComic.create :name => "Penny Arcade", :author => "Tycho & Gabe"
43
+
44
+ @gabe = @pa.characters.create :name => 'Gabe', :description => 'the simple one'
45
+ @tycho = @pa.characters.create :name => 'Tycho', :description => 'the wordy one'
46
+ @div = @pa.characters.create :name => 'Div', :description => 'a crude divx player with anger management issues'
47
+
48
+ @martin = @qc.characters.create :name => 'Martin', :description => 'the insecure protagonist'
49
+ @faye = @qc.characters.create :name => 'Faye', :description => 'a sarcastic barrista with anger management issues'
50
+ @pintsize = @qc.characters.create :name => 'Pintsize', :description => 'a crude AnthroPC'
51
+
52
+ @ananth = @jw.characters.create :name => 'Ananth', :description => 'Stubble! What is under that hat?!?'
53
+ @yuko = @jw.characters.create :name => 'Yuko', :description => 'So... small. Carl Sagan haircut.'
54
+ @john = @jw.characters.create :name => 'John', :description => 'Tall. Anger issues?'
55
+ @cricket = @jw.characters.create :name => 'Cricket', :description => 'Chirrup!'
56
+ end
57
+
58
+ teardown do
59
+ TextacularWebComic.delete_all
60
+ Character.delete_all
61
+ end
62
+
63
+ should "look in the related model with nested searching syntax" do
64
+ assert_equal [@jw], TextacularWebComic.joins(:characters).advanced_search(:characters => {:description => 'tall'})
65
+ assert_equal [@pa, @jw, @qc].sort, TextacularWebComic.joins(:characters).advanced_search(:characters => {:description => 'anger'}).sort
66
+ assert_equal [@pa, @qc].sort, TextacularWebComic.joins(:characters).advanced_search(:characters => {:description => 'crude'}).sort
67
+ end
68
+ end
69
+ end
70
+
71
+ context "after extending an ActiveRecord::Base subclass" do
72
+ setup do
73
+ @zelda = GameExtendedWithTextacular.create :system => "NES", :title => "Legend of Zelda", :description => "A Link to the Past."
74
+ @mario = GameExtendedWithTextacular.create :system => "NES", :title => "Super Mario Bros.", :description => "The original platformer."
75
+ @sonic = GameExtendedWithTextacular.create :system => "Genesis", :title => "Sonic the Hedgehog", :description => "Spiky."
76
+ @dkong = GameExtendedWithTextacular.create :system => "SNES", :title => "Diddy's Kong Quest", :description => "Donkey Kong Country 2"
77
+ @megam = GameExtendedWithTextacular.create :system => nil, :title => "Mega Man", :description => "Beware Dr. Brain"
78
+ @sfnes = GameExtendedWithTextacular.create :system => "SNES", :title => "Street Fighter 2", :description => "Yoga Flame!"
79
+ @sfgen = GameExtendedWithTextacular.create :system => "Genesis", :title => "Street Fighter 2", :description => "Yoga Flame!"
80
+ @takun = GameExtendedWithTextacular.create :system => "Saturn", :title => "Magical Tarurūto-kun", :description => "カッコイイ!"
81
+ end
82
+
83
+ teardown do
84
+ GameExtendedWithTextacular.delete_all
85
+ end
86
+
87
+ should "not break respond_to? when connection is unavailable" do
88
+ GameFailExtendedWithTextacular.establish_connection({:adapter => :postgresql, :database =>'unavailable', :username=>'bad', :pool=>5, :timeout=>5000}) rescue nil
89
+
90
+ assert_nothing_raised do
91
+ GameFailExtendedWithTextacular.respond_to?(:advanced_search)
92
+ end
93
+ end
94
+
95
+ should "define a #search method" do
96
+ assert GameExtendedWithTextacular.respond_to?(:search)
97
+ end
98
+
99
+ context "when searching with a String argument" do
100
+ should "search across all :string columns if no indexes have been specified" do
101
+ assert_equal [@mario], GameExtendedWithTextacular.advanced_search("Mario")
102
+ assert_equal Set.new([@mario, @zelda]), GameExtendedWithTextacular.advanced_search("NES").to_set
103
+ end
104
+
105
+ should "work if the query contains an apostrophe" do
106
+ assert_equal [@dkong], GameExtendedWithTextacular.advanced_search("Diddy's")
107
+ end
108
+
109
+ should "work if the query contains whitespace" do
110
+ assert_equal [@megam], GameExtendedWithTextacular.advanced_search("Mega Man")
111
+ end
112
+
113
+ should "work if the query contains an accent" do
114
+ assert_equal [@takun], GameExtendedWithTextacular.advanced_search("Tarurūto-kun")
115
+ end
116
+
117
+ should "search across records with NULL values" do
118
+ assert_equal [@megam], GameExtendedWithTextacular.advanced_search("Mega")
119
+ end
120
+
121
+ should "scope consecutively" do
122
+ assert_equal [@sfgen], GameExtendedWithTextacular.advanced_search("Genesis").advanced_search("Street Fighter")
123
+ end
124
+ end
125
+
126
+ context "when searching with a Hash argument" do
127
+ should "search across the given columns" do
128
+ assert_empty GameExtendedWithTextacular.advanced_search(:title => "NES")
129
+ assert_empty GameExtendedWithTextacular.advanced_search(:system => "Mario")
130
+ assert_empty GameExtendedWithTextacular.advanced_search(:system => "NES", :title => "Sonic")
131
+
132
+ assert_equal [@mario], GameExtendedWithTextacular.advanced_search(:title => "Mario")
133
+
134
+ assert_equal 2, GameExtendedWithTextacular.advanced_search(:system => "NES").count
135
+
136
+ assert_equal [@zelda], GameExtendedWithTextacular.advanced_search(:system => "NES", :title => "Zelda")
137
+ assert_equal [@megam], GameExtendedWithTextacular.advanced_search(:title => "Mega")
138
+ end
139
+
140
+ should "scope consecutively" do
141
+ assert_equal [@sfgen], GameExtendedWithTextacular.advanced_search(:system => "Genesis").advanced_search(:title => "Street Fighter")
142
+ end
143
+
144
+ should "cast non-:string columns as text" do
145
+ assert_equal [@mario], GameExtendedWithTextacular.advanced_search(:id => @mario.id)
146
+ end
147
+ end
148
+
149
+ context "when using dynamic search methods" do
150
+ should "generate methods for each :string column" do
151
+ assert_equal [@mario], GameExtendedWithTextacular.advanced_search_by_title("Mario")
152
+ assert_equal [@takun], GameExtendedWithTextacular.advanced_search_by_system("Saturn")
153
+ end
154
+
155
+ should "generate methods for each :text column" do
156
+ assert_equal [@mario], GameExtendedWithTextacular.advanced_search_by_description("platform")
157
+ end
158
+
159
+ should "generate methods for any combination of :string and :text columns" do
160
+ assert_equal [@mario], GameExtendedWithTextacular.advanced_search_by_title_and_system("Mario", "NES")
161
+ assert_equal [@sonic], GameExtendedWithTextacular.advanced_search_by_system_and_title("Genesis", "Sonic")
162
+ assert_equal [@mario], GameExtendedWithTextacular.advanced_search_by_title_and_title("Mario", "Mario")
163
+ assert_equal [@megam], GameExtendedWithTextacular.advanced_search_by_title_and_description("Man", "Brain")
164
+ end
165
+
166
+ should "generate methods for inclusive searches" do
167
+ assert_equal Set.new([@megam, @takun]), GameExtendedWithTextacular.advanced_search_by_system_or_title("Saturn", "Mega Man").to_set
168
+ end
169
+
170
+ should "scope consecutively" do
171
+ assert_equal [@sfgen], GameExtendedWithTextacular.advanced_search_by_system("Genesis").advanced_search_by_title("Street Fighter")
172
+ end
173
+
174
+ should "generate methods for non-:string columns" do
175
+ assert_equal [@mario], GameExtendedWithTextacular.advanced_search_by_id(@mario.id)
176
+ end
177
+
178
+ should "work with #respond_to?" do
179
+ assert GameExtendedWithTextacular.respond_to?(:advanced_search_by_system)
180
+ assert GameExtendedWithTextacular.respond_to?(:advanced_search_by_title)
181
+ assert GameExtendedWithTextacular.respond_to?(:advanced_search_by_system_and_title)
182
+ assert GameExtendedWithTextacular.respond_to?(:advanced_search_by_system_or_title)
183
+ assert GameExtendedWithTextacular.respond_to?(:advanced_search_by_title_and_title_and_title)
184
+ assert GameExtendedWithTextacular.respond_to?(:advanced_search_by_id)
185
+
186
+ assert !GameExtendedWithTextacular.respond_to?(:advanced_search_by_title_and_title_or_title)
187
+ end
188
+
189
+ should "allow for 2 arguments to #respond_to?" do
190
+ assert GameExtendedWithTextacular.respond_to?(:normalize, true)
191
+ end
192
+ end
193
+
194
+ context "when searching after selecting columns to return" do
195
+ should "not fetch extra columns" do
196
+ assert_raise(ActiveModel::MissingAttributeError) do
197
+ GameExtendedWithTextacular.select(:title).advanced_search("Mario").first.system
198
+ end
199
+ end
200
+ end
201
+
202
+ context "when setting a custom search language" do
203
+ setup do
204
+ GameExtendedWithTextacularAndCustomLanguage.create :system => "PS3", :title => "Harry Potter & the Deathly Hallows"
205
+ end
206
+
207
+ teardown do
208
+ GameExtendedWithTextacularAndCustomLanguage.delete_all
209
+ end
210
+
211
+ should "still find results" do
212
+ assert_not_empty GameExtendedWithTextacularAndCustomLanguage.advanced_search_by_title("harry")
213
+ end
214
+ end
215
+ end
216
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: textacular
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ben Hamill
9
+ - ecin
10
+ - Aaron Patterson
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2013-02-27 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: pg
18
+ requirement: !ruby/object:Gem::Requirement
19
+ none: false
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: 0.11.0
24
+ type: :development
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ~>
30
+ - !ruby/object:Gem::Version
31
+ version: 0.11.0
32
+ - !ruby/object:Gem::Dependency
33
+ name: shoulda
34
+ requirement: !ruby/object:Gem::Requirement
35
+ none: false
36
+ requirements:
37
+ - - ~>
38
+ - !ruby/object:Gem::Version
39
+ version: 2.11.3
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 2.11.3
48
+ - !ruby/object:Gem::Dependency
49
+ name: rake
50
+ requirement: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 0.9.0
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: 0.9.0
64
+ - !ruby/object:Gem::Dependency
65
+ name: pry
66
+ requirement: !ruby/object:Gem::Requirement
67
+ none: false
68
+ requirements:
69
+ - - ! '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ type: :development
73
+ prerelease: false
74
+ version_requirements: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ - !ruby/object:Gem::Dependency
81
+ name: pry-doc
82
+ requirement: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: activerecord
98
+ requirement: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ - - <
105
+ - !ruby/object:Gem::Version
106
+ version: '4.1'
107
+ type: :runtime
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ none: false
111
+ requirements:
112
+ - - ! '>='
113
+ - !ruby/object:Gem::Version
114
+ version: '3.0'
115
+ - - <
116
+ - !ruby/object:Gem::Version
117
+ version: '4.1'
118
+ description: ! "Textacular exposes full text search capabilities from PostgreSQL,
119
+ extending\n ActiveRecord with scopes making search easy and fun!"
120
+ email:
121
+ - git-commits@benhamill.com
122
+ - ecin@copypastel.com
123
+ executables: []
124
+ extensions: []
125
+ extra_rdoc_files: []
126
+ files:
127
+ - CHANGELOG.md
128
+ - Gemfile
129
+ - README.md
130
+ - Rakefile
131
+ - lib/textacular.rb
132
+ - lib/textacular/full_text_indexer.rb
133
+ - lib/textacular/postgres_module_installer.rb
134
+ - lib/textacular/rails.rb
135
+ - lib/textacular/searchable.rb
136
+ - lib/textacular/tasks.rb
137
+ - lib/textacular/version.rb
138
+ - spec/config.yml.example
139
+ - spec/fixtures/character.rb
140
+ - spec/fixtures/game.rb
141
+ - spec/fixtures/webcomic.rb
142
+ - spec/spec_helper.rb
143
+ - spec/textacular/searchable_spec.rb
144
+ - spec/textacular_spec.rb
145
+ homepage: http://textacular.github.com/textacular
146
+ licenses:
147
+ - MIT
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ none: false
160
+ requirements:
161
+ - - ! '>='
162
+ - !ruby/object:Gem::Version
163
+ version: '0'
164
+ requirements: []
165
+ rubyforge_project:
166
+ rubygems_version: 1.8.23
167
+ signing_key:
168
+ specification_version: 3
169
+ summary: Textacular exposes full text search capabilities from PostgreSQL
170
+ test_files:
171
+ - spec/config.yml.example
172
+ - spec/fixtures/character.rb
173
+ - spec/fixtures/game.rb
174
+ - spec/fixtures/webcomic.rb
175
+ - spec/spec_helper.rb
176
+ - spec/textacular/searchable_spec.rb
177
+ - spec/textacular_spec.rb
178
+ has_rdoc: