textacular 3.2.0 → 3.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|