activerecord-mysql-search 0.2.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dec7afc185ebe9baa4de931681c27b4718d138126111fbeb1467819b958e5883
4
- data.tar.gz: 21d49c730ea6064c0c6ba1a1993dd24e09273463e082d5a1d57fff9fe2ad17a9
3
+ metadata.gz: bf223abbfa9e47d49642b4f8f52d05eb60256749f6f1ca22b40f9a17db7aad57
4
+ data.tar.gz: 197c6c7c9c15de41a0ed7bd3578a988be3beb366acac94f249e1aa0315747728
5
5
  SHA512:
6
- metadata.gz: 706aa3813e15aa8814ab3f2c908de266db1ab65d09a7b96f00bc6234cabcc6579dd8413db97bcd6b4754c6375e1464156dcc5c930345cc193eaa532ee4794f03
7
- data.tar.gz: 0ce9414a415f48805420657fbc14c9e7b7f7111d2e4641dd6b127601c5100915ffaba100fea62023c9a0735d044d9362e436761e39f5c98e6632fdf3d324528b
6
+ metadata.gz: 225730631d733499a6fe947fc8c90ed27f61441939206516f54b5bc951c520130692ffdab155ca072a2986fc9c6d6e2f4bd6e1d10f302be15aca133402dd2d5e
7
+ data.tar.gz: 5475a8bd4aaf08b71c694814abcee48ca74bbf28514e20085ab34cf12ae4f1418e0dee2b57578a97134de1d687d7efd6d79f004bc429b2c2fe7dabc01b6f30de
data/Appraisals CHANGED
@@ -16,3 +16,11 @@ end
16
16
  appraise 'rails_8.0' do
17
17
  gem 'rails', '~> 8.0.0'
18
18
  end
19
+
20
+ appraise 'rails_8.0' do
21
+ gem 'rails', '~> 8.0.0'
22
+ end
23
+
24
+ appraise 'rails_8.1' do
25
+ gem 'rails', '~> 8.1.0'
26
+ end
data/CHANGELOG.md CHANGED
@@ -1,23 +1,31 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [0.1.0] - 2025-07-22
3
+ ## [0.2.1] - 2026-07-15
4
4
 
5
- - Initial release
5
+ - Adds automatic loading of source classes on Rails startup via new `autoload_sources` configuration option (default: true)
6
+ - Adds `load_source_classes!` method for manually loading source classes
7
+ - Fixes source actualization query composition to preserve base STI scope while appending OR conditions for associated updates
8
+ - Adds regression coverage for STI subclass source relations with associated updates
9
+ - Extracts Arel `AGAINST` integration into a dedicated extension file
6
10
 
7
- ## [0.1.1] - 2025-08-05
11
+ ## [0.2.0] - 2025-09-25
8
12
 
9
- - Fixes load paths to rake tasks
13
+ - Removes text normalization from default `:text` formatter
14
+ - Search term passed as argument to the scope `.full_text_search` is not pre-normalized anymore
15
+ - Adds `register_format` method to register custom formatters
16
+
17
+ ## [0.1.3] - 2025-08-06
18
+
19
+ - Move rake tasks to the proper place + explicit tasks load
10
20
 
11
21
  ## [0.1.2] - 2025-08-12
12
22
 
13
23
  - Removes code of conduct
14
24
 
15
- ## [0.1.3] - 2025-08-06
25
+ ## [0.1.1] - 2025-08-05
16
26
 
17
- - Move rake tasks to the proper place + explicit tasks load
27
+ - Fixes load paths to rake tasks
18
28
 
19
- ## [0.2.0] - 2025-09-25
29
+ ## [0.1.0] - 2025-07-22
20
30
 
21
- - Removes text normalization from default `:text` formatter
22
- - Search term passed as argument to the scope `.full_text_search` is not pre-normalized anymore
23
- - Adds `register_format` method to register custom formatters
31
+ - Initial release
data/Gemfile CHANGED
@@ -16,6 +16,7 @@ gem 'database_cleaner-active_record'
16
16
  gem 'rspec'
17
17
 
18
18
  gem 'rubocop'
19
+ gem 'rubocop-performance'
19
20
  gem 'rubocop-rails'
20
21
  gem 'rubocop-rake'
21
22
  gem 'rubocop-rspec'
data/README.md CHANGED
@@ -213,6 +213,13 @@ MySQL::Search.configure do |config|
213
213
  # Path to search source classes (default: 'app/search_sources')
214
214
  config.sources_path = 'app/search_sources'
215
215
 
216
+ # Automatically load source classes on Rails startup (default: true)
217
+ # Loading of source classes assigns callbacks to the target models
218
+ # to keep the search index updated. In case of loading issue you can load
219
+ # the source classes manually with `MySQL::Search.load_source_classes!`
220
+ # and disable automatic initialization.
221
+ config.autoload_sources = true
222
+
216
223
  # Automatically update search index when models change (default: true)
217
224
  config.automatic_update = true
218
225
 
@@ -224,6 +231,9 @@ MySQL::Search.configure do |config|
224
231
 
225
232
  # Calendar week format (default: 'week %V')
226
233
  config.calendar_week_format = 'week %V'
234
+
235
+ # Register custom formatters
236
+ config.register_format(:upcase) { |value| value.to_s.upcase }
227
237
  end
228
238
  ```
229
239
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'activerecord-mysql-search'
5
- spec.version = '0.2.0'
5
+ spec.version = '0.2.1'
6
6
  spec.authors = ['Daydream Unicorn GmbH & Co. KG']
7
7
  spec.email = ['hello@daydreamunicorn.com']
8
8
 
@@ -13,8 +13,10 @@ gem 'rails', '~> 7.0.0'
13
13
  gem 'rake', '~> 13.0'
14
14
  gem 'rspec'
15
15
  gem 'rubocop'
16
+ gem 'rubocop-performance'
16
17
  gem 'rubocop-rails'
17
18
  gem 'rubocop-rake'
18
19
  gem 'rubocop-rspec'
20
+ gem 'ruby-lsp'
19
21
 
20
22
  gemspec path: '../'
@@ -12,8 +12,10 @@ gem 'rails', '~> 7.1.0'
12
12
  gem 'rake', '~> 13.0'
13
13
  gem 'rspec'
14
14
  gem 'rubocop'
15
+ gem 'rubocop-performance'
15
16
  gem 'rubocop-rails'
16
17
  gem 'rubocop-rake'
17
18
  gem 'rubocop-rspec'
19
+ gem 'ruby-lsp'
18
20
 
19
21
  gemspec path: '../'
@@ -12,8 +12,10 @@ gem 'rails', '~> 7.2.0'
12
12
  gem 'rake', '~> 13.0'
13
13
  gem 'rspec'
14
14
  gem 'rubocop'
15
+ gem 'rubocop-performance'
15
16
  gem 'rubocop-rails'
16
17
  gem 'rubocop-rake'
17
18
  gem 'rubocop-rspec'
19
+ gem 'ruby-lsp'
18
20
 
19
21
  gemspec path: '../'
@@ -12,8 +12,10 @@ gem 'rails', '~> 8.0.0'
12
12
  gem 'rake', '~> 13.0'
13
13
  gem 'rspec'
14
14
  gem 'rubocop'
15
+ gem 'rubocop-performance'
15
16
  gem 'rubocop-rails'
16
17
  gem 'rubocop-rake'
17
18
  gem 'rubocop-rspec'
19
+ gem 'ruby-lsp'
18
20
 
19
21
  gemspec path: '../'
@@ -0,0 +1,21 @@
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.1.0'
12
+ gem 'rake', '~> 13.0'
13
+ gem 'rspec'
14
+ gem 'rubocop'
15
+ gem 'rubocop-performance'
16
+ gem 'rubocop-rails'
17
+ gem 'rubocop-rake'
18
+ gem 'rubocop-rspec'
19
+ gem 'ruby-lsp'
20
+
21
+ gemspec path: '../'
@@ -15,11 +15,7 @@ module ActiveRecord
15
15
  add_column table_name, :updated_at, 'DATETIME ON UPDATE CURRENT_TIMESTAMP', **options
16
16
  end
17
17
  end
18
- end
19
- end
20
18
 
21
- module ActiveRecord
22
- module ConnectionAdapters
23
19
  # Overrides the `timestamps` method in `TableDefinition` to use MySQL's `DATETIME ON UPDATE CURRENT_TIMESTAMP`
24
20
  # for the `updated_at` column.
25
21
  # This allows the `updated_at` column to automatically update its value whenever the row is updated.
@@ -2,21 +2,31 @@
2
2
 
3
3
  # Configure MySQL::Search settings
4
4
  MySQL::Search.configure do |config|
5
- # Defines the name of the search index activerecord model.
6
- config.search_index_class_name = 'SearchIndex'
5
+ # Model class name for search indices (default: 'SearchIndex')
6
+ # config.search_index_class_name = 'SearchIndex'
7
7
 
8
- # Location of the search sources folder
9
- config.sources_path = 'app/search_sources'
8
+ # Path to search source classes (default: 'app/search_sources')
9
+ # config.sources_path = 'app/search_sources'
10
10
 
11
- # Enables the search index to be automatically updated via source and nested models callbacks "on save"
12
- config.automatic_update = true
11
+ # Automatically load source classes on Rails startup (default: true)
12
+ # Loading of source classes assigns callbacks to the target models
13
+ # to keep the search index updated. In case of loading issue you can load
14
+ # the source classes manually with `MySQL::Search.load_source_classes!`
15
+ # and disable automatic initialization.
16
+ # config.autoload_sources = true
13
17
 
14
- # Use ActiveJob to update the search index in the background
15
- config.update_asyncronously = true
18
+ # Automatically update search index when models change (default: true)
19
+ # config.automatic_update = true
16
20
 
17
- # Defines the format for `calendar_week` formatter.
18
- config.calendar_week_format = 'week %W'
21
+ # Process index updates asynchronously (default: false)
22
+ # config.update_asyncronously = false
19
23
 
20
- # Defines the format for `date` formater.
21
- config.date_format = '%d.%m.%Y'
24
+ # Date format for date fields (default: '%d.%m.%Y')
25
+ # config.date_format = '%d.%m.%Y'
26
+
27
+ # Calendar week format (default: 'week %V')
28
+ # config.calendar_week_format = 'week %V'
29
+
30
+ # Register custom formatters
31
+ # config.register_format(:upcase) { |value| value.to_s.upcase }
22
32
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'arel'
4
+
5
+ module Arel
6
+ module Visitors
7
+ # Custom visitor for MySQL to handle the `AGAINST` clause in full-text search.
8
+ class MySQL
9
+ def visit_Arel_Nodes_Against(node, collector) # rubocop:disable Naming/MethodName
10
+ visit(node.left, collector) << ' AGAINST ('
11
+ visit(node.right, collector) << ')'
12
+ end
13
+ end
14
+ end
15
+
16
+ module Nodes
17
+ # Represents the `AGAINST` clause used in MySQL full-text search queries.
18
+ class Against < Arel::Nodes::Matches
19
+ end
20
+ end
21
+
22
+ # Adds a method to the `Arel::Nodes::Node` class to allow for full-text search queries.
23
+ module Predications
24
+ def against(other)
25
+ Arel::Nodes::Against.new(self, quoted_node(other))
26
+ end
27
+ end
28
+ end
@@ -1,35 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'arel'
4
-
5
- module Arel
6
- module Visitors
7
- # Custom visitor for MySQL to handle the `AGAINST` clause in full-text search.
8
- class MySQL
9
- def visit_Arel_Nodes_Against(node, collector) # rubocop:disable Naming/MethodName
10
- visit(node.left, collector) << ' AGAINST ('
11
- visit(node.right, collector) << ')'
12
- end
13
- end
14
- end
15
- end
16
-
17
- module Arel
18
- module Nodes
19
- # Represents the `AGAINST` clause used in MySQL full-text search queries.
20
- class Against < Arel::Nodes::Matches
21
- end
22
- end
23
- end
24
-
25
- module Arel
26
- # Adds a method to the `Arel::Nodes::Node` class to allow for full-text search queries.
27
- module Predications
28
- def against(other)
29
- Arel::Nodes::Against.new(self, quoted_node(other))
30
- end
31
- end
32
- end
3
+ require_relative '../extensions/arel_against'
33
4
 
34
5
  module MySQL
35
6
  module Search
@@ -14,24 +14,37 @@ module MySQL
14
14
 
15
15
  def call(time_ago)
16
16
  joins_args = source_class_name.constantize.joins_args
17
- relation = source_relation.left_joins(joins_args).where(updated_at: time_ago..)
17
+ base_relation = source_relation.left_joins(joins_args)
18
+ relation = base_relation.where(source_relation.klass.arel_table[:updated_at].gteq(time_ago))
18
19
 
19
- append_conditions(relation, source_relation, joins_args, time_ago)
20
+ append_conditions(relation, source_relation.klass, joins_args, time_ago, base_relation)
20
21
  end
21
22
 
22
23
  private
23
24
 
24
- def append_conditions(relation, root_class, config, time_ago)
25
+ def append_conditions(relation, root_class, config, time_ago, base_relation)
25
26
  case config
26
- when Array then config.reduce(relation) { |rel, item| append_conditions(rel, root_class, item, time_ago) }
27
+ when Array
28
+ append_array_conditions(relation, root_class, config, time_ago, base_relation)
27
29
  when Hash
28
- relation = append_conditions(relation, root_class, config.keys, time_ago)
29
-
30
- config.reduce(relation) do |rel, (root_association, nested_config)|
31
- append_conditions(rel, association_class(root_class, root_association), nested_config, time_ago)
32
- end
30
+ append_hash_conditions(relation, root_class, config, time_ago, base_relation)
33
31
  else
34
- append_or(relation, association_class(root_class, config), time_ago)
32
+ append_or(relation, association_class(root_class, config), time_ago, base_relation)
33
+ end
34
+ end
35
+
36
+ def append_array_conditions(relation, root_class, config, time_ago, base_relation)
37
+ config.reduce(relation) do |rel, item|
38
+ append_conditions(rel, root_class, item, time_ago, base_relation)
39
+ end
40
+ end
41
+
42
+ def append_hash_conditions(relation, root_class, config, time_ago, base_relation)
43
+ relation = append_conditions(relation, root_class, config.keys, time_ago, base_relation)
44
+
45
+ config.reduce(relation) do |rel, (root_association, nested_config)|
46
+ append_conditions(rel, association_class(root_class, root_association), nested_config, time_ago,
47
+ base_relation)
35
48
  end
36
49
  end
37
50
 
@@ -39,10 +52,10 @@ module MySQL
39
52
  root_class.reflect_on_association(association).klass
40
53
  end
41
54
 
42
- def append_or(relation, model, time_ago)
55
+ def append_or(relation, model, time_ago, base_relation)
43
56
  return relation unless model.column_names.include?('updated_at')
44
57
 
45
- relation.or(model.where(updated_at: time_ago..))
58
+ relation.or(base_relation.where(model.arel_table[:updated_at].gteq(time_ago)))
46
59
  end
47
60
  end
48
61
  end
@@ -6,6 +6,10 @@ module MySQL
6
6
  class Railtie < Rails::Railtie
7
7
  railtie_name :mysql_search
8
8
 
9
+ config.to_prepare do
10
+ MySQL::Search.load_source_classes! if MySQL::Search.autoload_sources
11
+ end
12
+
9
13
  rake_tasks do
10
14
  load 'tasks/mysql/search/actualize.rake'
11
15
  load 'tasks/mysql/search/reindex.rake'
@@ -7,9 +7,9 @@ module MySQL
7
7
  class Formatter
8
8
  attr_reader :value, :formatter
9
9
 
10
- def self.register(name, &block)
10
+ def self.register(name, &)
11
11
  define_method(name) do
12
- block.call(value)
12
+ yield(value)
13
13
  end
14
14
  end
15
15
 
data/lib/mysql/search.rb CHANGED
@@ -18,6 +18,7 @@ module MySQL
18
18
  # Runtime configuration
19
19
  mattr_accessor :automatic_update, default: true
20
20
  mattr_accessor :update_asyncronously, default: false
21
+ mattr_accessor :autoload_sources, default: true
21
22
 
22
23
  # Search Index & Sources
23
24
  mattr_accessor :search_index_class_name, default: 'SearchIndex'
@@ -36,11 +37,16 @@ module MySQL
36
37
  end
37
38
 
38
39
  def source_classes
39
- @source_classes ||= Dir.glob("#{sources_path}/**/*.rb").filter_map do |file|
40
+ Dir.glob("#{sources_path}/**/*.rb").filter_map do |file|
40
41
  file.sub("#{sources_path}/", '').sub('.rb', '').camelize.safe_constantize
41
42
  end
42
43
  end
43
44
 
45
+ # Keep a separate API entrypoint used by the Railtie initialization hook.
46
+ def load_source_classes!
47
+ source_classes
48
+ end
49
+
44
50
  def configure
45
51
  yield self
46
52
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-mysql-search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daydream Unicorn GmbH & Co. KG
@@ -32,6 +32,7 @@ files:
32
32
  - gemfiles/rails_7.1.gemfile
33
33
  - gemfiles/rails_7.2.gemfile
34
34
  - gemfiles/rails_8.0.gemfile
35
+ - gemfiles/rails_8.1.gemfile
35
36
  - lib/activerecord-mysql-search.rb
36
37
  - lib/generators/mysql/search/create_trigger_generator.rb
37
38
  - lib/generators/mysql/search/install_generator.rb
@@ -42,6 +43,7 @@ files:
42
43
  - lib/generators/mysql/search/templates/db/migrate/enable_auto_update_of_updated_at.rb
43
44
  - lib/mysql/search.rb
44
45
  - lib/mysql/search/callbacks.rb
46
+ - lib/mysql/search/extensions/arel_against.rb
45
47
  - lib/mysql/search/grabber.rb
46
48
  - lib/mysql/search/jobs.rb
47
49
  - lib/mysql/search/jobs/scheduled_updater_job.rb
@@ -79,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
81
  - !ruby/object:Gem::Version
80
82
  version: '0'
81
83
  requirements: []
82
- rubygems_version: 3.6.9
84
+ rubygems_version: 4.0.16
83
85
  specification_version: 4
84
86
  summary: Full Text Search for ActiveRecord with MySQL
85
87
  test_files: []