textacular 5.1.0 → 5.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 3bd98a4fec2d72714909b49059a7f8b50298d734
4
- data.tar.gz: 9b3fc608d5192296392da8323048c65440146cbd
2
+ SHA256:
3
+ metadata.gz: 974b8a8209d44a134f421db20f9ae6338c03057d8bcf26ce1e0c40778a29d6bf
4
+ data.tar.gz: 422c096f881919e53eaaab5f530e26e0e5aeffe33a4c8646bcb7f3b367dc9084
5
5
  SHA512:
6
- metadata.gz: 7e1cd1fdce05d21df1e9803ca0bd1db66fc18ba371a4980175824ad5c46df5eb1e4cfd1aed857e5cea384887734f7f563ce498cc860c8c8827873f669a9ca584
7
- data.tar.gz: 6237664f59c95a81b48283b3f761289a8c63199c908fba347e3931be9f93c59f5be897c971434266a7d1a203dfc3aa11af217736ca2e3313cea180c73c0d16c3
6
+ metadata.gz: ab93dff1128025cc4615d29c819d104a9300bdbb0a88d9cc588940a1f630922a201b9f11449d610ba5cadc5ae85c2e761c0ad3ff5b8cb7a85f1fd3dd15acf744
7
+ data.tar.gz: 2102ea9936a087f18ed4945ddb4213e93acb70dc4bc8d3281489664fda2968b635e024636f0f2112f0fa8fc7a13b5d2b7e728aa200ac7663b681ae287b7e6b5d
data/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 5.5.0
6
+
7
+ * ActiveRecord 7.0 compatibility
8
+
9
+ ## 5.4.0
10
+
11
+ * ActiveRecord 6.1 compatibility
12
+
13
+ ## 5.3.0
14
+
15
+ * Add `#web_search` method to use Postgres' 11+ `websearch_to_tsquery`
16
+
17
+ ## 5.2.0
18
+
19
+ * Active Record 6.0 compatibility
20
+
5
21
  ## 5.1.0
6
22
 
7
23
  * ActiveRecord 5.2 compatibility by wrapping string queries with `Arel.sql()`
data/Gemfile CHANGED
@@ -2,4 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'activerecord', '~> 5.2.x'
5
+ git 'git://github.com/rails/rails.git', branch: 'main' do
6
+ gem 'activerecord'
7
+ end
8
+
9
+ gem "pg", "~> 1.1"
data/README.md CHANGED
@@ -25,20 +25,18 @@ extending ActiveRecord with scopes making search easy and fun!
25
25
 
26
26
  ### Quick Start
27
27
 
28
- #### Rails 3, Rails 4
29
-
30
28
  In the project's Gemfile add
31
29
 
32
30
  ```ruby
33
- gem 'textacular', '~> 4.0'
31
+ gem 'textacular', '~> 5.0'
34
32
  ```
35
33
 
36
- #### Rails 5.0 and Rails 5.1!
34
+ #### Rails 3, Rails 4
37
35
 
38
36
  In the project's Gemfile add
39
37
 
40
38
  ```ruby
41
- gem 'textacular', '~> 5.0'
39
+ gem 'textacular', '~> 4.0'
42
40
  ```
43
41
 
44
42
  #### ActiveRecord outside of Rails
@@ -75,6 +73,20 @@ Game.advanced_search(title: 'Street|Fantasy')
75
73
  Game.advanced_search(system: '!PS2')
76
74
  ```
77
75
 
76
+ The `#web_search` method lets you use Postgres' 11+ `websearch_to_tsquery` function
77
+ supporting websearch like syntax:
78
+
79
+ - unquoted text: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery.
80
+ - "quoted text": text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery.
81
+ - OR: logical or will be converted to the | operator.
82
+ - -: the logical not operator, converted to the the ! operator.
83
+
84
+ ```ruby
85
+ Game.web_search(title: '"Street Fantasy"')
86
+ Game.web_search(title: 'Street OR Fantasy')
87
+ Game.web_search(system: '-PS2')
88
+ ```
89
+
78
90
  Finally, the `#fuzzy_search` method lets you use Postgres's trigram search
79
91
  functionality.
80
92
 
@@ -150,29 +162,20 @@ migration add code like the following:
150
162
 
151
163
  #### For basic_search
152
164
  ```ruby
153
- execute "
154
- create index on email_logs using gin(to_tsvector('english', subject));
155
- create index on email_logs using gin(to_tsvector('english', email_address));"
165
+ add_index :email_logs, %{to_tsvector('english', subject)}, using: :gin
166
+ add_index :email_logs, %{to_tsvector('english', email_address)}, using: :gin
156
167
  ```
157
168
 
158
169
  #### For fuzzy_search
159
170
  ```ruby
160
- execute "
161
- CREATE INDEX trgm_subject_indx ON users USING gist (subject gist_trgm_ops);
162
- CREATE INDEX trgm_email_address_indx ON users USING gist (email_address gist_trgm_ops);
171
+ add_index :email_logs, :subject, using: :gist, opclass: :gist_trgm_ops
172
+ add_index :email_logs, :email_address, using: :gist, opclass: :gist_trgm_ops
163
173
  ```
164
174
 
165
175
  In the above example, the table email_logs has two text columns that we search against, subject and email_address.
166
176
  You will need to add an index for every text/string column you query against, or else Postgresql will revert to a
167
177
  full table scan instead of using the indexes.
168
178
 
169
- If you create these indexes, you should also switch to sql for your schema_format in `config/application.rb`:
170
-
171
- ```ruby
172
- config.active_record.schema_format = :sql
173
- ```
174
-
175
-
176
179
  ## REQUIREMENTS:
177
180
 
178
181
  * ActiveRecord
data/Rakefile CHANGED
@@ -43,12 +43,18 @@ namespace :db do
43
43
 
44
44
  desc 'Run the test database migrations'
45
45
  task :up => :'db:connect' do
46
- migrations = if ActiveRecord.version.version >= '5.2'
47
- ActiveRecord::Migration.new.migration_context.migrations
46
+ if ActiveRecord.version >= Gem::Version.new('6.0.0')
47
+ context = ActiveRecord::Migration.new.migration_context
48
+ migrations = context.migrations
49
+ schema_migration = context.schema_migration
50
+ elsif ActiveRecord.version >= Gem::Version.new('5.2')
51
+ migrations = ActiveRecord::Migration.new.migration_context.migrations
52
+ schema_migration = nil
48
53
  else
49
- ActiveRecord::Migrator.migrations('db/migrate')
54
+ migrations = ActiveRecord::Migrator.migrations('db/migrate')
55
+ schema_migration = nil
50
56
  end
51
- ActiveRecord::Migrator.new(:up, migrations, nil).migrate
57
+ ActiveRecord::Migrator.new(:up, migrations, schema_migration).migrate
52
58
  end
53
59
 
54
60
  desc 'Reverse the test database migrations'
@@ -1,5 +1,5 @@
1
1
  module Textacular
2
- VERSION = '5.1.0'
2
+ VERSION = '5.5.0'
3
3
 
4
4
  def self.version
5
5
  VERSION
data/lib/textacular.rb CHANGED
@@ -12,29 +12,36 @@ module Textacular
12
12
  'english'
13
13
  end
14
14
 
15
- def search(query = "", exclusive = true)
16
- basic_search(query, exclusive)
15
+ def search(query = "", exclusive = true, rank_alias = nil)
16
+ basic_search(query, exclusive, rank_alias)
17
17
  end
18
18
 
19
- def basic_search(query = "", exclusive = true)
19
+ def basic_search(query = "", exclusive = true, rank_alias = nil)
20
20
  exclusive, query = munge_exclusive_and_query(exclusive, query)
21
21
  parsed_query_hash = parse_query_hash(query)
22
22
  similarities, conditions = basic_similarities_and_conditions(parsed_query_hash)
23
- assemble_query(similarities, conditions, exclusive)
23
+ assemble_query(similarities, conditions, exclusive, rank_alias)
24
24
  end
25
25
 
26
- def advanced_search(query = "", exclusive = true)
26
+ def advanced_search(query = "", exclusive = true, rank_alias = nil)
27
27
  exclusive, query = munge_exclusive_and_query(exclusive, query)
28
28
  parsed_query_hash = parse_query_hash(query)
29
29
  similarities, conditions = advanced_similarities_and_conditions(parsed_query_hash)
30
- assemble_query(similarities, conditions, exclusive)
30
+ assemble_query(similarities, conditions, exclusive, rank_alias)
31
31
  end
32
32
 
33
- def fuzzy_search(query = '', exclusive = true)
33
+ def fuzzy_search(query = '', exclusive = true, rank_alias = nil)
34
34
  exclusive, query = munge_exclusive_and_query(exclusive, query)
35
35
  parsed_query_hash = parse_query_hash(query)
36
36
  similarities, conditions = fuzzy_similarities_and_conditions(parsed_query_hash)
37
- assemble_query(similarities, conditions, exclusive)
37
+ assemble_query(similarities, conditions, exclusive, rank_alias)
38
+ end
39
+
40
+ def web_search(query = '', exclusive = true, rank_alias = nil)
41
+ exclusive, query = munge_exclusive_and_query(exclusive, query)
42
+ parsed_query_hash = parse_query_hash(query)
43
+ similarities, conditions = web_similarities_and_conditions(parsed_query_hash)
44
+ assemble_query(similarities, conditions, exclusive, rank_alias)
38
45
  end
39
46
 
40
47
  private
@@ -113,15 +120,38 @@ module Textacular
113
120
  end
114
121
 
115
122
  def fuzzy_similarity_string(table_name, column, search_term)
116
- "COALESCE(similarity(#{table_name}.#{column}, #{search_term}), 0)"
123
+ "COALESCE(similarity(#{table_name}.#{column}::text, #{search_term}), 0)"
117
124
  end
118
125
 
119
126
  def fuzzy_condition_string(table_name, column, search_term)
120
- "(#{table_name}.#{column} % #{search_term})"
127
+ # At this point, search_term is already quoted and query ready. Insert % between the quotes and the actual string.
128
+ search_term = search_term.gsub(/^(['"])/, '\1%')
129
+ search_term = search_term.gsub(/(['"])$/, '%\1')
130
+
131
+ "(#{table_name}.#{column}::text ILIKE #{search_term})"
132
+ end
133
+
134
+
135
+ def web_similarities_and_conditions(parsed_query_hash)
136
+ parsed_query_hash.inject([[], []]) do |(similarities, conditions), query_args|
137
+ similarities << web_similarity_string(*query_args)
138
+ conditions << web_condition_string(*query_args)
139
+
140
+ [similarities, conditions]
141
+ end
142
+ end
143
+
144
+ def web_similarity_string(table_name, column, search_term)
145
+ "COALESCE(ts_rank(to_tsvector(#{quoted_language}, #{table_name}.#{column}::text), websearch_to_tsquery(#{quoted_language}, #{search_term}::text)), 0)"
146
+ end
147
+
148
+ def web_condition_string(table_name, column, search_term)
149
+ "to_tsvector(#{quoted_language}, #{table_name}.#{column}::text) @@ websearch_to_tsquery(#{quoted_language}, #{search_term}::text)"
121
150
  end
122
151
 
123
- def assemble_query(similarities, conditions, exclusive)
124
- rank = connection.quote_column_name('rank' + rand(100000000000000000).to_s)
152
+ def assemble_query(similarities, conditions, exclusive, rank_alias)
153
+ rank_alias ||= 'rank' + rand(100000000000000000).to_s
154
+ rank = connection.quote_column_name(rank_alias)
125
155
 
126
156
  select(Arel.sql("#{quoted_table_name + '.*,' if select_values.empty?} #{similarities.join(" + ")} AS #{rank}")).
127
157
  where(conditions.join(exclusive ? " AND " : " OR ")).
@@ -3,6 +3,6 @@ timeout: 5000
3
3
  host: localhost
4
4
  adapter: postgresql
5
5
  username: postgres
6
- password:
6
+ password: password
7
7
  database: textacular_test
8
- min_messages: ERROR
8
+ min_messages: ERROR
@@ -113,6 +113,14 @@ RSpec.describe "Searchable" do
113
113
  end
114
114
  end
115
115
 
116
+ describe "web search" do # Uses websearch_to_tsquery
117
+ ["hello \\", "tebow!" , "food &"].each do |search_term|
118
+ it "works with interesting term \"#{search_term}\"" do
119
+ expect(WebComicWithSearchableName.web_search(search_term)).to be_empty
120
+ end
121
+ end
122
+ end
123
+
116
124
  it "does fuzzy searching" do
117
125
  expect(
118
126
  WebComicWithSearchableName.fuzzy_search('Questio')
@@ -182,6 +190,14 @@ RSpec.describe "Searchable" do
182
190
  expect(
183
191
  WebComicWithSearchableNameAndAuthor.advanced_search("Tycho")
184
192
  ).to eq([penny_arcade])
193
+
194
+ expect(
195
+ WebComicWithSearchableNameAndAuthor.web_search("Penny")
196
+ ).to eq([penny_arcade])
197
+
198
+ expect(
199
+ WebComicWithSearchableNameAndAuthor.web_search("Tycho")
200
+ ).to eq([penny_arcade])
185
201
  end
186
202
 
187
203
  it "allows includes" do
@@ -190,5 +206,39 @@ RSpec.describe "Searchable" do
190
206
  ).to eq([penny_arcade])
191
207
  end
192
208
  end
209
+
210
+ context 'custom rank' do
211
+ let!(:questionable_content) do
212
+ WebComicWithSearchableName.create(
213
+ name: 'Questionable Content',
214
+ author: nil,
215
+ )
216
+ end
217
+
218
+ it "is selected for search" do
219
+ search_result = WebComicWithSearchableNameAndAuthor.search('Questionable Content', true, 'my_rank')
220
+ expect(search_result.first.attributes['my_rank']).to be_truthy
221
+ end
222
+
223
+ it "is selected for basic_search" do
224
+ search_result = WebComicWithSearchableNameAndAuthor.basic_search('Questionable Content', true, 'my_rank')
225
+ expect(search_result.first.attributes['my_rank']).to be_truthy
226
+ end
227
+
228
+ it "is selected for advanced_search" do
229
+ search_result = WebComicWithSearchableNameAndAuthor.advanced_search('Questionable Content', true, 'my_rank')
230
+ expect(search_result.first.attributes['my_rank']).to be_truthy
231
+ end
232
+
233
+ it "is selected for fuzzy_search" do
234
+ search_result = WebComicWithSearchableNameAndAuthor.fuzzy_search('Questionable Content', true, 'my_rank')
235
+ expect(search_result.first.attributes['my_rank']).to be_truthy
236
+ end
237
+
238
+ it "is selected for web_search" do
239
+ search_result = WebComicWithSearchableNameAndAuthor.web_search('Questionable Content', true, 'my_rank')
240
+ expect(search_result.first.attributes['my_rank']).to be_truthy
241
+ end
242
+ end
193
243
  end
194
244
  end
@@ -181,6 +181,13 @@ RSpec.describe Textacular do
181
181
  expect(GameExtendedWithTextacular).to respond_to(:search)
182
182
  end
183
183
 
184
+ describe "#fuzzy_search" do
185
+ it 'searches non-text columns' do
186
+ expect(GameExtendedWithTextacular.fuzzy_search(id: mario.id)
187
+ ).to eq([mario])
188
+ end
189
+ end
190
+
184
191
  describe "#advanced_search" do
185
192
  context "with a String argument" do
186
193
  it "searches across all :string columns (if not indexes have been specified)" do
@@ -282,6 +289,12 @@ RSpec.describe Textacular do
282
289
  end
283
290
  end
284
291
  end
292
+
293
+ describe "#fuzzy_search" do
294
+ it "works if column contains multiple space delimited strings" do
295
+ expect(GameExtendedWithTextacular.fuzzy_search(title: 'mar')).to eq([mario])
296
+ end
297
+ end
285
298
  end
286
299
  end
287
300
  end
metadata CHANGED
@@ -1,32 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: textacular
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.0
4
+ version: 5.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Hamill
8
8
  - ecin
9
9
  - Aaron Patterson
10
10
  - Greg Molnar
11
- autorequire:
11
+ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2018-05-12 00:00:00.000000000 Z
14
+ date: 2021-12-23 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: pg
18
18
  requirement: !ruby/object:Gem::Requirement
19
19
  requirements:
20
- - - "~>"
20
+ - - ">="
21
21
  - !ruby/object:Gem::Version
22
- version: 1.0.0
22
+ version: '0'
23
23
  type: :development
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
- - - "~>"
27
+ - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: 1.0.0
29
+ version: '0'
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: rspec
32
32
  requirement: !ruby/object:Gem::Requirement
@@ -97,6 +97,20 @@ dependencies:
97
97
  - - ">="
98
98
  - !ruby/object:Gem::Version
99
99
  version: '0'
100
+ - !ruby/object:Gem::Dependency
101
+ name: byebug
102
+ requirement: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ type: :development
108
+ prerelease: false
109
+ version_requirements: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
100
114
  - !ruby/object:Gem::Dependency
101
115
  name: activerecord
102
116
  requirement: !ruby/object:Gem::Requirement
@@ -106,7 +120,7 @@ dependencies:
106
120
  version: '5.0'
107
121
  - - "<"
108
122
  - !ruby/object:Gem::Version
109
- version: '6.0'
123
+ version: '7.1'
110
124
  type: :runtime
111
125
  prerelease: false
112
126
  version_requirements: !ruby/object:Gem::Requirement
@@ -116,7 +130,7 @@ dependencies:
116
130
  version: '5.0'
117
131
  - - "<"
118
132
  - !ruby/object:Gem::Version
119
- version: '6.0'
133
+ version: '7.1'
120
134
  description: |-
121
135
  Textacular exposes full text search capabilities from PostgreSQL, extending
122
136
  ActiveRecord with scopes making search easy and fun!
@@ -165,7 +179,7 @@ homepage: http://textacular.github.com/textacular
165
179
  licenses:
166
180
  - MIT
167
181
  metadata: {}
168
- post_install_message:
182
+ post_install_message:
169
183
  rdoc_options: []
170
184
  require_paths:
171
185
  - lib
@@ -180,9 +194,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
180
194
  - !ruby/object:Gem::Version
181
195
  version: '0'
182
196
  requirements: []
183
- rubyforge_project:
184
- rubygems_version: 2.6.8
185
- signing_key:
197
+ rubygems_version: 3.1.4
198
+ signing_key:
186
199
  specification_version: 4
187
200
  summary: Textacular exposes full text search capabilities from PostgreSQL
188
201
  test_files:
@@ -207,4 +220,3 @@ test_files:
207
220
  - spec/textacular/migration_generator_spec.rb
208
221
  - spec/textacular/searchable_spec.rb
209
222
  - spec/textacular/trigram_installer_spec.rb
210
- has_rdoc: