polysearch 0.1.1 → 0.2.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -5
- data/README.md +21 -8
- data/app/models/concerns/searchable.rb +19 -20
- data/app/models/record.rb +46 -24
- data/bin/loc +3 -0
- data/lib/polysearch/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cbd94171f65db5d6d0e92c6ad1198716c8fe689c395f82b2f15d59adec6b8ce7
|
4
|
+
data.tar.gz: 720f9f7ea37e863dff3d05631abc61ff2ff09d0b5b2af2f68b53444cb42eba3c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2dbc836cb1279c2ae3ee5c7d2fc9a1bedb1ea2bbad8f7b2974369d6c48ff646bf7038f409902047b69f782821ca7a6e135ef0670282d732b366dcccb2cf2a37f
|
7
|
+
data.tar.gz: 8e2b04099b36fc14c9fc4f4247414b44d020c153a8510184114d5607b5fe48b89f165c1f2d098aed4c58dcb67c29e72279c6e0a0233f0b91c3fa5baaf327d67c
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
polysearch (0.
|
4
|
+
polysearch (0.2.0)
|
5
5
|
rails (>= 6.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -84,12 +84,8 @@ GEM
|
|
84
84
|
marcel (1.0.1)
|
85
85
|
method_source (1.0.0)
|
86
86
|
mini_mime (1.0.3)
|
87
|
-
mini_portile2 (2.5.1)
|
88
87
|
minitest (5.14.4)
|
89
88
|
nio4r (2.5.7)
|
90
|
-
nokogiri (1.11.5)
|
91
|
-
mini_portile2 (~> 2.5.0)
|
92
|
-
racc (~> 1.4)
|
93
89
|
nokogiri (1.11.5-arm64-darwin)
|
94
90
|
racc (~> 1.4)
|
95
91
|
parallel (1.20.1)
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
[](http://blog.codinghorror.com/the-best-code-is-no-code-at-all/)
|
2
2
|
|
3
3
|
# Polysearch
|
4
4
|
|
5
|
-
Simplified polymorphic full text + similarity search based on postgres
|
5
|
+
Simplified polymorphic full text + similarity search based on postgres.
|
6
6
|
|
7
7
|
> NOTE: This project is narrower in scope and more opinionated than [pg_search](https://github.com/Casecommons/pg_search).
|
8
8
|
|
@@ -50,7 +50,7 @@ Simplified polymorphic full text + similarity search based on postgres
|
|
50
50
|
[
|
51
51
|
make_tsvector(first_name, weight: "A"),
|
52
52
|
make_tsvector(last_name, weight: "A"),
|
53
|
-
make_tsvector(
|
53
|
+
make_tsvector(nickname, weight: "B")
|
54
54
|
]
|
55
55
|
end
|
56
56
|
end
|
@@ -65,12 +65,25 @@ Simplified polymorphic full text + similarity search based on postgres
|
|
65
65
|
1. Start searching
|
66
66
|
|
67
67
|
```ruby
|
68
|
-
User.create first_name: "
|
68
|
+
User.create first_name: "Shawn", last_name: "Spencer", nickname: "Maverick"
|
69
69
|
|
70
|
-
|
71
|
-
User.
|
72
|
-
|
73
|
-
|
70
|
+
# find natural language matches (faster)
|
71
|
+
User.full_text_search("shawn")
|
72
|
+
|
73
|
+
# find similarity matches, best for misspelled search terms (slower)
|
74
|
+
User.similarity_search("shwn")
|
75
|
+
|
76
|
+
# perform a combined full text search and a similarity search
|
77
|
+
User.combined_search("shwn")
|
78
|
+
|
79
|
+
# perform a full text search and fall back to similarity (faster than combined_search)
|
80
|
+
User.polysearch("shwn")
|
81
|
+
|
82
|
+
# calculate counts (explicitly pass :id to omit search rankings)
|
83
|
+
User.full_text_search("shawn").count(:id)
|
84
|
+
User.similarity_search("shwn").count(:id)
|
85
|
+
User.combined_search("shwn").count(:id)
|
86
|
+
User.polysearch("shwn").count(:id)
|
74
87
|
```
|
75
88
|
|
76
89
|
## License
|
@@ -36,27 +36,26 @@ module Polysearch
|
|
36
36
|
has_one :polysearch, as: :searchable, class_name: "Polysearch::Record", inverse_of: "searchable"
|
37
37
|
after_destroy :destroy_polysearch
|
38
38
|
|
39
|
+
scope :full_text_search, ->(value) {
|
40
|
+
value.blank? ? all : joins(:polysearch).merge(Polysearch::Record.full_text_search(value).select_full_text_search_rank(value))
|
41
|
+
}
|
42
|
+
|
43
|
+
scope :similarity_search, ->(value) {
|
44
|
+
value.blank? ? all : joins(:polysearch).merge(Polysearch::Record.similarity_search(value).select_similarity_rank(value))
|
45
|
+
}
|
46
|
+
|
47
|
+
scope :combined_search, ->(value) {
|
48
|
+
subquery = <<~SQL
|
49
|
+
(
|
50
|
+
SELECT #{table_name}.*, searchable_polysearches.search_rank from (#{Polysearch::Record.combined_search(value).except(:order).to_sql}) searchable_polysearches
|
51
|
+
LEFT JOIN LATERAL (select * from #{table_name} WHERE id = searchable_polysearches.searchable_id) #{table_name} ON TRUE
|
52
|
+
) #{table_name}
|
53
|
+
SQL
|
54
|
+
from(subquery).order("search_rank desc")
|
55
|
+
}
|
56
|
+
|
39
57
|
scope :polysearch, ->(value) {
|
40
|
-
|
41
|
-
all
|
42
|
-
else
|
43
|
-
fts_rank_alias = "#{table_name.singularize}_fts_rank"
|
44
|
-
similarity_rank_alias = "#{table_name.singularize}_similarity_rank"
|
45
|
-
|
46
|
-
fts = Polysearch::Record
|
47
|
-
.select_fts_rank(value, :searchable_id, rank_alias: fts_rank_alias)
|
48
|
-
.select_similarity_rank(value, :searchable_id, rank_alias: similarity_rank_alias)
|
49
|
-
.where(searchable_type: name)
|
50
|
-
.fts(value).or(Polysearch::Record.where(searchable_type: name).similar(value))
|
51
|
-
|
52
|
-
query = <<~SQL
|
53
|
-
SELECT searchables.*, fts.#{fts_rank_alias}, fts.#{similarity_rank_alias} from (#{fts.to_sql}) fts
|
54
|
-
LEFT JOIN LATERAL (select * from #{table_name} WHERE id = fts.searchable_id) searchables ON TRUE
|
55
|
-
SQL
|
56
|
-
|
57
|
-
select(Arel.star).from(Arel::Nodes::SqlLiteral.new("(#{query})").as(table_name))
|
58
|
-
.reorder(fts_rank_alias => :desc, similarity_rank_alias => :desc)
|
59
|
-
end
|
58
|
+
value.blank? ? all : joins(:polysearch).merge(Polysearch::Record.polysearch(value))
|
60
59
|
}
|
61
60
|
end
|
62
61
|
|
data/app/models/record.rb
CHANGED
@@ -33,39 +33,57 @@ module Polysearch
|
|
33
33
|
|
34
34
|
# scopes ....................................................................
|
35
35
|
|
36
|
-
scope :
|
37
|
-
|
38
|
-
value = Arel::Nodes::SqlLiteral.new(sanitize_sql_array(["?", value.to_s]))
|
39
|
-
plainto_tsquery = Arel::Nodes::NamedFunction.new("plainto_tsquery", [Arel::Nodes::SqlLiteral.new("'simple'"), value])
|
36
|
+
scope :select_full_text_search_rank, ->(value, *selects) {
|
37
|
+
plainto_tsquery = Arel::Nodes::NamedFunction.new("plainto_tsquery", [Arel::Nodes::SqlLiteral.new("'simple'"), arel_search_value(value)])
|
40
38
|
ts_rank = Arel::Nodes::NamedFunction.new("ts_rank", [arel_table[:value], plainto_tsquery])
|
41
|
-
|
42
|
-
rank_alias ||= "fts_rank"
|
43
39
|
selects << Arel.star if selects.blank?
|
44
|
-
selects << ts_rank.as(
|
45
|
-
select(*selects).
|
40
|
+
selects << ts_rank.as("search_rank")
|
41
|
+
select(*selects).reorder("search_rank desc")
|
46
42
|
}
|
47
43
|
|
48
|
-
scope :
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
44
|
+
scope :full_text_search, ->(value) {
|
45
|
+
if value.blank?
|
46
|
+
all
|
47
|
+
else
|
48
|
+
plainto_tsquery = Arel::Nodes::NamedFunction.new("plainto_tsquery", [Arel::Nodes::SqlLiteral.new("'simple'"), arel_search_value(value)])
|
49
|
+
where(Arel::Nodes::InfixOperation.new("@@", arel_table[:value], plainto_tsquery))
|
50
|
+
end
|
53
51
|
}
|
54
52
|
|
55
|
-
scope :select_similarity_rank, ->(value, *selects
|
56
|
-
|
57
|
-
value = Arel::Nodes::SqlLiteral.new(sanitize_sql_array(["?", value.to_s]))
|
58
|
-
|
59
|
-
rank_alias ||= "similarity_rank"
|
53
|
+
scope :select_similarity_rank, ->(value, *selects) {
|
54
|
+
similarity = Arel::Nodes::NamedFunction.new("similarity", [arel_table[:words], arel_search_value(value)])
|
60
55
|
selects << Arel.star if selects.blank?
|
61
|
-
selects <<
|
62
|
-
select(*selects).order("
|
56
|
+
selects << similarity.as("search_rank")
|
57
|
+
select(*selects).order("search_rank desc")
|
58
|
+
}
|
59
|
+
|
60
|
+
scope :similarity_search, ->(value) {
|
61
|
+
if value.blank?
|
62
|
+
all
|
63
|
+
else
|
64
|
+
where Arel::Nodes::NamedFunction.new("similarity", [arel_table[:words], arel_search_value(value)]).gt(0)
|
65
|
+
end
|
66
|
+
}
|
67
|
+
|
68
|
+
scope :combined_search, ->(value) {
|
69
|
+
subquery = <<~SQL
|
70
|
+
(
|
71
|
+
#{select_full_text_search_rank(value).full_text_search(value).except(:order).to_sql}
|
72
|
+
UNION ALL
|
73
|
+
#{select_similarity_rank(value).similarity_search(value).except(:order).to_sql}
|
74
|
+
) AS #{table_name}
|
75
|
+
SQL
|
76
|
+
from(subquery).order("search_rank desc")
|
63
77
|
}
|
64
78
|
|
65
|
-
scope :
|
66
|
-
|
67
|
-
|
68
|
-
|
79
|
+
scope :polysearch, ->(value) {
|
80
|
+
if value.blank?
|
81
|
+
all
|
82
|
+
else
|
83
|
+
full_text_search(value).exists? ?
|
84
|
+
select_full_text_search_rank(value).full_text_search(value) :
|
85
|
+
select_similarity_rank(value).similarity_search(value)
|
86
|
+
end
|
69
87
|
}
|
70
88
|
|
71
89
|
# additional config (i.e. accepts_nested_attribute_for etc...) ..............
|
@@ -74,6 +92,10 @@ module Polysearch
|
|
74
92
|
|
75
93
|
# class methods .............................................................
|
76
94
|
class << self
|
95
|
+
def arel_search_value(value)
|
96
|
+
value = value.to_s.gsub(/\W/, " ").squeeze(" ").downcase.strip
|
97
|
+
Arel::Nodes::SqlLiteral.new(sanitize_sql_array(["?", value]))
|
98
|
+
end
|
77
99
|
end
|
78
100
|
|
79
101
|
# public instance methods ...................................................
|
data/bin/loc
ADDED
data/lib/polysearch/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: polysearch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Hopkins
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -95,6 +95,7 @@ files:
|
|
95
95
|
- app/models/concerns/searchable.rb
|
96
96
|
- app/models/record.rb
|
97
97
|
- bin/console
|
98
|
+
- bin/loc
|
98
99
|
- bin/setup
|
99
100
|
- bin/standardize
|
100
101
|
- lib/generators/polysearch/migration_generator.rb
|