card-mod-fulltext 0.1

Sign up to get free protection for your applications and to get access to all the features.
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,3 @@
1
+ def content_for_search
2
+ nil
3
+ 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: []