activerecord-text-search-configuration 0.0.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +123 -0
- data/lib/active_record/text_search_configuration/command_recorder.rb +37 -0
- data/lib/active_record/text_search_configuration/railtie.rb +23 -0
- data/lib/active_record/text_search_configuration/schema_dumper.rb +42 -0
- data/lib/active_record/text_search_configuration/schema_statements.rb +104 -0
- data/lib/active_record/text_search_configuration/version.rb +7 -0
- data/lib/active_record/text_search_configuration.rb +12 -0
- data/lib/activerecord-text-search-configuration.rb +4 -0
- metadata +66 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: afbdb8cf9b7b89a5f0825b33393d58a5fb0478e6a031646a4dbbf4afbb89ba34
|
|
4
|
+
data.tar.gz: 90574a92d626ba0861cc522dfe8dd6d9bb4d5e9f8397fe80e8efceaeb08926d2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d13520cbe86371cf2df820aca696cd482ea69a9205e7a665fea3a4597fd8ae9503339b102cc9c41a428d79c699693498bedeaa21632a2f1f3afee2ca672e3bce
|
|
7
|
+
data.tar.gz: e61a3191d201d6fdbebadb7c36248d1419daf75a474e62c956dc29ae8b8996b37be92fe3954f694ea41a72e610e3ddf745e3328955e6e04e4e5a2a248274b473
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © Fingertips <manfred@fngtps.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Active Record – Text Search Configuration
|
|
2
|
+
|
|
3
|
+
Allows basic management of text search configurations in Postgres.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
# Assumes you already have the unaccent extension created for the database.
|
|
7
|
+
class CreateSimpleUnaccentSearchConfiguration < ActiveRecord::Migration[8.1]
|
|
8
|
+
def change
|
|
9
|
+
create_text_search_configuration(
|
|
10
|
+
:simple_unaccent,
|
|
11
|
+
# Start from the simple search configuration.
|
|
12
|
+
copy: :simple,
|
|
13
|
+
# The tsearch parser assigns a type to each token. We configure how
|
|
14
|
+
# values for each type are mapped to an internal value before
|
|
15
|
+
# creating a search vector.
|
|
16
|
+
mappings: {
|
|
17
|
+
%w[hword hword_part word] => %w[unaccent simple],
|
|
18
|
+
%w[asciiword] => %w[simple]
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
After this we can use the search configuration every time we create a vector:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
create_table :documents do |t|
|
|
29
|
+
t.text :title
|
|
30
|
+
t.virtual(
|
|
31
|
+
:title_tsvector,
|
|
32
|
+
type: :tsvector,
|
|
33
|
+
stored: true,
|
|
34
|
+
as: "to_tsvector('simple_unaccent', coalesce(title, ''))"
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
We store a vector for query performance reasons and the original text for presentation. Having a predefined search configuration means we can re-use the same settings when using a function like `ts_headline` on the text:
|
|
40
|
+
|
|
41
|
+
ts_headline('simple_unaccent', "documents"."title", …)
|
|
42
|
+
|
|
43
|
+
## Setup
|
|
44
|
+
|
|
45
|
+
Add `activerecord-text-search-configuration` to your dependencies.
|
|
46
|
+
|
|
47
|
+
## Defining a search configuration and mapping
|
|
48
|
+
|
|
49
|
+
You probably want to start by reading the [PostgreSQL documentation](https://www.postgresql.org/docs/current/textsearch-controls.html) about controlling text search.
|
|
50
|
+
|
|
51
|
+
A configuration starts from either a parser, without any mappings:
|
|
52
|
+
|
|
53
|
+
create_text_search_configuration(parser: :default)
|
|
54
|
+
|
|
55
|
+
Or it starts as a copy of an existing configuration:
|
|
56
|
+
|
|
57
|
+
create_text_search_configuration(copy: :simple)
|
|
58
|
+
|
|
59
|
+
You can get a full list of token types from the server by running `psql`:
|
|
60
|
+
|
|
61
|
+
\dFp+ default
|
|
62
|
+
|
|
63
|
+
In schema changes you can use `change_text_search_configuration_mapping(name, token_types, dictionaries)` and `drop_text_search_configuration(name)` to remove a configuration. Pass `force: :cascade` to also drop generated columns and indexes that depend on it.
|
|
64
|
+
|
|
65
|
+
Note that `create_text_search_configuration` is reversible; `drop_text_search_configuration` is reversible when you pass `parser:` or `copy:` with `mappings:`.
|
|
66
|
+
|
|
67
|
+
If you're still lost, you probably want something like this:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
create_text_search_configuration(
|
|
71
|
+
'simple_unaccent',
|
|
72
|
+
parser: 'default',
|
|
73
|
+
mappings: {
|
|
74
|
+
%w[
|
|
75
|
+
asciihword
|
|
76
|
+
asciiword
|
|
77
|
+
email
|
|
78
|
+
file
|
|
79
|
+
float
|
|
80
|
+
host
|
|
81
|
+
hword_asciipart
|
|
82
|
+
hword_numpart
|
|
83
|
+
int
|
|
84
|
+
numhword
|
|
85
|
+
numword
|
|
86
|
+
protocol
|
|
87
|
+
sfloat
|
|
88
|
+
uint
|
|
89
|
+
url
|
|
90
|
+
url_path
|
|
91
|
+
version
|
|
92
|
+
] => ['simple'],
|
|
93
|
+
%w[word hword_part hword] => %w[unaccent simple]
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
This leaves out `blank`, `entity`, and `tag`. We probably don't care about whitespace and entities (eg. `>`) and tags (eg. `<xml>`) need to pre-processing to handle in a useful way if you want to make them searchable so the parser should never find them.
|
|
99
|
+
|
|
100
|
+
You will need to do a lot of work to massage documents and queries so you get the type of query features you want, but it's outside of the scope of this README to discuss all these details.
|
|
101
|
+
|
|
102
|
+
## Development
|
|
103
|
+
|
|
104
|
+
Run tests in this repository with `rake`. Migrations are exercised by running them in the dummy application.
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
bundle install
|
|
108
|
+
cd test/dummy
|
|
109
|
+
rm -f db/schema.rb && RAILS_ENV=test bundle exec rake db:drop db:create db:migrate
|
|
110
|
+
cd ../../
|
|
111
|
+
bundle exec rake
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
If you also want to verify running tests on Rails Edge, you can set `BUNDLE_GEMFILE` and run everything again.
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
export BUNDLE_GEMFILE=$(pwd)/Gemfile.edge
|
|
118
|
+
bundle install
|
|
119
|
+
cd test/dummy
|
|
120
|
+
rm -f db/schema.rb && RAILS_ENV=test bundle exec rake db:drop db:create db:migrate
|
|
121
|
+
cd ../../
|
|
122
|
+
bundle exec rake
|
|
123
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module TextSearchConfiguration
|
|
5
|
+
# Records text search configuration statements in migrations so they can be reverted.
|
|
6
|
+
module CommandRecorder
|
|
7
|
+
def create_text_search_configuration(*args, &)
|
|
8
|
+
record(:create_text_search_configuration, args, &)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def drop_text_search_configuration(*args, &)
|
|
12
|
+
record(:drop_text_search_configuration, args, &)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def invert_create_text_search_configuration(args)
|
|
18
|
+
[:drop_text_search_configuration, [args.first]]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Dropping can only be reverted when the migration passes everything needed to create the
|
|
22
|
+
# configuration again.
|
|
23
|
+
def invert_drop_text_search_configuration(args)
|
|
24
|
+
options = args.extract_options!
|
|
25
|
+
if options[:parser].nil? && options[:copy].nil?
|
|
26
|
+
raise(
|
|
27
|
+
ActiveRecord::IrreversibleMigration,
|
|
28
|
+
'To make drop_text_search_configuration reversible, pass parser: or copy: and ' \
|
|
29
|
+
'mappings:.'
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
[:create_text_search_configuration, [args.first, options.except(:if_exists, :force)]]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module TextSearchConfiguration
|
|
5
|
+
class Railtie < Rails::Railtie # :nodoc:
|
|
6
|
+
initializer 'active_record.text_search_configuration' do
|
|
7
|
+
ActiveSupport.on_load(:active_record_postgresqladapter) do
|
|
8
|
+
ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaDumper.include(
|
|
9
|
+
ActiveRecord::TextSearchConfiguration::SchemaDumper
|
|
10
|
+
)
|
|
11
|
+
ActiveSupport.on_load(:active_record) do
|
|
12
|
+
ActiveRecord::ConnectionAdapters::PostgreSQL::SchemaStatements.include(
|
|
13
|
+
ActiveRecord::TextSearchConfiguration::SchemaStatements
|
|
14
|
+
)
|
|
15
|
+
ActiveRecord::Migration::CommandRecorder.include(
|
|
16
|
+
ActiveRecord::TextSearchConfiguration::CommandRecorder
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module TextSearchConfiguration
|
|
5
|
+
# Overrides the PostgresAdapter schema dumper to include text search configurations.
|
|
6
|
+
module SchemaDumper
|
|
7
|
+
extend ActiveSupport::Concern
|
|
8
|
+
|
|
9
|
+
def text_search_configurations(stream)
|
|
10
|
+
configurations = @connection.text_search_configurations
|
|
11
|
+
return unless configurations.any?
|
|
12
|
+
|
|
13
|
+
configurations.each do |configuration|
|
|
14
|
+
text_search_configuration(stream, configuration)
|
|
15
|
+
end
|
|
16
|
+
stream.puts
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def tables(stream)
|
|
20
|
+
text_search_configurations(stream)
|
|
21
|
+
super
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def text_search_configuration(stream, configuration)
|
|
27
|
+
stream.puts(" create_text_search_configuration(\n")
|
|
28
|
+
stream.puts(" #{configuration[:name].inspect},")
|
|
29
|
+
stream.puts(" parser: #{configuration[:parser].inspect},")
|
|
30
|
+
stream.puts(' force: :cascade,')
|
|
31
|
+
stream.puts(' mappings: {')
|
|
32
|
+
stream.puts(
|
|
33
|
+
configuration[:mappings].map do |token_types, dictionaries|
|
|
34
|
+
" #{token_types.inspect} => #{dictionaries.inspect}"
|
|
35
|
+
end.join(",\n")
|
|
36
|
+
)
|
|
37
|
+
stream.puts(' }')
|
|
38
|
+
stream.puts(' )')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module TextSearchConfiguration
|
|
5
|
+
# Defines methods to create, drop, and inspect text search configurations on a schema in
|
|
6
|
+
# Postgres.
|
|
7
|
+
module SchemaStatements
|
|
8
|
+
# Creates a text search configuration.
|
|
9
|
+
def create_text_search_configuration(
|
|
10
|
+
name, parser: nil, copy: nil, mappings: {}, force: false
|
|
11
|
+
)
|
|
12
|
+
drop_text_search_configuration(name, if_exists: true, force: force) if force
|
|
13
|
+
execute(
|
|
14
|
+
"CREATE TEXT SEARCH CONFIGURATION #{quote_column_name(name)} " \
|
|
15
|
+
"(#{text_search_configuration_source(parser, copy)})"
|
|
16
|
+
)
|
|
17
|
+
mappings.each do |token_types, dictionaries|
|
|
18
|
+
change_text_search_configuration_mapping(name, token_types, dictionaries)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Replaces the dictionaries for one or more token types. An empty list of dictionaries
|
|
23
|
+
# removes the mapping.
|
|
24
|
+
def change_text_search_configuration_mapping(name, token_types, dictionaries)
|
|
25
|
+
token_types = quote_column_names(token_types)
|
|
26
|
+
execute(
|
|
27
|
+
"ALTER TEXT SEARCH CONFIGURATION #{quote_column_name(name)} " \
|
|
28
|
+
"DROP MAPPING IF EXISTS FOR #{token_types}"
|
|
29
|
+
)
|
|
30
|
+
return if dictionaries.blank?
|
|
31
|
+
|
|
32
|
+
execute(
|
|
33
|
+
"ALTER TEXT SEARCH CONFIGURATION #{quote_column_name(name)} " \
|
|
34
|
+
"ADD MAPPING FOR #{token_types} " \
|
|
35
|
+
"WITH #{quote_column_names(dictionaries)}"
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Drops a text search configuration.
|
|
40
|
+
def drop_text_search_configuration(name, if_exists: false, force: false, **)
|
|
41
|
+
execute(
|
|
42
|
+
"DROP TEXT SEARCH CONFIGURATION #{'IF EXISTS ' if if_exists}" \
|
|
43
|
+
"#{quote_column_name(name)}#{' CASCADE' if force == :cascade}"
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the text search configurations in the public schema.
|
|
48
|
+
def text_search_configurations
|
|
49
|
+
rows = select_all(text_search_configurations_sql, 'SCHEMA')
|
|
50
|
+
rows.group_by { |row| row['name'] }.map do |name, configuration_rows|
|
|
51
|
+
{
|
|
52
|
+
name: name,
|
|
53
|
+
parser: configuration_rows.first['parser'],
|
|
54
|
+
mappings: text_search_configuration_mappings(configuration_rows)
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def text_search_configuration_source(parser, copy)
|
|
62
|
+
if parser && copy
|
|
63
|
+
raise ArgumentError, 'Pass either parser: or copy:, a configuration needs one source.'
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
return "COPY = #{quote_column_name(copy)}" if copy
|
|
67
|
+
|
|
68
|
+
"PARSER = #{quote_column_name(parser || 'default')}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def quote_column_names(names)
|
|
72
|
+
Array(names).map { |name| quote_column_name(name) }.join(', ')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def text_search_configuration_mappings(rows)
|
|
76
|
+
rows
|
|
77
|
+
.select { |row| row['token_type'] }
|
|
78
|
+
.group_by { |row| row['dictionaries'] }
|
|
79
|
+
.to_h do |dictionaries, grouped|
|
|
80
|
+
[grouped.pluck('token_type'), dictionaries.split(',')]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def text_search_configurations_sql
|
|
85
|
+
<<~SQL
|
|
86
|
+
SELECT
|
|
87
|
+
config.cfgname AS name,
|
|
88
|
+
parser.prsname AS parser,
|
|
89
|
+
token.alias AS token_type,
|
|
90
|
+
string_agg(dictionary.dictname, ',' ORDER BY map.mapseqno) AS dictionaries
|
|
91
|
+
FROM pg_ts_config config
|
|
92
|
+
JOIN pg_namespace namespace
|
|
93
|
+
ON config.cfgnamespace = namespace.oid AND namespace.nspname = 'public'
|
|
94
|
+
JOIN pg_ts_parser parser ON config.cfgparser = parser.oid
|
|
95
|
+
LEFT JOIN pg_ts_config_map map ON map.mapcfg = config.oid
|
|
96
|
+
LEFT JOIN ts_token_type(config.cfgparser) token ON token.tokid = map.maptokentype
|
|
97
|
+
LEFT JOIN pg_ts_dict dictionary ON dictionary.oid = map.mapdict
|
|
98
|
+
GROUP BY config.cfgname, parser.prsname, token.tokid, token.alias
|
|
99
|
+
ORDER BY config.cfgname, token.tokid
|
|
100
|
+
SQL
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
# Implements tools to manage and use text search configurations in Postgres.
|
|
5
|
+
module TextSearchConfiguration
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
require 'active_record/text_search_configuration/command_recorder'
|
|
10
|
+
require 'active_record/text_search_configuration/schema_dumper'
|
|
11
|
+
require 'active_record/text_search_configuration/schema_statements'
|
|
12
|
+
require 'active_record/text_search_configuration/version'
|
metadata
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: activerecord-text-search-configuration
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Manfred Stienstra
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
description: |2
|
|
27
|
+
Adds methods like create_text_search_configuration to manage search configurations in your
|
|
28
|
+
Ruby on Rails project.
|
|
29
|
+
email:
|
|
30
|
+
- manfred@fngtps.com
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- LICENSE
|
|
36
|
+
- README.md
|
|
37
|
+
- lib/active_record/text_search_configuration.rb
|
|
38
|
+
- lib/active_record/text_search_configuration/command_recorder.rb
|
|
39
|
+
- lib/active_record/text_search_configuration/railtie.rb
|
|
40
|
+
- lib/active_record/text_search_configuration/schema_dumper.rb
|
|
41
|
+
- lib/active_record/text_search_configuration/schema_statements.rb
|
|
42
|
+
- lib/active_record/text_search_configuration/version.rb
|
|
43
|
+
- lib/activerecord-text-search-configuration.rb
|
|
44
|
+
homepage: https://github.com/Fingertips/activerecord-text-search-configuration
|
|
45
|
+
licenses:
|
|
46
|
+
- MIT
|
|
47
|
+
metadata:
|
|
48
|
+
rubygems_mfa_required: 'true'
|
|
49
|
+
rdoc_options: []
|
|
50
|
+
require_paths:
|
|
51
|
+
- lib
|
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">"
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '3.3'
|
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
requirements: []
|
|
63
|
+
rubygems_version: 4.0.10
|
|
64
|
+
specification_version: 4
|
|
65
|
+
summary: Text search configuration support for Active Record.
|
|
66
|
+
test_files: []
|