textacular 3.2.0 → 3.2.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +7 -1
- data/Rakefile +5 -6
- data/lib/textacular/trigram_installer.rb +1 -1
- data/lib/textacular/version.rb +1 -1
- data/spec/config.travis.yml +8 -0
- data/spec/spec_helper.rb +93 -67
- data/spec/support/ar_stand_in.rb +4 -0
- data/spec/{fixtures → support}/character.rb +1 -0
- data/spec/{fixtures → support}/game.rb +0 -2
- data/spec/support/game_extended_with_textacular.rb +5 -0
- data/spec/support/game_extended_with_textacular_and_custom_language.rb +7 -0
- data/spec/support/game_fail.rb +3 -0
- data/spec/support/game_fail_extended_with_textacular.rb +5 -0
- data/spec/support/not_there.rb +3 -0
- data/spec/support/textacular_web_comic.rb +7 -0
- data/spec/{fixtures/webcomic.rb → support/web_comic.rb} +0 -0
- data/spec/support/web_comic_with_searchable.rb +6 -0
- data/spec/support/web_comic_with_searchable_name.rb +6 -0
- data/spec/support/web_comic_with_searchable_name_and_author.rb +6 -0
- data/spec/textacular/full_text_indexer_spec.rb +69 -0
- data/spec/textacular/migration_generator_spec.rb +67 -0
- data/spec/textacular/searchable_spec.rb +136 -55
- data/spec/textacular/trigram_installer_spec.rb +24 -0
- data/spec/textacular_spec.rb +292 -150
- metadata +54 -40
@@ -1,102 +1,183 @@
|
|
1
|
-
require '
|
2
|
-
require '
|
1
|
+
require 'support/web_comic_with_searchable'
|
2
|
+
require 'support/web_comic_with_searchable_name'
|
3
|
+
require 'support/web_comic_with_searchable_name_and_author'
|
4
|
+
require 'support/character'
|
3
5
|
|
4
|
-
|
6
|
+
RSpec.describe "Searchable" do
|
5
7
|
context "when extending an ActiveRecord::Base subclass" do
|
6
8
|
context "with no parameters" do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@null = WebComicWithSearchable.create :author => 'Foo'
|
9
|
+
let!(:questionable_content) do
|
10
|
+
WebComicWithSearchable.create(
|
11
|
+
name: 'Questionable Content',
|
12
|
+
author: 'Jeph Jaques',
|
13
|
+
)
|
13
14
|
end
|
14
15
|
|
15
|
-
|
16
|
-
WebComicWithSearchable.
|
16
|
+
let!(:johnny_wander) do
|
17
|
+
WebComicWithSearchable.create(
|
18
|
+
name: 'Johnny Wander',
|
19
|
+
author: 'Ananth & Yuko',
|
20
|
+
)
|
17
21
|
end
|
18
22
|
|
19
|
-
|
20
|
-
|
21
|
-
|
23
|
+
let!(:dominic_deegan) do
|
24
|
+
WebComicWithSearchable.create(
|
25
|
+
name: 'Dominic Deegan',
|
26
|
+
author: 'Mookie',
|
27
|
+
)
|
22
28
|
end
|
23
29
|
|
24
|
-
|
30
|
+
let!(:penny_arcade) do
|
31
|
+
WebComicWithSearchable.create(
|
32
|
+
name: 'Penny Arcade',
|
33
|
+
author: 'Tycho & Gabe',
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
let!(:null) do
|
38
|
+
WebComicWithSearchable.create(
|
39
|
+
author: 'Foo',
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "searches across all columns" do
|
44
|
+
expect(
|
45
|
+
WebComicWithSearchable.advanced_search("Penny")
|
46
|
+
).to eq([penny_arcade])
|
47
|
+
expect(
|
48
|
+
WebComicWithSearchable.advanced_search("Dominic")
|
49
|
+
).to eq([dominic_deegan])
|
50
|
+
end
|
51
|
+
|
52
|
+
it "ranks results, egen with NULL columns" do
|
25
53
|
comic = WebComicWithSearchable.basic_search('Foo').first
|
26
54
|
rank = comic.attributes.find { |key, value| key.to_s =~ /\Arank\d+\z/ }.last
|
27
55
|
|
28
|
-
|
56
|
+
expect(rank).to be_present
|
29
57
|
end
|
30
58
|
end
|
31
59
|
|
32
|
-
context "with one column as parameter" do
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
60
|
+
context "with one column as a parameter" do
|
61
|
+
let!(:questionable_content) do
|
62
|
+
WebComicWithSearchableName.create(
|
63
|
+
name: 'Questionable Content',
|
64
|
+
author: 'Jeph Jaques',
|
65
|
+
)
|
38
66
|
end
|
39
67
|
|
40
|
-
|
41
|
-
WebComicWithSearchableName.
|
68
|
+
let!(:johnny_wander) do
|
69
|
+
WebComicWithSearchableName.create(
|
70
|
+
name: 'Johnny Wander',
|
71
|
+
author: 'Ananth & Yuko',
|
72
|
+
)
|
42
73
|
end
|
43
74
|
|
44
|
-
|
45
|
-
|
46
|
-
|
75
|
+
let!(:dominic_deegan) do
|
76
|
+
WebComicWithSearchableName.create(
|
77
|
+
name: 'Dominic Deegan',
|
78
|
+
author: 'Mookie',
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
let!(:penny_arcade) do
|
83
|
+
WebComicWithSearchableName.create(
|
84
|
+
name: 'Penny Arcade',
|
85
|
+
author: 'Tycho & Gabe',
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
it "only searches across the given column" do
|
90
|
+
expect(WebComicWithSearchableName.advanced_search("Penny")).to eq([penny_arcade])
|
91
|
+
|
92
|
+
expect(WebComicWithSearchableName.advanced_search("Tycho")).to be_empty
|
47
93
|
end
|
48
94
|
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
|
95
|
+
describe "basic search" do # Uses plainto_tsquery
|
96
|
+
["hello \\", "tebow!" , "food &"].each do |search_term|
|
97
|
+
it "works with interesting term \"#{search_term}\"" do
|
98
|
+
expect(WebComicWithSearchableName.basic_search(search_term)).to be_empty
|
99
|
+
end
|
53
100
|
end
|
101
|
+
end
|
54
102
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
103
|
+
describe "advanced_search" do # Uses to_tsquery
|
104
|
+
["hello \\", "tebow!" , "food &"].each do |search_term|
|
105
|
+
it "fails with interesting term \"#{search_term}\"" do
|
106
|
+
expect {
|
107
|
+
WebComicWithSearchableName.advanced_search(search_term).first
|
108
|
+
}.to raise_error(ActiveRecord::StatementInvalid)
|
59
109
|
end
|
60
110
|
end
|
61
111
|
end
|
62
112
|
|
63
|
-
|
64
|
-
|
113
|
+
it "does fuzzy searching" do
|
114
|
+
expect(
|
115
|
+
WebComicWithSearchableName.fuzzy_search('Questio')
|
116
|
+
).to eq([questionable_content])
|
65
117
|
end
|
66
118
|
|
67
|
-
|
68
|
-
|
119
|
+
it "defines :searchable_columns as private" do
|
120
|
+
expect { WebComicWithSearchableName.searchable_columns }.to raise_error(NoMethodError)
|
121
|
+
|
69
122
|
begin
|
70
123
|
WebComicWithSearchableName.searchable_columns
|
71
124
|
rescue NoMethodError => error
|
72
|
-
|
125
|
+
expect(error.message).to match(/private method/)
|
73
126
|
end
|
74
127
|
end
|
75
128
|
|
76
|
-
|
77
|
-
|
78
|
-
|
129
|
+
it "defines #indexable_columns which returns a write-proof Enumerable" do
|
130
|
+
expect(WebComicWithSearchableName.indexable_columns).to be_an(Enumerator)
|
131
|
+
|
132
|
+
expect {
|
133
|
+
WebComicWithSearchableName.indexable_columns[0] = 'foo'
|
134
|
+
}.to raise_error(NoMethodError)
|
79
135
|
end
|
80
136
|
end
|
81
137
|
|
82
138
|
context "with two columns as parameters" do
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
139
|
+
let!(:questionable_content) do
|
140
|
+
WebComicWithSearchableNameAndAuthor.create(
|
141
|
+
name: 'Questionable Content',
|
142
|
+
author: 'Jeph Jaques',
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
let!(:johnny_wander) do
|
147
|
+
WebComicWithSearchableNameAndAuthor.create(
|
148
|
+
name: 'Johnny Wander',
|
149
|
+
author: 'Ananth & Yuko',
|
150
|
+
)
|
88
151
|
end
|
89
152
|
|
90
|
-
|
91
|
-
WebComicWithSearchableNameAndAuthor.
|
153
|
+
let!(:dominic_deegan) do
|
154
|
+
WebComicWithSearchableNameAndAuthor.create(
|
155
|
+
name: 'Dominic Deegan',
|
156
|
+
author: 'Mookie',
|
157
|
+
)
|
92
158
|
end
|
93
159
|
|
94
|
-
|
95
|
-
|
96
|
-
|
160
|
+
let!(:penny_arcade) do
|
161
|
+
WebComicWithSearchableNameAndAuthor.create(
|
162
|
+
name: 'Penny Arcade',
|
163
|
+
author: 'Tycho & Gabe',
|
164
|
+
)
|
97
165
|
end
|
98
|
-
|
99
|
-
|
166
|
+
|
167
|
+
it "only searches across the given columns" do
|
168
|
+
expect(
|
169
|
+
WebComicWithSearchableNameAndAuthor.advanced_search("Penny")
|
170
|
+
).to eq([penny_arcade])
|
171
|
+
|
172
|
+
expect(
|
173
|
+
WebComicWithSearchableNameAndAuthor.advanced_search("Tycho")
|
174
|
+
).to eq([penny_arcade])
|
175
|
+
end
|
176
|
+
|
177
|
+
it "allows includes" do
|
178
|
+
expect(
|
179
|
+
WebComicWithSearchableNameAndAuthor.includes(:characters).advanced_search("Penny")
|
180
|
+
).to eq([penny_arcade])
|
100
181
|
end
|
101
182
|
end
|
102
183
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
RSpec.describe "Textacular::TrigramInstaller" do
|
2
|
+
let(:content) do
|
3
|
+
<<-MIGRATION
|
4
|
+
class InstallTrigram < ActiveRecord::Migration
|
5
|
+
def self.up
|
6
|
+
ActiveRecord::Base.connection.execute("CREATE EXTENSION pg_trgm;")
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.down
|
10
|
+
ActiveRecord::Base.connection.execute("DROP EXTENSION pg_trgm;")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
MIGRATION
|
14
|
+
end
|
15
|
+
|
16
|
+
it "generates a migration" do
|
17
|
+
generator = double(:migration_generator)
|
18
|
+
|
19
|
+
expect(Textacular::MigrationGenerator).to receive(:new).with('install_trigram', content).and_return(generator)
|
20
|
+
expect(generator).to receive(:generate_migration)
|
21
|
+
|
22
|
+
Textacular::TrigramInstaller.new.generate_migration
|
23
|
+
end
|
24
|
+
end
|
data/spec/textacular_spec.rb
CHANGED
@@ -1,215 +1,357 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
|
2
|
+
# Above for Ruby 1.9 tests
|
3
3
|
|
4
|
-
|
4
|
+
require 'support/ar_stand_in'
|
5
|
+
require 'support/not_there'
|
6
|
+
require 'support/textacular_web_comic'
|
7
|
+
require 'support/game_extended_with_textacular'
|
8
|
+
require 'support/game_extended_with_textacular_and_custom_language'
|
9
|
+
require 'support/game_fail_extended_with_textacular'
|
10
|
+
|
11
|
+
RSpec.describe Textacular do
|
5
12
|
context "after extending ActiveRecord::Base" do
|
6
|
-
|
7
|
-
|
8
|
-
ARStandIn.respond_to? :abstract_class?
|
9
|
-
end
|
13
|
+
it "doesn't break #respond_to?" do
|
14
|
+
expect{ ARStandIn.respond_to?(:abstract_class?) }.to_not raise_error
|
10
15
|
end
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
NotThere.respond_to? :system
|
16
|
-
end
|
17
|
+
it "doesn't break #respond_to? for table-less classes" do
|
18
|
+
expect(NotThere.table_exists?).to be_falsey
|
19
|
+
expect { NotThere.respond_to? :system }.to_not raise_error
|
17
20
|
end
|
18
21
|
|
19
|
-
|
20
|
-
|
22
|
+
it "doesn't break #method_missing" do
|
23
|
+
expect { ARStandIn.random }.to raise_error(NoMethodError)
|
24
|
+
|
21
25
|
begin
|
22
26
|
ARStandIn.random
|
23
27
|
rescue NoMethodError => error
|
24
|
-
|
28
|
+
expect(error.message).to match(/undefined method `random'/)
|
25
29
|
end
|
26
30
|
end
|
27
31
|
|
28
|
-
|
29
|
-
|
30
|
-
|
32
|
+
it "doesn't break #method_missing for table-less classes" do
|
33
|
+
expect(NotThere.table_exists?).to be_falsey
|
34
|
+
|
35
|
+
expect { NotThere.random }.to raise_error(NoMethodError)
|
36
|
+
|
31
37
|
begin
|
32
38
|
NotThere.random
|
33
39
|
rescue NoMethodError => error
|
34
|
-
|
40
|
+
expect(error.message).to match(/undefined method `random'/)
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
38
44
|
context "when finding models based on searching a related model" do
|
39
|
-
|
40
|
-
|
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!'
|
45
|
+
let(:webcomics_with_tall_characters) do
|
46
|
+
[johnny_wander]
|
56
47
|
end
|
57
48
|
|
58
|
-
|
59
|
-
|
60
|
-
Character.delete_all
|
49
|
+
let(:webcomics_with_angry_characters) do
|
50
|
+
[johnny_wander, penny_arcade, questionable_content]
|
61
51
|
end
|
62
52
|
|
63
|
-
|
64
|
-
|
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
|
53
|
+
let(:webcomics_with_crude_characters) do
|
54
|
+
[penny_arcade, questionable_content]
|
67
55
|
end
|
68
|
-
end
|
69
|
-
end
|
70
56
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
57
|
+
let!(:johnny_wander) do
|
58
|
+
TextacularWebComic.create(:name => "Johnny Wander", :author => "Ananth & Yuko").tap do |comic|
|
59
|
+
comic.characters.create :name => 'Ananth', :description => 'Stubble! What is under that hat?!?'
|
60
|
+
comic.characters.create :name => 'Yuko', :description => 'So... small. Carl Sagan haircut.'
|
61
|
+
comic.characters.create :name => 'John', :description => 'Tall. Anger issues?'
|
62
|
+
comic.characters.create :name => 'Cricket', :description => 'Chirrup!'
|
63
|
+
end
|
103
64
|
end
|
104
65
|
|
105
|
-
|
106
|
-
|
66
|
+
let!(:questionable_content) do
|
67
|
+
TextacularWebComic.create(:name => "Questionable Content", :author => "Jeph Jaques").tap do |comic|
|
68
|
+
comic.characters.create :name => 'Martin', :description => 'the insecure protagonist'
|
69
|
+
comic.characters.create :name => 'Faye', :description => 'a sarcastic barrista with anger management issues'
|
70
|
+
comic.characters.create :name => 'Pintsize', :description => 'a crude AnthroPC'
|
71
|
+
end
|
107
72
|
end
|
108
73
|
|
109
|
-
|
110
|
-
|
74
|
+
let!(:penny_arcade) do
|
75
|
+
TextacularWebComic.create(:name => "Penny Arcade", :author => "Tycho & Gabe").tap do |comic|
|
76
|
+
comic.characters.create :name => 'Gabe', :description => 'the simple one'
|
77
|
+
comic.characters.create :name => 'Tycho', :description => 'the wordy one'
|
78
|
+
comic.characters.create :name => 'Div', :description => 'a crude divx player with anger management issues'
|
79
|
+
end
|
111
80
|
end
|
112
81
|
|
113
|
-
|
114
|
-
|
82
|
+
it "looks in the related model with nested searching syntax" do
|
83
|
+
expect(
|
84
|
+
TextacularWebComic.joins(:characters).advanced_search(
|
85
|
+
:characters => {:description => 'tall'}
|
86
|
+
)
|
87
|
+
).to eq(webcomics_with_tall_characters)
|
88
|
+
|
89
|
+
expect(
|
90
|
+
TextacularWebComic.joins(:characters).advanced_search(
|
91
|
+
:characters => {:description => 'anger'}
|
92
|
+
).sort
|
93
|
+
).to eq(webcomics_with_angry_characters.sort)
|
94
|
+
|
95
|
+
expect(
|
96
|
+
TextacularWebComic.joins(:characters).advanced_search(
|
97
|
+
:characters => {:description => 'crude'}
|
98
|
+
).sort
|
99
|
+
).to eq(webcomics_with_crude_characters.sort)
|
115
100
|
end
|
101
|
+
end
|
102
|
+
end
|
116
103
|
|
117
|
-
|
118
|
-
|
104
|
+
context "after extending an ActiveRecord::Base subclass" do
|
105
|
+
context "when the DB connection is unavailable" do
|
106
|
+
before do
|
107
|
+
GameFailExtendedWithTextacular.establish_connection({:adapter => :postgresql, :database =>'unavailable', :username=>'bad', :pool=>5, :timeout=>5000}) rescue nil
|
119
108
|
end
|
120
109
|
|
121
|
-
|
122
|
-
|
110
|
+
it "doesn't break respond_to?" do
|
111
|
+
expect { GameFailExtendedWithTextacular.respond_to?(:advanced_search) }.to_not raise_error
|
123
112
|
end
|
124
113
|
end
|
125
114
|
|
126
|
-
context "when
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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")
|
115
|
+
context "when the DB connection is available" do
|
116
|
+
let!(:zelda) do
|
117
|
+
GameExtendedWithTextacular.create(
|
118
|
+
:system => "NES",
|
119
|
+
:title => "Legend of Zelda",
|
120
|
+
:description => "A Link to the Past."
|
121
|
+
)
|
138
122
|
end
|
139
123
|
|
140
|
-
|
141
|
-
|
124
|
+
let!(:mario) do
|
125
|
+
GameExtendedWithTextacular.create(
|
126
|
+
:system => "NES",
|
127
|
+
:title => "Super Mario Bros.",
|
128
|
+
:description => "The original platformer."
|
129
|
+
)
|
142
130
|
end
|
143
131
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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")
|
132
|
+
let!(:sonic) do
|
133
|
+
GameExtendedWithTextacular.create(
|
134
|
+
:system => "Genesis",
|
135
|
+
:title => "Sonic the Hedgehog",
|
136
|
+
:description => "Spiky."
|
137
|
+
)
|
153
138
|
end
|
154
139
|
|
155
|
-
|
156
|
-
|
140
|
+
let!(:donkey_kong) do
|
141
|
+
GameExtendedWithTextacular.create(
|
142
|
+
:system => "SNES",
|
143
|
+
:title => "Diddy's Kong Quest",
|
144
|
+
:description => "Donkey Kong Country 2"
|
145
|
+
)
|
157
146
|
end
|
158
147
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
148
|
+
let!(:mega_man) do
|
149
|
+
GameExtendedWithTextacular.create(
|
150
|
+
:system => nil,
|
151
|
+
:title => "Mega Man",
|
152
|
+
:description => "Beware Dr. Brain"
|
153
|
+
)
|
164
154
|
end
|
165
155
|
|
166
|
-
|
167
|
-
|
156
|
+
let!(:sf_nes) do
|
157
|
+
GameExtendedWithTextacular.create(
|
158
|
+
:system => "SNES",
|
159
|
+
:title => "Street Fighter 2",
|
160
|
+
:description => "Yoga Flame!"
|
161
|
+
)
|
168
162
|
end
|
169
163
|
|
170
|
-
|
171
|
-
|
164
|
+
let!(:sf_genesis) do
|
165
|
+
GameExtendedWithTextacular.create(
|
166
|
+
:system => "Genesis",
|
167
|
+
:title => "Street Fighter 2",
|
168
|
+
:description => "Yoga Flame!"
|
169
|
+
)
|
172
170
|
end
|
173
171
|
|
174
|
-
|
175
|
-
|
172
|
+
let!(:takun) do
|
173
|
+
GameExtendedWithTextacular.create(
|
174
|
+
:system => "Saturn",
|
175
|
+
:title => "Magical Tarurūto-kun",
|
176
|
+
:description => "カッコイイ!"
|
177
|
+
)
|
176
178
|
end
|
177
179
|
|
178
|
-
|
179
|
-
|
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)
|
180
|
+
it "defines a #search method" do
|
181
|
+
expect(GameExtendedWithTextacular).to respond_to(:search)
|
187
182
|
end
|
188
183
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
184
|
+
describe "#advanced_search" do
|
185
|
+
context "with a String argument" do
|
186
|
+
it "searches across all :string columns (if not indexes have been specified)" do
|
187
|
+
expect(
|
188
|
+
GameExtendedWithTextacular.advanced_search("Mario")
|
189
|
+
).to eq([mario])
|
190
|
+
|
191
|
+
expect(
|
192
|
+
GameExtendedWithTextacular.advanced_search("NES").to_set
|
193
|
+
).to eq(Set.new([mario, zelda]))
|
194
|
+
end
|
195
|
+
|
196
|
+
it "works if a query has an apostrophe" do
|
197
|
+
expect(GameExtendedWithTextacular.advanced_search("Diddy's")).to eq([donkey_kong])
|
198
|
+
end
|
199
|
+
|
200
|
+
it "works if the query contains whitespace" do
|
201
|
+
expect(GameExtendedWithTextacular.advanced_search("Mega Man")).to eq([mega_man])
|
202
|
+
end
|
203
|
+
|
204
|
+
it "works if the query contains an accent" do
|
205
|
+
expect(GameExtendedWithTextacular.advanced_search("Tarurūto-kun")).to eq([takun])
|
206
|
+
end
|
207
|
+
|
208
|
+
it "searches across records with NULL values" do
|
209
|
+
expect(GameExtendedWithTextacular.advanced_search("Mega")).to eq([mega_man])
|
210
|
+
end
|
211
|
+
|
212
|
+
it "scopes consecutively" do
|
213
|
+
expect(
|
214
|
+
GameExtendedWithTextacular.advanced_search("Genesis").advanced_search("Street Fighter")
|
215
|
+
).to eq([sf_genesis])
|
216
|
+
end
|
217
|
+
end
|
193
218
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
219
|
+
context "with a Hash argument" do
|
220
|
+
it "searches across the given columns" do
|
221
|
+
expect(
|
222
|
+
GameExtendedWithTextacular.advanced_search(:title => 'NES')
|
223
|
+
).to be_empty
|
224
|
+
expect(
|
225
|
+
GameExtendedWithTextacular.advanced_search(:system => "Mario")
|
226
|
+
).to be_empty
|
227
|
+
expect(
|
228
|
+
GameExtendedWithTextacular.advanced_search(:system => "NES", :title => "Sonic")
|
229
|
+
).to be_empty
|
230
|
+
|
231
|
+
expect(
|
232
|
+
GameExtendedWithTextacular.advanced_search(:title => "Mario")
|
233
|
+
).to eq([mario])
|
234
|
+
|
235
|
+
expect(
|
236
|
+
GameExtendedWithTextacular.advanced_search(:system => "NES").size
|
237
|
+
).to eq(2)
|
238
|
+
|
239
|
+
expect(
|
240
|
+
GameExtendedWithTextacular.advanced_search(:system => "NES", :title => "Zelda")
|
241
|
+
).to eq([zelda])
|
242
|
+
expect(
|
243
|
+
GameExtendedWithTextacular.advanced_search(:title => "Mega")
|
244
|
+
).to eq([mega_man])
|
245
|
+
end
|
246
|
+
|
247
|
+
it "scopes consecutively" do
|
248
|
+
expect(
|
249
|
+
GameExtendedWithTextacular
|
250
|
+
.advanced_search(:system => "Genesis")
|
251
|
+
.advanced_search(:title => "Street Fighter")
|
252
|
+
).to eq([sf_genesis])
|
253
|
+
end
|
254
|
+
|
255
|
+
it "casts non-string columns as text" do
|
256
|
+
expect(
|
257
|
+
GameExtendedWithTextacular.advanced_search(:id => mario.id)
|
258
|
+
).to eq([mario])
|
259
|
+
end
|
198
260
|
end
|
199
|
-
end
|
200
|
-
end
|
201
261
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
262
|
+
context "via dynamic method names" do
|
263
|
+
it "only exists prior to 4.0.0" do
|
264
|
+
current_version = Gem::Version.new(Textacular.version)
|
265
|
+
|
266
|
+
expect(current_version).to be < Gem::Version.new("4.0.0")
|
267
|
+
end
|
268
|
+
|
269
|
+
it "generates methods for each string column" do
|
270
|
+
expect(
|
271
|
+
GameExtendedWithTextacular.advanced_search_by_title("Mario")
|
272
|
+
).to eq([mario])
|
273
|
+
expect(
|
274
|
+
GameExtendedWithTextacular.advanced_search_by_system("Saturn")
|
275
|
+
).to eq([takun])
|
276
|
+
end
|
277
|
+
|
278
|
+
it "generates methods for each text column" do
|
279
|
+
expect(
|
280
|
+
GameExtendedWithTextacular.advanced_search_by_description("platform")
|
281
|
+
).to eq([mario])
|
282
|
+
end
|
283
|
+
|
284
|
+
it "generates methods for any combination of string and text columns" do
|
285
|
+
expect(
|
286
|
+
GameExtendedWithTextacular.advanced_search_by_title_and_system("Mario", "NES")
|
287
|
+
).to eq([mario])
|
288
|
+
expect(
|
289
|
+
GameExtendedWithTextacular.advanced_search_by_system_and_title("Genesis", "Sonic")
|
290
|
+
).to eq([sonic])
|
291
|
+
expect(
|
292
|
+
GameExtendedWithTextacular.advanced_search_by_title_and_title("Mario", "Mario")
|
293
|
+
).to eq([mario])
|
294
|
+
expect(
|
295
|
+
GameExtendedWithTextacular.advanced_search_by_title_and_description("Man", "Brain")
|
296
|
+
).to eq([mega_man])
|
297
|
+
end
|
298
|
+
|
299
|
+
it "generates methods for inclusive searches" do
|
300
|
+
expect(
|
301
|
+
GameExtendedWithTextacular.advanced_search_by_system_or_title("Saturn", "Mega Man").to_set
|
302
|
+
).to eq(Set.new([mega_man, takun]))
|
303
|
+
end
|
304
|
+
|
305
|
+
it "scopes consecutively" do
|
306
|
+
expect(
|
307
|
+
GameExtendedWithTextacular.advanced_search_by_system("Genesis").advanced_search_by_title("Street Fighter")
|
308
|
+
).to eq([sf_genesis])
|
309
|
+
end
|
310
|
+
|
311
|
+
it "generates methods for non-string columns" do
|
312
|
+
expect(
|
313
|
+
GameExtendedWithTextacular.advanced_search_by_id(mario.id)
|
314
|
+
).to eq([mario])
|
315
|
+
end
|
316
|
+
|
317
|
+
it "works with #respond_to?" do
|
318
|
+
expect(GameExtendedWithTextacular).to respond_to(:advanced_search_by_system)
|
319
|
+
expect(GameExtendedWithTextacular).to respond_to(:advanced_search_by_title)
|
320
|
+
expect(GameExtendedWithTextacular).to respond_to(:advanced_search_by_system_and_title)
|
321
|
+
expect(GameExtendedWithTextacular).to respond_to(:advanced_search_by_system_or_title)
|
322
|
+
expect(GameExtendedWithTextacular).to respond_to(:advanced_search_by_title_and_title_and_title)
|
323
|
+
expect(GameExtendedWithTextacular).to respond_to(:advanced_search_by_id)
|
324
|
+
|
325
|
+
expect(GameExtendedWithTextacular).to_not respond_to(:advanced_search_by_title_and_title_or_title)
|
326
|
+
end
|
327
|
+
|
328
|
+
it "allows for 2 arguments to #respond_to?" do
|
329
|
+
expect(GameExtendedWithTextacular.respond_to?(:searchable_language, true)).to be_truthy
|
330
|
+
end
|
331
|
+
end
|
206
332
|
|
207
|
-
|
208
|
-
|
209
|
-
|
333
|
+
context "after selecting columns to return" do
|
334
|
+
it "doesn't fetch extra columns" do
|
335
|
+
expect {
|
336
|
+
GameExtendedWithTextacular.select(:title).advanced_search("Mario").first.system
|
337
|
+
}.to raise_error(ActiveModel::MissingAttributeError)
|
338
|
+
end
|
339
|
+
end
|
210
340
|
|
211
|
-
|
212
|
-
|
341
|
+
context "after setting a custom language" do
|
342
|
+
let!(:harry_potter_7) do
|
343
|
+
GameExtendedWithTextacularAndCustomLanguage.create(
|
344
|
+
:system => "PS3",
|
345
|
+
:title => "Harry Potter & the Deathly Hallows"
|
346
|
+
)
|
347
|
+
end
|
348
|
+
|
349
|
+
it "finds results" do
|
350
|
+
expect(
|
351
|
+
GameExtendedWithTextacularAndCustomLanguage.advanced_search_by_title("harry")
|
352
|
+
).to eq([harry_potter_7])
|
353
|
+
end
|
354
|
+
end
|
213
355
|
end
|
214
356
|
end
|
215
357
|
end
|