card-mod-fulltext 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +44 -0
- data/db/migrate/20200302171011_add_fulltext_indexing.rb +11 -0
- data/lib/card/query/card_query/full_text_matching.rb +67 -0
- data/set/abstract/filter.rb +27 -0
- data/set/abstract/no_search_content.rb +3 -0
- data/set/abstract/search_content_fields.rb +15 -0
- data/set/all/search_content.rb +45 -0
- metadata +66 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 93a52bd07ccc0265fd84b336b56756dd8a4ef2aa2b8e5152b4f44e4f453b2492
|
4
|
+
data.tar.gz: def54ecc50932efc833d39b2f6657c5c67ba3f18d28ee9039b17fb26a21f207c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 005e152195747735d7dcda0622790d165bcc6564cbc1174efcce62651721005de2cf42669df721c2b8bc38ee7deb78ea0d3dc12a6e357e3c8c28427abba8ef95
|
7
|
+
data.tar.gz: fa89c31f7e9581f5441328c52552685f6153e29308a279953a477ab712f85faf6a12aee121eb12f2e692fdd2ae2814b89d9c319a1542854da90c362957b1dc03
|
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
<!--
|
2
|
+
# @title README - mod: fulltext
|
3
|
+
-->
|
4
|
+
|
5
|
+
# Fulltext mod
|
6
|
+
|
7
|
+
This mod adds MySQL fulltext support for card searches.
|
8
|
+
|
9
|
+
It does so by adding a `search_content` field to the cards table and indexing the
|
10
|
+
`name` and `search_content` fields.
|
11
|
+
|
12
|
+
For simple cards, the `search_content` simply duplicates the `db_content` field (which
|
13
|
+
contains default card content). But the fulltext search becomes much more powerful if
|
14
|
+
the `search_content` is customized for different sets, often by including the content of
|
15
|
+
select related cards.
|
16
|
+
|
17
|
+
## CQL
|
18
|
+
|
19
|
+
This mod adds support for fulltext matching in card queries, eg:
|
20
|
+
|
21
|
+
fulltext_match: "MYKEYWORD"
|
22
|
+
|
23
|
+
Alternatively, you can trigger a fulltext match using a `:` prefix with a standard
|
24
|
+
match statement.
|
25
|
+
|
26
|
+
match: ":MYKEYWORD"
|
27
|
+
|
28
|
+
It also adds support for relevance sorting:
|
29
|
+
|
30
|
+
sort: "relevance"
|
31
|
+
|
32
|
+
## Sets
|
33
|
+
|
34
|
+
### Abstract::SearchContentFields
|
35
|
+
|
36
|
+
After including this set, you can add a `#search_content_field_codes` method that returns
|
37
|
+
a list of codenames. The `#search_content` field will be populated by concatenating
|
38
|
+
the content of those fields.
|
39
|
+
|
40
|
+
### Abstract::NoSearchContent
|
41
|
+
|
42
|
+
With this set, the `search_content` will be blank (and fulltext matching will be use
|
43
|
+
only the name field)
|
44
|
+
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class AddFulltextIndexing < ActiveRecord::Migration[6.0]
|
2
|
+
def up
|
3
|
+
add_column :cards, :search_content, :text, limit: 1.megabyte
|
4
|
+
add_index :cards, %i[name search_content],
|
5
|
+
name: "name, search_content_index", type: :fulltext
|
6
|
+
end
|
7
|
+
|
8
|
+
def down
|
9
|
+
remove_column :cards, :search_content
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
|
2
|
+
class Card
|
3
|
+
module Query
|
4
|
+
attributes[:fulltext_match] = :relational
|
5
|
+
|
6
|
+
class CardQuery
|
7
|
+
# handle `fulltext_match` condition in card queries
|
8
|
+
module FullTextMatching
|
9
|
+
def fulltext_match value
|
10
|
+
return if value.strip.empty?
|
11
|
+
if prefixed_match? value
|
12
|
+
name_match value
|
13
|
+
else
|
14
|
+
add_condition Value.new([:match, ":#{value}"], self).to_sql(:name)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def prefixed_match? value
|
19
|
+
value.match?(/^[\~\:\=]/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class SqlStatement
|
25
|
+
# handle `fulltext_match` relevance sorting in card queries
|
26
|
+
module FullTextOrdering
|
27
|
+
# when there is full text matching in the where clause,
|
28
|
+
# the default ordering is by relevance, so we just need to
|
29
|
+
# make sure there is no explicit order by clause
|
30
|
+
def order
|
31
|
+
super unless order_config == "relevance"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Note: the more explicit route (which will be necessary if we want to support
|
35
|
+
# relevance as one of multiple sort options), is to do something like
|
36
|
+
# select MATCH (x) AGAINST (y) as relevance... order by relevance desc
|
37
|
+
end
|
38
|
+
include FullTextOrdering
|
39
|
+
end
|
40
|
+
|
41
|
+
class Value
|
42
|
+
def self.match_prefices
|
43
|
+
@match_prefices ||= %w[= ~ :]
|
44
|
+
end
|
45
|
+
|
46
|
+
module FullTextValue
|
47
|
+
def match_sql _field
|
48
|
+
return super unless fulltext_match_term?
|
49
|
+
"MATCH (#{fulltext_fields}) AGAINST (#{quote match_term} #{fulltext_mode})"
|
50
|
+
end
|
51
|
+
|
52
|
+
def fulltext_match_term?
|
53
|
+
match_prefix.match? ":"
|
54
|
+
end
|
55
|
+
|
56
|
+
def fulltext_fields
|
57
|
+
%i[search_content name].map { |fld| "#{@query.table_alias}.#{fld}" }.join ", "
|
58
|
+
end
|
59
|
+
|
60
|
+
def fulltext_mode
|
61
|
+
match_prefix == "::" ? "IN BOOLEAN MODE" : ""
|
62
|
+
end
|
63
|
+
end
|
64
|
+
include FullTextValue
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
format do
|
2
|
+
def sort_options
|
3
|
+
handling_fulltext do
|
4
|
+
# TODO: make it possible to use super here
|
5
|
+
{ "Alphabetical": :name, "Recently Added": :create }
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
# This only shows relevance sorting when the page is loaded with an explicit
|
11
|
+
# fulltext prefix (:) in the name search. Otherwise "relevance" sorting doesn't
|
12
|
+
# make sense.
|
13
|
+
def handling_fulltext
|
14
|
+
options = yield
|
15
|
+
add_sort_by_relevance options if fulltext_name_filtering?
|
16
|
+
options
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_sort_by_relevance options
|
20
|
+
options["Relevance"] = :relevance
|
21
|
+
params[:sort] = :relevance unless sort_param
|
22
|
+
end
|
23
|
+
|
24
|
+
def fulltext_name_filtering?
|
25
|
+
params.dig(:filter, :name)&.match?(/^\:/)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
def content_for_search
|
2
|
+
content_for_search_from_fields
|
3
|
+
end
|
4
|
+
|
5
|
+
def search_content_cards
|
6
|
+
search_content_field_codes.map do |code|
|
7
|
+
fetch code, new: {}
|
8
|
+
end.compact
|
9
|
+
end
|
10
|
+
|
11
|
+
def search_content_field_names
|
12
|
+
search_content_field_codes.map do |code|
|
13
|
+
name.trait_name code
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
event :set_search_content, after: :set_content do
|
2
|
+
self.search_content = tagless { content_for_search.to_s }
|
3
|
+
end
|
4
|
+
|
5
|
+
event :trigger_left_search_content_update, after: :set_search_content do
|
6
|
+
return unless name.compound?
|
7
|
+
l = left
|
8
|
+
|
9
|
+
return unless l&.real? && l.search_content_field_names&.include?(name)
|
10
|
+
|
11
|
+
l.set_search_content
|
12
|
+
l.save if l.search_content_changed?
|
13
|
+
end
|
14
|
+
|
15
|
+
def tagless
|
16
|
+
::ActionView::Base.full_sanitizer.sanitize yield
|
17
|
+
end
|
18
|
+
|
19
|
+
def name_for_search
|
20
|
+
name
|
21
|
+
end
|
22
|
+
|
23
|
+
def content_for_search
|
24
|
+
structure ? content_for_search_from_fields : content
|
25
|
+
end
|
26
|
+
|
27
|
+
def search_content_cards
|
28
|
+
return [] unless structure && nest_chunks
|
29
|
+
|
30
|
+
nest_chunks.map(&:referee_card).compact
|
31
|
+
end
|
32
|
+
|
33
|
+
def search_content_field_names
|
34
|
+
search_content_cards.map(&:name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def content_for_search_from_fields
|
38
|
+
search_content_cards.map(&:content).compact.join "\n"
|
39
|
+
end
|
40
|
+
|
41
|
+
format do
|
42
|
+
view :search_content do
|
43
|
+
card.search_content
|
44
|
+
end
|
45
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: card-mod-fulltext
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Philipp Kühl
|
8
|
+
- Ethan McCutchen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2021-10-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: card
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
description: ''
|
29
|
+
email:
|
30
|
+
- info@decko.org
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- README.md
|
36
|
+
- db/migrate/20200302171011_add_fulltext_indexing.rb
|
37
|
+
- lib/card/query/card_query/full_text_matching.rb
|
38
|
+
- set/abstract/filter.rb
|
39
|
+
- set/abstract/no_search_content.rb
|
40
|
+
- set/abstract/search_content_fields.rb
|
41
|
+
- set/all/search_content.rb
|
42
|
+
homepage: http://decko.org
|
43
|
+
licenses:
|
44
|
+
- GPL-3.0
|
45
|
+
metadata:
|
46
|
+
card-mod: fulltext
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '2.5'
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubygems_version: 3.2.28
|
63
|
+
signing_key:
|
64
|
+
specification_version: 4
|
65
|
+
summary: MySQL fulltext searches for decko
|
66
|
+
test_files: []
|