activerecord-mysql-search 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +22 -0
- data/Appraisals +18 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +235 -0
- data/Rakefile +16 -0
- data/activerecord-mysql-search.gemspec +39 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/rails_7.0.gemfile +20 -0
- data/gemfiles/rails_7.1.gemfile +19 -0
- data/gemfiles/rails_7.2.gemfile +19 -0
- data/gemfiles/rails_8.0.gemfile +19 -0
- data/lib/activerecord-mysql-search.rb +3 -0
- data/lib/generators/mysql/search/create_trigger_generator.rb +28 -0
- data/lib/generators/mysql/search/install_generator.rb +25 -0
- data/lib/generators/mysql/search/templates/app/models/search_index.rb +6 -0
- data/lib/generators/mysql/search/templates/config/initializers/active_record_ext.rb +37 -0
- data/lib/generators/mysql/search/templates/config/initializers/mysql_search.rb +22 -0
- data/lib/generators/mysql/search/templates/db/migrate/create_search_indices.rb +11 -0
- data/lib/generators/mysql/search/templates/db/migrate/enable_auto_update_of_updated_at.rb +22 -0
- data/lib/mysql/search/callbacks.rb +75 -0
- data/lib/mysql/search/grabber.rb +51 -0
- data/lib/mysql/search/jobs/scheduled_updater_job.rb +43 -0
- data/lib/mysql/search/jobs/updater_job.rb +24 -0
- data/lib/mysql/search/jobs.rb +12 -0
- data/lib/mysql/search/queries/full_text_search_query.rb +71 -0
- data/lib/mysql/search/queries/updated_sources_query.rb +50 -0
- data/lib/mysql/search/railtie.rb +19 -0
- data/lib/mysql/search/searchable.rb +24 -0
- data/lib/mysql/search/source.rb +69 -0
- data/lib/mysql/search/updater.rb +46 -0
- data/lib/mysql/search/utils/duration_parser.rb +20 -0
- data/lib/mysql/search/utils/formatter.rb +42 -0
- data/lib/mysql/search/utils/text_normalizer.rb +16 -0
- data/lib/mysql/search/utils.rb +13 -0
- data/lib/mysql/search.rb +47 -0
- data/lib/tasks/actualize.rake +24 -0
- data/lib/tasks/reindex.rake +30 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ac5bdb13de210b726a4e3616bfd79905f559bdea9018994e1ad853aae3c3db25
|
4
|
+
data.tar.gz: 7f16cc54115105d57b7043557a7fba5c27ec7ea095c411ceee175cf44845ba3f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1f0437f9d721c3834712ce514c899d7d597446db22c2cfa768b62fc121929d1931473ee209e8933578b9c5d790f3873fd5b36e13428011ddb1b6c6a67831999e
|
7
|
+
data.tar.gz: 0bac11a15dbf2a6df82c6636833eec5826aa12673e8e02bdc91bf5ef2298410101a7833f7a59672e9ea469c1383d46e0ef585d77f5f340dcf26059841f7abcab
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
plugins:
|
2
|
+
- rubocop-rspec
|
3
|
+
- rubocop-rake
|
4
|
+
- rubocop-rails
|
5
|
+
|
6
|
+
AllCops:
|
7
|
+
TargetRubyVersion: 3.1
|
8
|
+
NewCops: enable
|
9
|
+
|
10
|
+
Layout/LineLength:
|
11
|
+
Max: 120
|
12
|
+
|
13
|
+
RSpec/SpecFilePathFormat:
|
14
|
+
CustomTransform:
|
15
|
+
MySQL: mysql
|
16
|
+
|
17
|
+
Rails/ApplicationJob:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Naming/FileName:
|
21
|
+
Exclude:
|
22
|
+
- 'lib/activerecord-mysql-search.rb'
|
data/Appraisals
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
appraise 'rails_7.0' do
|
4
|
+
gem 'concurrent-ruby', '1.3.4'
|
5
|
+
gem 'rails', '~> 7.0.0'
|
6
|
+
end
|
7
|
+
|
8
|
+
appraise 'rails_7.1' do
|
9
|
+
gem 'rails', '~> 7.1.0'
|
10
|
+
end
|
11
|
+
|
12
|
+
appraise 'rails_7.2' do
|
13
|
+
gem 'rails', '~> 7.2.0'
|
14
|
+
end
|
15
|
+
|
16
|
+
appraise 'rails_8.0' do
|
17
|
+
gem 'rails', '~> 8.0.0'
|
18
|
+
end
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source 'https://rubygems.org'
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in activerecord-mysql-search.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem 'appraisal'
|
9
|
+
gem 'pry'
|
10
|
+
|
11
|
+
gem 'mysql2'
|
12
|
+
gem 'rails', '~> 7.1.0'
|
13
|
+
gem 'rake', '~> 13.0'
|
14
|
+
|
15
|
+
gem 'database_cleaner-active_record'
|
16
|
+
gem 'rspec'
|
17
|
+
|
18
|
+
gem 'rubocop'
|
19
|
+
gem 'rubocop-rails'
|
20
|
+
gem 'rubocop-rake'
|
21
|
+
gem 'rubocop-rspec'
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Daydream Unicorn GmbH & Co. KG
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
# ActiveRecord MySQL Search
|
2
|
+
|
3
|
+
[](https://github.com/ddunicorn/activerecord-mysql-search.rubygem/actions/workflows/main.yml)
|
4
|
+
|
5
|
+
A Ruby gem that provides efficient full-text search capabilities for ActiveRecord models using MySQL's native full-text search features. This gem simplifies the process of making your models searchable by automatically indexing specified fields and providing intuitive search methods.
|
6
|
+
|
7
|
+
## How It Works
|
8
|
+
|
9
|
+
The gem creates a dedicated `search_indices` table that stores denormalized, searchable content extracted from your ActiveRecord models. When you define a search schema, the gem automatically copies and transforms data from your source models (e.g., `articles`, `products`) into optimized text columns with MySQL FULLTEXT indexes. This design delivers **fast search performance** by avoiding complex JOINs across multiple tables during queries, while **automatic synchronization** keeps the search index current when your source data changes. The gem handles the complexity of data extraction, formatting, and index maintenance, so you get scalable full-text search without manual SQL or external search services.
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
- 🚀 **Native MySQL Full-Text Search**: Fast, relevant searches using MySQL `FULLTEXT` indexes.
|
14
|
+
- 🔄 **Automatic & Background Indexing**: Keeps search data up-to-date with synchronous or ActiveJob-powered background updates.
|
15
|
+
- 📝 **Declarative Search Schema**: Easily specify indexed fields, including nested associations, dates, calendar weeks, or custom logic via `Proc` in dedicated source classes.
|
16
|
+
- 👥 **Multi-Role & Context-Aware Search**: Define separate search columns for different user roles (e.g., buyer, seller, admin) to support multi-tenant apps and data privacy.
|
17
|
+
- 🧹 **Separation of Concerns**: Move indexing logic and field formatting out of models for cleaner, more maintainable code.
|
18
|
+
- ⚡ **Easy Rails Integration**: Includes generators for setup, migrations, and rake tasks for bulk reindexing.
|
19
|
+
- 🧩 **Flexible Field Mapping**: Supports complex data structures and custom extraction for precise indexing.
|
20
|
+
- 🎯 **Customizable Search Scopes**: Search specific columns based on user context or application needs.
|
21
|
+
|
22
|
+
## Requirements
|
23
|
+
|
24
|
+
- Ruby >= 3.1.0
|
25
|
+
- Rails (supports versions 7.0+)
|
26
|
+
- MySQL database with FULLTEXT index support
|
27
|
+
|
28
|
+
## Implementing Full-Text Search in 5 Minutes
|
29
|
+
|
30
|
+
### Step 1: Install the Gem
|
31
|
+
|
32
|
+
Add to your `Gemfile`:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
gem 'activerecord-mysql-search'
|
36
|
+
```
|
37
|
+
|
38
|
+
And:
|
39
|
+
|
40
|
+
```bash
|
41
|
+
bundle install
|
42
|
+
```
|
43
|
+
|
44
|
+
### Step 2: Generate Configuration and Migrations
|
45
|
+
|
46
|
+
Run:
|
47
|
+
|
48
|
+
```bash
|
49
|
+
rails generate mysql:search:install
|
50
|
+
```
|
51
|
+
|
52
|
+
This creates:
|
53
|
+
- `config/initializers/mysql_search.rb`: Search configuration.
|
54
|
+
- `app/models/search_index.rb`: Search index model.
|
55
|
+
- A migration to create `search_indices` table.
|
56
|
+
|
57
|
+
### Step 3: Run the Migration
|
58
|
+
|
59
|
+
```bash
|
60
|
+
rails db:migrate
|
61
|
+
```
|
62
|
+
|
63
|
+
### Step 4: Enable Search in Your Model
|
64
|
+
|
65
|
+
Add to your model (e.g., `Article`):
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class Article < ApplicationRecord
|
69
|
+
include MySQL::Search::Searchable
|
70
|
+
belongs_to :news_digest
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
### Step 5: Define the Indexing Schema (Source Class)
|
75
|
+
|
76
|
+
Create `app/search_sources/article_source.rb`:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
class ArticleSource < MySQL::Search::Source
|
80
|
+
schema content: {
|
81
|
+
title: :text,
|
82
|
+
content: :text,
|
83
|
+
type: ->(value) { I18n.t("article.types.#{value}") },
|
84
|
+
news_digest: {
|
85
|
+
title: :text,
|
86
|
+
published_at: [:date, :calendar_week]
|
87
|
+
}
|
88
|
+
}
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
**Available Formatters **
|
93
|
+
|
94
|
+
- **`:text`** - Extracts text content from the field
|
95
|
+
- **`:date`** - Formats dates using the configured date format (e.g., "12.01.2025", format is configurable)
|
96
|
+
- **`:calendar_week`** - Extracts calendar week information (e.g., "week 42", format is configurable)
|
97
|
+
- **`Proc`** - Custom extraction logic with access to the attribute value
|
98
|
+
- **Nested Associations** - Supports nested associations
|
99
|
+
- **Your formatters**. Check the [source code](lib/mysql/search/utils/formatter.rb) for more details.
|
100
|
+
|
101
|
+
### Step 6: Index Existing Data
|
102
|
+
|
103
|
+
```bash
|
104
|
+
rails mysql:search:reindex
|
105
|
+
```
|
106
|
+
|
107
|
+
This command populates the `search_indices` table with existing data from your model(s), using the schema defined in your source class.
|
108
|
+
|
109
|
+
### Step 7: Use Search in Controllers or Services
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
results = Article.full_text_search("Ruby on Rails")
|
113
|
+
```
|
114
|
+
|
115
|
+
**That’s it!** Users now get fast, scalable, and relevant search—no complex SQL, external services, or maintenance headaches.
|
116
|
+
|
117
|
+
## Advanced Scenarios: Multi-Column Search for Roles and Contexts
|
118
|
+
|
119
|
+
Real projects rarely need "single-column search." Business logic often requires showing different data to different users, supporting flexible filters, and ensuring privacy. `activerecord-mysql-search` supports this out of the box. For example, clients, sellers, and admins each need their own "view of the world." The gem lets you create separate indexes per role:
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
class ProductSource < MySQL::Search::Source
|
123
|
+
schema content: {
|
124
|
+
name: :text,
|
125
|
+
description: :text,
|
126
|
+
brand: :text,
|
127
|
+
reviews: { content: :text, rating: :text }
|
128
|
+
},
|
129
|
+
# Extra information for seller's search
|
130
|
+
seller_extra: {
|
131
|
+
sku: :text,
|
132
|
+
internal_notes: :text,
|
133
|
+
supplier: { name: :text, contact_info: :text },
|
134
|
+
},
|
135
|
+
# Even more detailed information for admin's search
|
136
|
+
admin_extra: {
|
137
|
+
created_by: { name: :text, email: :text }
|
138
|
+
}
|
139
|
+
end
|
140
|
+
```
|
141
|
+
|
142
|
+
Add columns and indexes to `SearchIndex`:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
class ExtraContentForSearchIndices < ActiveRecord::Migration[7.1]
|
146
|
+
def change
|
147
|
+
add_column :search_indices, :seller_extra, :text
|
148
|
+
add_column :search_indices, :admin_extra, :text
|
149
|
+
|
150
|
+
add_index :search_indices, [:content, :seller_extra], type: :fulltext
|
151
|
+
add_index :search_indices, [:content, :seller_extra, :admin_extra], type: :fulltext
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
Now, sellers search with:
|
157
|
+
|
158
|
+
```ruby
|
159
|
+
results = Product.full_text_search("Ruby on Rails", search_column: [:content, :seller_extra])
|
160
|
+
```
|
161
|
+
|
162
|
+
Admins use:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
results = Product.full_text_search("Ruby on Rails", search_column: [:content, :seller_extra, :admin_extra])
|
166
|
+
```
|
167
|
+
|
168
|
+
You can completely separate search contexts for different roles. In this case, there is no need to create combined indexes, just use different columns and separate indexes for each role.
|
169
|
+
|
170
|
+
### What if I use methods that don't trigger ActiveRecord callbacks?
|
171
|
+
|
172
|
+
Using `#update_column` and other methods that don't trigger ActiveRecord callbacks can lead to search index desynchronization. Solution: use `#update` or `#save` to update records to ensure indexes remain current. If you don't have this option, the gem provides the following tool to maintain index consistency.
|
173
|
+
|
174
|
+
In this case, the gem relies on the `updated_at` column. You can delegate keeping this column up-to-date to the database itself using a trigger. Create a migration using the generator:
|
175
|
+
|
176
|
+
```bash
|
177
|
+
rails generate mysql:search:create_triggers
|
178
|
+
```
|
179
|
+
|
180
|
+
This migration will create a trigger in each table that will update the `updated_at` column when records are modified, and will also add a monkey-patch to ActiveRecord's `#timestamps` method in migrations (to automatically add this trigger to future tables). This allows maintaining search index relevance using one or more of the following tools:
|
181
|
+
|
182
|
+
- Rake task `rails mysql:search:actualize[1.hour]` - periodically checks and updates indexes, syncing them with the current database state. You can configure it to run via cron.
|
183
|
+
- `MySQL::Search::Jobs::ScheduledUpdaterJob` - a background job that periodically checks and updates indexes. Example for Solid Queue:
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
# config/recurring.yml
|
187
|
+
actualize_search_indices:
|
188
|
+
class: MySQL::Search::Jobs::ScheduledUpdaterJob
|
189
|
+
args: [:daily]
|
190
|
+
schedule: every day at noon
|
191
|
+
```
|
192
|
+
|
193
|
+
- Full reindexing via rake task `mysql:search:reindex` - if you want to completely refresh index content, for example after migrations or schema changes. In this case, adding SQL triggers isn't required. You can also use this task to reindex specific models by passing their names as arguments, e.g., `rails mysql:search:reindex[Article]`.
|
194
|
+
|
195
|
+
## Configuration
|
196
|
+
|
197
|
+
Configure the gem in `config/initializers/mysql_search.rb`:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
MySQL::Search.configure do |config|
|
201
|
+
# Model class name for search indices (default: 'SearchIndex')
|
202
|
+
config.search_index_class_name = 'SearchIndex'
|
203
|
+
|
204
|
+
# Path to search source classes (default: 'app/search_sources')
|
205
|
+
config.sources_path = 'app/search_sources'
|
206
|
+
|
207
|
+
# Automatically update search index when models change (default: true)
|
208
|
+
config.automatic_update = true
|
209
|
+
|
210
|
+
# Process index updates asynchronously (default: false)
|
211
|
+
config.update_asyncronously = false
|
212
|
+
|
213
|
+
# Date format for date fields (default: '%d.%m.%Y')
|
214
|
+
config.date_format = '%d.%m.%Y'
|
215
|
+
|
216
|
+
# Calendar week format (default: 'week %V')
|
217
|
+
config.calendar_week_format = 'week %V'
|
218
|
+
end
|
219
|
+
```
|
220
|
+
|
221
|
+
### MySQL FULLTEXT Limitations
|
222
|
+
|
223
|
+
- **Minimum word length**: MySQL ignores words shorter than 4 characters by default (`ft_min_word_len`)
|
224
|
+
- **Stop words**: Common words like "the", "and", "or" are ignored
|
225
|
+
- **Memory usage**: FULLTEXT indexes can be memory-intensive for large datasets
|
226
|
+
|
227
|
+
## Development
|
228
|
+
|
229
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
230
|
+
|
231
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
232
|
+
|
233
|
+
## Contributing
|
234
|
+
|
235
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ddunicorn/activerecord-mysql-search.rubygem
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
|
5
|
+
load 'tasks/actualize.rake'
|
6
|
+
load 'tasks/reindex.rake'
|
7
|
+
|
8
|
+
require 'rspec/core/rake_task'
|
9
|
+
|
10
|
+
RSpec::Core::RakeTask.new(:spec)
|
11
|
+
|
12
|
+
require 'rubocop/rake_task'
|
13
|
+
|
14
|
+
RuboCop::RakeTask.new
|
15
|
+
|
16
|
+
task default: %i[spec rubocop]
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'activerecord-mysql-search'
|
5
|
+
spec.version = '0.1.0'
|
6
|
+
spec.authors = ['Daydream Unicorn GmbH & Co. KG']
|
7
|
+
spec.email = ['hello@daydreamunicorn.com']
|
8
|
+
|
9
|
+
spec.summary = 'Full Text Search for ActiveRecord with MySQL'
|
10
|
+
spec.description = <<~DESC
|
11
|
+
This gem provides a simple way to perform full-text search in MySQL databases using ActiveRecord.
|
12
|
+
It allows you to define search scopes and perform searches on your models with ease.
|
13
|
+
DESC
|
14
|
+
spec.homepage = 'https://github.com/ddunicorn/activerecord-mysql-search.rubygem/blob/main/README.md'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
spec.required_ruby_version = '>= 3.1.0'
|
17
|
+
|
18
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
19
|
+
spec.metadata['source_code_uri'] = 'https://github.com/ddunicorn/activerecord-mysql-search.rubygem'
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/ddunicorn/activerecord-mysql-search.rubygem/blob/main/CHANGELOG.md'
|
21
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
spec.bindir = 'exe'
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ['lib']
|
33
|
+
|
34
|
+
# Uncomment to register a new dependency of your gem
|
35
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
36
|
+
|
37
|
+
# For more information and examples about making a new gem, check out our
|
38
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was generated by Appraisal
|
4
|
+
|
5
|
+
source 'https://rubygems.org'
|
6
|
+
|
7
|
+
gem 'appraisal'
|
8
|
+
gem 'concurrent-ruby', '1.3.4'
|
9
|
+
gem 'database_cleaner-active_record'
|
10
|
+
gem 'mysql2'
|
11
|
+
gem 'pry'
|
12
|
+
gem 'rails', '~> 7.0.0'
|
13
|
+
gem 'rake', '~> 13.0'
|
14
|
+
gem 'rspec'
|
15
|
+
gem 'rubocop'
|
16
|
+
gem 'rubocop-rails'
|
17
|
+
gem 'rubocop-rake'
|
18
|
+
gem 'rubocop-rspec'
|
19
|
+
|
20
|
+
gemspec path: '../'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was generated by Appraisal
|
4
|
+
|
5
|
+
source 'https://rubygems.org'
|
6
|
+
|
7
|
+
gem 'appraisal'
|
8
|
+
gem 'database_cleaner-active_record'
|
9
|
+
gem 'mysql2'
|
10
|
+
gem 'pry'
|
11
|
+
gem 'rails', '~> 7.1.0'
|
12
|
+
gem 'rake', '~> 13.0'
|
13
|
+
gem 'rspec'
|
14
|
+
gem 'rubocop'
|
15
|
+
gem 'rubocop-rails'
|
16
|
+
gem 'rubocop-rake'
|
17
|
+
gem 'rubocop-rspec'
|
18
|
+
|
19
|
+
gemspec path: '../'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was generated by Appraisal
|
4
|
+
|
5
|
+
source 'https://rubygems.org'
|
6
|
+
|
7
|
+
gem 'appraisal'
|
8
|
+
gem 'database_cleaner-active_record'
|
9
|
+
gem 'mysql2'
|
10
|
+
gem 'pry'
|
11
|
+
gem 'rails', '~> 7.2.0'
|
12
|
+
gem 'rake', '~> 13.0'
|
13
|
+
gem 'rspec'
|
14
|
+
gem 'rubocop'
|
15
|
+
gem 'rubocop-rails'
|
16
|
+
gem 'rubocop-rake'
|
17
|
+
gem 'rubocop-rspec'
|
18
|
+
|
19
|
+
gemspec path: '../'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This file was generated by Appraisal
|
4
|
+
|
5
|
+
source 'https://rubygems.org'
|
6
|
+
|
7
|
+
gem 'appraisal'
|
8
|
+
gem 'database_cleaner-active_record'
|
9
|
+
gem 'mysql2'
|
10
|
+
gem 'pry'
|
11
|
+
gem 'rails', '~> 8.0.0'
|
12
|
+
gem 'rake', '~> 13.0'
|
13
|
+
gem 'rspec'
|
14
|
+
gem 'rubocop'
|
15
|
+
gem 'rubocop-rails'
|
16
|
+
gem 'rubocop-rake'
|
17
|
+
gem 'rubocop-rspec'
|
18
|
+
|
19
|
+
gemspec path: '../'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Generates an initializer file for the MySQL Search gem
|
4
|
+
module MySQL
|
5
|
+
module Search
|
6
|
+
# This generator creates a migration to add a trigger for automatically updating the `updated_at` column in MySQL.
|
7
|
+
# It also includes a monkey-patch for ActiveRecord's `#timestamps` method to use
|
8
|
+
# MySQL's `DATETIME ON UPDATE CURRENT_TIMESTAMP` for the `updated_at` column.
|
9
|
+
class CreateTriggerGenerator < Rails::Generators::Base
|
10
|
+
include Rails::Generators::Migration
|
11
|
+
|
12
|
+
namespace 'mysql:search:create_trigger'
|
13
|
+
source_root File.expand_path('templates', __dir__)
|
14
|
+
|
15
|
+
def self.next_migration_number(_path)
|
16
|
+
Time.now.utc.strftime('%Y%m%d%H%M%S')
|
17
|
+
end
|
18
|
+
|
19
|
+
desc 'Generates migration to add a trigger to automatically update the `updated_at` ' \
|
20
|
+
'column in MySQL + ActiveRecord monkey-patch for `#timestamps`'
|
21
|
+
def create_config_and_migration_files
|
22
|
+
migration_template 'db/migrate/enable_auto_update_of_updated_at.rb',
|
23
|
+
'db/migrate/enable_auto_update_of_updated_at.rb'
|
24
|
+
template 'config/initializers/active_record_ext.rb'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Generates an initializer file for the MySQL Search gem
|
4
|
+
module MySQL
|
5
|
+
module Search
|
6
|
+
# This generator creates an initializer file for configuring MySQL Search.
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
8
|
+
include Rails::Generators::Migration
|
9
|
+
|
10
|
+
namespace 'mysql:search:install'
|
11
|
+
source_root File.expand_path('templates', __dir__)
|
12
|
+
|
13
|
+
def self.next_migration_number(_path)
|
14
|
+
Time.now.utc.strftime('%Y%m%d%H%M%S')
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'Creates an initializer file for MySQL Search configuration'
|
18
|
+
def create_config_and_migration_files
|
19
|
+
template 'config/initializers/mysql_search.rb'
|
20
|
+
template 'app/models/search_index.rb'
|
21
|
+
migration_template 'db/migrate/create_search_indices.rb', 'db/migrate/create_search_indices.rb'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module ConnectionAdapters
|
5
|
+
# Overrides the default `add_timestamps` method to use MySQL's `DATETIME ON UPDATE CURRENT_TIMESTAMP`
|
6
|
+
# for the `updated_at` column.
|
7
|
+
# This allows the `updated_at` column to automatically update its value whenever the row is updated.
|
8
|
+
module SchemaStatements
|
9
|
+
def add_timestamps(table_name, **options)
|
10
|
+
options[:null] = false if options[:null].nil?
|
11
|
+
|
12
|
+
options[:precision] = 6 if !options.key?(:precision) && supports_datetime_with_precision?
|
13
|
+
|
14
|
+
add_column table_name, :created_at, :datetime, **options
|
15
|
+
add_column table_name, :updated_at, 'DATETIME ON UPDATE CURRENT_TIMESTAMP', **options
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ActiveRecord
|
22
|
+
module ConnectionAdapters
|
23
|
+
# Overrides the `timestamps` method in `TableDefinition` to use MySQL's `DATETIME ON UPDATE CURRENT_TIMESTAMP`
|
24
|
+
# for the `updated_at` column.
|
25
|
+
# This allows the `updated_at` column to automatically update its value whenever the row is updated.
|
26
|
+
class TableDefinition
|
27
|
+
def timestamps(**options)
|
28
|
+
options[:null] = false if options[:null].nil?
|
29
|
+
|
30
|
+
options[:precision] = 6 if !options.key?(:precision) && @conn.supports_datetime_with_precision?
|
31
|
+
|
32
|
+
column(:created_at, :datetime, **options)
|
33
|
+
column(:updated_at, 'DATETIME ON UPDATE CURRENT_TIMESTAMP', **options)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Configure MySQL::Search settings
|
4
|
+
MySQL::Search.configure do |config|
|
5
|
+
# Defines the name of the search index activerecord model.
|
6
|
+
config.search_index_class_name = 'SearchIndex'
|
7
|
+
|
8
|
+
# Location of the search sources folder
|
9
|
+
config.sources_path = 'app/search_sources'
|
10
|
+
|
11
|
+
# Enables the search index to be automatically updated via source and nested models callbacks "on save"
|
12
|
+
config.automatic_update = true
|
13
|
+
|
14
|
+
# Use ActiveJob to update the search index in the background
|
15
|
+
config.update_asyncronously = true
|
16
|
+
|
17
|
+
# Defines the format for `calendar_week` formatter.
|
18
|
+
config.calendar_week_format = 'week %W'
|
19
|
+
|
20
|
+
# Defines the format for `date` formater.
|
21
|
+
config.date_format = '%d.%m.%Y'
|
22
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This migration creates a search index table for MySQL full-text search.
|
4
|
+
class CreateSearchIndices < ActiveRecord::Migration[7.0]
|
5
|
+
create_table :search_indices do |t|
|
6
|
+
t.text :content, null: false
|
7
|
+
t.references :searchable, polymorphic: true, null: false
|
8
|
+
|
9
|
+
t.index :content, type: :fulltext
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This migration enables automatic updates for `updated_at` columns in all tables that have this column.
|
4
|
+
# It changes the column type to `DATETIME ON UPDATE CURRENT_TIMESTAMP`, which allows MySQL to automatically update
|
5
|
+
# the `updated_at` timestamp whenever the row is updated.
|
6
|
+
class EnableAutoUpdateForUpdatedAtColumns < ActiveRecord::Migration[7.0]
|
7
|
+
def up
|
8
|
+
ActiveRecord::Base.connection.tables.each do |table_name|
|
9
|
+
next unless column_exists?(table_name, :updated_at)
|
10
|
+
|
11
|
+
change_column table_name, :updated_at, 'DATETIME ON UPDATE CURRENT_TIMESTAMP'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def down
|
16
|
+
ActiveRecord::Base.connection.tables.each do |table_name|
|
17
|
+
next unless column_exists?(table_name, :updated_at)
|
18
|
+
|
19
|
+
change_column table_name, :updated_at, :datetime
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|