pg_search 2.3.6 → 2.3.8

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +47 -35
  3. data/.github/workflows/codeql.yml +87 -0
  4. data/.standard.yml +6 -0
  5. data/CHANGELOG.md +23 -0
  6. data/Gemfile +21 -6
  7. data/LICENSE +1 -1
  8. data/README.md +133 -114
  9. data/Rakefile +4 -13
  10. data/lib/pg_search/configuration/column.rb +6 -4
  11. data/lib/pg_search/configuration/foreign_column.rb +1 -1
  12. data/lib/pg_search/configuration.rb +11 -27
  13. data/lib/pg_search/document.rb +8 -8
  14. data/lib/pg_search/features/dmetaphone.rb +1 -1
  15. data/lib/pg_search/features/trigram.rb +4 -4
  16. data/lib/pg_search/features/tsearch.rb +23 -30
  17. data/lib/pg_search/migration/dmetaphone_generator.rb +2 -2
  18. data/lib/pg_search/migration/generator.rb +5 -5
  19. data/lib/pg_search/migration/multisearch_generator.rb +2 -2
  20. data/lib/pg_search/model.rb +12 -14
  21. data/lib/pg_search/multisearch/rebuilder.rb +4 -3
  22. data/lib/pg_search/multisearch.rb +4 -2
  23. data/lib/pg_search/multisearchable.rb +7 -7
  24. data/lib/pg_search/normalizer.rb +17 -7
  25. data/lib/pg_search/scope_options.rb +36 -8
  26. data/lib/pg_search/tasks.rb +2 -2
  27. data/lib/pg_search/version.rb +1 -1
  28. data/lib/pg_search.rb +3 -3
  29. data/pg_search.gemspec +16 -31
  30. data/spec/integration/associations_spec.rb +118 -106
  31. data/spec/integration/deprecation_spec.rb +12 -9
  32. data/spec/integration/pagination_spec.rb +1 -0
  33. data/spec/integration/pg_search_spec.rb +343 -298
  34. data/spec/integration/single_table_inheritance_spec.rb +7 -5
  35. data/spec/lib/pg_search/configuration/association_spec.rb +17 -15
  36. data/spec/lib/pg_search/configuration/column_spec.rb +13 -1
  37. data/spec/lib/pg_search/configuration/foreign_column_spec.rb +5 -4
  38. data/spec/lib/pg_search/features/dmetaphone_spec.rb +4 -4
  39. data/spec/lib/pg_search/features/trigram_spec.rb +32 -33
  40. data/spec/lib/pg_search/features/tsearch_spec.rb +57 -39
  41. data/spec/lib/pg_search/multisearch/rebuilder_spec.rb +70 -20
  42. data/spec/lib/pg_search/multisearch_spec.rb +8 -8
  43. data/spec/lib/pg_search/multisearchable_spec.rb +34 -26
  44. data/spec/lib/pg_search/normalizer_spec.rb +11 -11
  45. data/spec/lib/pg_search_spec.rb +22 -18
  46. data/spec/spec_helper.rb +3 -18
  47. data/spec/support/database.rb +6 -6
  48. metadata +11 -219
  49. data/.codeclimate.yml +0 -17
  50. data/.rubocop.yml +0 -137
  51. data/.travis.yml +0 -37
  52. data/spec/.rubocop.yml +0 -14
  53. data/spec/integration/.rubocop.yml +0 -11
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_support/core_ext/module/delegation"
4
- require 'active_support/deprecation'
4
+ require "active_support/deprecation"
5
5
 
6
6
  module PgSearch
7
7
  module Features
8
- class TSearch < Feature # rubocop:disable Metrics/ClassLength
8
+ class TSearch < Feature
9
9
  def self.valid_options
10
10
  super + %i[dictionary prefix negation any_word normalization tsvector_column highlight]
11
11
  end
@@ -36,12 +36,11 @@ module PgSearch
36
36
  end
37
37
 
38
38
  def ts_headline_options
39
- return '' unless options[:highlight].is_a?(Hash)
39
+ return "" unless options[:highlight].is_a?(Hash)
40
40
 
41
41
  headline_options
42
42
  .merge(deprecated_headline_options)
43
- .map { |key, value| "#{key} = #{value}" unless value.nil? }
44
- .compact
43
+ .filter_map { |key, value| "#{key} = #{value}" unless value.nil? }
45
44
  .join(", ")
46
45
  end
47
46
 
@@ -50,35 +49,30 @@ module PgSearch
50
49
 
51
50
  %w[
52
51
  StartSel StopSel MaxFragments MaxWords MinWords ShortWord FragmentDelimiter HighlightAll
53
- ].reduce({}) do |hash, key|
54
- hash.tap do
55
- value = indifferent_options[:highlight][key]
56
-
57
- hash[key] = ts_headline_option_value(value)
58
- end
52
+ ].to_h do |key|
53
+ [key, ts_headline_option_value(indifferent_options[:highlight][key])]
59
54
  end
60
55
  end
61
56
 
62
- def deprecated_headline_options # rubocop:disable Metrics/MethodLength
57
+ def deprecated_headline_options
63
58
  indifferent_options = options.with_indifferent_access
64
59
 
65
60
  %w[
66
61
  start_sel stop_sel max_fragments max_words min_words short_word fragment_delimiter highlight_all
67
- ].reduce({}) do |hash, deprecated_key|
68
- hash.tap do
69
- value = indifferent_options[:highlight][deprecated_key]
62
+ ].each_with_object({}) do |deprecated_key, hash|
63
+ value = indifferent_options[:highlight][deprecated_key]
64
+ next if value.nil?
70
65
 
71
- unless value.nil?
72
- key = deprecated_key.camelize
66
+ key = deprecated_key.camelize
73
67
 
74
- ActiveSupport::Deprecation.warn(
75
- "pg_search 3.0 will no longer accept :#{deprecated_key} as an argument to :ts_headline, " \
76
- "use :#{key} instead."
77
- )
68
+ warn(
69
+ "pg_search 3.0 will no longer accept :#{deprecated_key} as an argument to :ts_headline, " \
70
+ "use :#{key} instead.",
71
+ category: :deprecated,
72
+ uplevel: 1
73
+ )
78
74
 
79
- hash[key] = ts_headline_option_value(value)
80
- end
81
- end
75
+ hash[key] = ts_headline_option_value(value)
82
76
  end
83
77
  end
84
78
 
@@ -95,11 +89,10 @@ module PgSearch
95
89
  end
96
90
  end
97
91
 
98
- DISALLOWED_TSQUERY_CHARACTERS = /['?\\:‘’]/.freeze
99
-
92
+ DISALLOWED_TSQUERY_CHARACTERS = /['?\\:]/ # standard:disable Lint/UselessConstantScoping
100
93
  def tsquery_for_term(unsanitized_term)
101
94
  if options[:negation] && unsanitized_term.start_with?("!")
102
- unsanitized_term[0] = ''
95
+ unsanitized_term[0] = ""
103
96
  negated = true
104
97
  end
105
98
 
@@ -117,7 +110,7 @@ module PgSearch
117
110
  # If :negated is true, then the term will have ! prepended to the front.
118
111
  def tsquery_expression(term_sql, negated:, prefix:)
119
112
  terms = [
120
- (Arel::Nodes.build_quoted('!') if negated),
113
+ (Arel::Nodes.build_quoted("!") if negated),
121
114
  Arel::Nodes.build_quoted("' "),
122
115
  term_sql,
123
116
  Arel::Nodes.build_quoted(" '"),
@@ -134,7 +127,7 @@ module PgSearch
134
127
 
135
128
  query_terms = query.split.compact
136
129
  tsquery_terms = query_terms.map { |term| tsquery_for_term(term) }
137
- tsquery_terms.join(options[:any_word] ? ' || ' : ' && ')
130
+ tsquery_terms.join(options[:any_word] ? " || " : " && ")
138
131
  end
139
132
 
140
133
  def tsdocument
@@ -152,7 +145,7 @@ module PgSearch
152
145
  end
153
146
  end
154
147
 
155
- tsdocument_terms.join(' || ')
148
+ tsdocument_terms.join(" || ")
156
149
  end
157
150
 
158
151
  # From http://www.postgresql.org/docs/8.3/static/textsearch-controls.html
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pg_search/migration/generator'
3
+ require "pg_search/migration/generator"
4
4
 
5
5
  module PgSearch
6
6
  module Migration
7
7
  class DmetaphoneGenerator < Generator
8
8
  def migration_name
9
- 'add_pg_search_dmetaphone_support_functions'
9
+ "add_pg_search_dmetaphone_support_functions"
10
10
  end
11
11
  end
12
12
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_record'
4
- require 'rails/generators/base'
3
+ require "active_record"
4
+ require "rails/generators/base"
5
5
 
6
6
  module PgSearch
7
7
  module Migration
@@ -10,19 +10,19 @@ module PgSearch
10
10
 
11
11
  def self.inherited(subclass)
12
12
  super
13
- subclass.source_root File.expand_path('templates', __dir__)
13
+ subclass.source_root File.expand_path("templates", __dir__)
14
14
  end
15
15
 
16
16
  def create_migration
17
17
  now = Time.now.utc
18
- filename = "#{now.strftime('%Y%m%d%H%M%S')}_#{migration_name}.rb"
18
+ filename = "#{now.strftime("%Y%m%d%H%M%S")}_#{migration_name}.rb"
19
19
  template "#{migration_name}.rb.erb", "db/migrate/#{filename}", migration_version
20
20
  end
21
21
 
22
22
  private
23
23
 
24
24
  def read_sql_file(filename)
25
- sql_directory = File.expand_path('../../../sql', __dir__)
25
+ sql_directory = File.expand_path("../../../sql", __dir__)
26
26
  source_path = File.join(sql_directory, "#{filename}.sql")
27
27
  File.read(source_path).strip
28
28
  end
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'pg_search/migration/generator'
3
+ require "pg_search/migration/generator"
4
4
 
5
5
  module PgSearch
6
6
  module Migration
7
7
  class MultisearchGenerator < Generator
8
8
  def migration_name
9
- 'create_pg_search_documents'
9
+ "create_pg_search_documents"
10
10
  end
11
11
  end
12
12
  end
@@ -7,12 +7,12 @@ module PgSearch
7
7
  module ClassMethods
8
8
  def pg_search_scope(name, options)
9
9
  options_proc = if options.respond_to?(:call)
10
- options
11
- elsif options.respond_to?(:merge)
12
- ->(query) { { query: query }.merge(options) }
13
- else
14
- raise ArgumentError, 'pg_search_scope expects a Hash or Proc'
15
- end
10
+ options
11
+ elsif options.respond_to?(:merge)
12
+ ->(query) { {query: query}.merge(options) }
13
+ else
14
+ raise ArgumentError, "pg_search_scope expects a Hash or Proc"
15
+ end
16
16
 
17
17
  define_singleton_method(name) do |*args|
18
18
  config = Configuration.new(options_proc.call(*args), self)
@@ -23,13 +23,14 @@ module PgSearch
23
23
 
24
24
  def multisearchable(options = {})
25
25
  include PgSearch::Multisearchable
26
+
26
27
  class_attribute :pg_search_multisearchable_options
27
28
  self.pg_search_multisearchable_options = options
28
29
  end
29
30
  end
30
31
 
31
- def method_missing(symbol, *args)
32
- case symbol
32
+ def method_missing(method, *, **)
33
+ case method
33
34
  when :pg_search_rank
34
35
  raise PgSearchRankNotSelected unless respond_to?(:pg_search_rank)
35
36
 
@@ -43,12 +44,9 @@ module PgSearch
43
44
  end
44
45
  end
45
46
 
46
- def respond_to_missing?(symbol, *args)
47
- case symbol
48
- when :pg_search_rank
49
- attributes.key?(:pg_search_rank)
50
- when :pg_search_highlight
51
- attributes.key?(:pg_search_highlight)
47
+ def respond_to_missing?(method, *, **)
48
+ if method.in?(%i[pg_search_rank pg_search_highlight])
49
+ attributes.key?(method)
52
50
  else
53
51
  super
54
52
  end
@@ -42,7 +42,7 @@ module PgSearch
42
42
  end
43
43
 
44
44
  def primary_key
45
- model.primary_key
45
+ connection.quote_column_name(model.primary_key)
46
46
  end
47
47
 
48
48
  def rebuild_sql_template
@@ -68,9 +68,10 @@ module PgSearch
68
68
  def sti_clause
69
69
  clause = ""
70
70
  if model.column_names.include? model.inheritance_column
71
+ quoted_inheritance_column = connection.quote_column_name(model.inheritance_column)
71
72
  clause = "WHERE"
72
- clause = "#{clause} #{model.inheritance_column} IS NULL OR" if model.base_class == model
73
- clause = "#{clause} #{model.inheritance_column} = #{model_name}"
73
+ clause = "#{clause} #{quoted_inheritance_column} IS NULL OR" if model.base_class == model
74
+ clause = "#{clause} #{quoted_inheritance_column} = #{model_name}"
74
75
  end
75
76
  clause
76
77
  end
@@ -7,9 +7,11 @@ module PgSearch
7
7
  class << self
8
8
  def rebuild(model, deprecated_clean_up = nil, clean_up: true, transactional: true)
9
9
  unless deprecated_clean_up.nil?
10
- ActiveSupport::Deprecation.warn(
10
+ warn(
11
11
  "pg_search 3.0 will no longer accept a boolean second argument to PgSearchMultisearch.rebuild, " \
12
- "use keyword argument `clean_up:` instead."
12
+ "use keyword argument `clean_up:` instead.",
13
+ category: :deprecated,
14
+ uplevel: 1
13
15
  )
14
16
  clean_up = deprecated_clean_up
15
17
  end
@@ -7,12 +7,12 @@ module PgSearch
7
7
  def self.included(mod)
8
8
  mod.class_eval do
9
9
  has_one :pg_search_document,
10
- as: :searchable,
11
- class_name: "PgSearch::Document",
12
- dependent: :delete
10
+ as: :searchable,
11
+ class_name: "PgSearch::Document",
12
+ dependent: :delete
13
13
 
14
14
  after_save :update_pg_search_document,
15
- if: -> { PgSearch.multisearch_enabled? }
15
+ if: -> { PgSearch.multisearch_enabled? }
16
16
  end
17
17
  end
18
18
 
@@ -39,7 +39,7 @@ module PgSearch
39
39
  conditions.all? { |condition| condition.to_proc.call(self) }
40
40
  end
41
41
 
42
- def update_pg_search_document # rubocop:disable Metrics/AbcSize
42
+ def update_pg_search_document
43
43
  if_conditions = Array(pg_search_multisearchable_options[:if])
44
44
  unless_conditions = Array(pg_search_multisearchable_options[:unless])
45
45
 
@@ -50,7 +50,7 @@ module PgSearch
50
50
  if should_have_document
51
51
  create_or_update_pg_search_document
52
52
  else
53
- pg_search_document&.destroy
53
+ pg_search_document&.destroy # standard:disable Rails/SaveBang
54
54
  end
55
55
  end
56
56
 
@@ -58,7 +58,7 @@ module PgSearch
58
58
  if !pg_search_document
59
59
  create_pg_search_document(pg_search_document_attrs)
60
60
  elsif should_update_pg_search_document?
61
- pg_search_document.update(pg_search_document_attrs)
61
+ pg_search_document.update(pg_search_document_attrs) # standard:disable Rails/SaveBang
62
62
  end
63
63
  end
64
64
  end
@@ -6,19 +6,29 @@ module PgSearch
6
6
  @config = config
7
7
  end
8
8
 
9
+ DISALLOWED_CHARACTERS = "'[''?\\:]'"
10
+
9
11
  def add_normalization(sql_expression)
10
12
  return sql_expression unless config.ignore.include?(:accents)
11
13
 
12
14
  sql_node = case sql_expression
13
- when Arel::Nodes::Node
14
- sql_expression
15
- else
16
- Arel.sql(sql_expression)
17
- end
15
+ when Arel::Nodes::Node
16
+ sql_expression
17
+ else
18
+ Arel.sql(sql_expression)
19
+ end
18
20
 
19
21
  Arel::Nodes::NamedFunction.new(
20
- PgSearch.unaccent_function,
21
- [sql_node]
22
+ "regexp_replace",
23
+ [
24
+ Arel::Nodes::NamedFunction.new(
25
+ PgSearch.unaccent_function,
26
+ [sql_node]
27
+ ),
28
+ Arel.sql(DISALLOWED_CHARACTERS),
29
+ Arel.sql("''"),
30
+ Arel.sql("'g'")
31
+ ]
22
32
  ).to_sql
23
33
  end
24
34
 
@@ -27,6 +27,7 @@ module PgSearch
27
27
  def self.[](tsearch)
28
28
  Module.new do
29
29
  include WithPgSearchHighlight
30
+
30
31
  define_method(:tsearch) { tsearch }
31
32
  end
32
33
  end
@@ -90,12 +91,33 @@ module PgSearch
90
91
  end
91
92
 
92
93
  def conditions
93
- config.features
94
- .reject { |_feature_name, feature_options| feature_options && feature_options[:sort_only] }
95
- .map { |feature_name, _feature_options| feature_for(feature_name).conditions }
96
- .inject { |accumulator, expression| Arel::Nodes::Or.new(accumulator, expression) }
94
+ expressions =
95
+ config.features
96
+ .reject { |_feature_name, feature_options| feature_options && feature_options[:sort_only] }
97
+ .map { |feature_name, _feature_options| feature_for(feature_name).conditions }
98
+
99
+ or_node(expressions)
97
100
  end
98
101
 
102
+ # https://github.com/rails/rails/pull/51492
103
+ # :nocov:
104
+ # standard:disable Lint/DuplicateMethods
105
+ or_arity = Arel::Nodes::Or.instance_method(:initialize).arity
106
+ case or_arity
107
+ when 1
108
+ def or_node(expressions)
109
+ Arel::Nodes::Or.new(expressions)
110
+ end
111
+ when 2
112
+ def or_node(expressions)
113
+ expressions.inject { |accumulator, expression| Arel::Nodes::Or.new(accumulator, expression) }
114
+ end
115
+ else
116
+ raise "Unsupported arity #{or_arity} for Arel::Nodes::Or#initialize"
117
+ end
118
+ # :nocov:
119
+ # standard:enable Lint/DuplicateMethods
120
+
99
121
  def order_within_rank
100
122
  config.order_within_rank || "#{primary_key} ASC"
101
123
  end
@@ -108,11 +130,11 @@ module PgSearch
108
130
  if config.associations.any?
109
131
  config.associations.map do |association|
110
132
  association.join(primary_key)
111
- end.join(' ')
133
+ end.join(" ")
112
134
  end
113
135
  end
114
136
 
115
- FEATURE_CLASSES = {
137
+ FEATURE_CLASSES = { # standard:disable Lint/UselessConstantScoping
116
138
  dmetaphone: Features::DMetaphone,
117
139
  tsearch: Features::TSearch,
118
140
  trigram: Features::Trigram
@@ -142,11 +164,17 @@ module PgSearch
142
164
  end
143
165
 
144
166
  def rank_join(rank_table_alias)
145
- "INNER JOIN (#{subquery.to_sql}) AS #{rank_table_alias} ON #{primary_key} = #{rank_table_alias}.pg_search_id"
167
+ arel_table = model.arel_table
168
+ subquery_arel = subquery.arel.as(rank_table_alias)
169
+
170
+ arel_table
171
+ .join(subquery_arel)
172
+ .on(arel_table[model.primary_key].eq(subquery_arel[:pg_search_id]))
173
+ .join_sources
146
174
  end
147
175
 
148
176
  def include_table_aliasing_for_rank(scope)
149
- return scope if scope.included_modules.include?(PgSearchRankTableAliasing)
177
+ return scope if scope.include?(PgSearchRankTableAliasing)
150
178
 
151
179
  scope.all.spawn.tap do |new_scope|
152
180
  new_scope.instance_eval { extend PgSearchRankTableAliasing }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rake'
4
- require 'pg_search'
3
+ require "rake"
4
+ require "pg_search"
5
5
 
6
6
  namespace :pg_search do
7
7
  namespace :multisearch do
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgSearch
4
- VERSION = '2.3.6'
4
+ VERSION = "2.3.8"
5
5
  end
data/lib/pg_search.rb CHANGED
@@ -18,7 +18,7 @@ module PgSearch
18
18
  autoload :Document, "pg_search/document"
19
19
 
20
20
  def self.included(base)
21
- ActiveSupport::Deprecation.warn <<~MESSAGE
21
+ warn(<<~MESSAGE, category: :deprecated, uplevel: 1)
22
22
  Directly including `PgSearch` into an Active Record model is deprecated and will be removed in pg_search 3.0.
23
23
 
24
24
  Please replace `include PgSearch` with `include PgSearch::Model`.
@@ -34,8 +34,8 @@ module PgSearch
34
34
  self.unaccent_function = "unaccent"
35
35
 
36
36
  class << self
37
- def multisearch(*args)
38
- PgSearch::Document.search(*args)
37
+ def multisearch(...)
38
+ PgSearch::Document.search(...)
39
39
  end
40
40
 
41
41
  def disable_multisearch
data/pg_search.gemspec CHANGED
@@ -1,40 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- $LOAD_PATH.push File.expand_path('lib', __dir__)
4
- require 'pg_search/version'
3
+ $LOAD_PATH.push File.expand_path("lib", __dir__)
4
+ require "pg_search/version"
5
5
 
6
- Gem::Specification.new do |s| # rubocop:disable Metrics/BlockLength
7
- s.name = 'pg_search'
8
- s.version = PgSearch::VERSION
9
- s.platform = Gem::Platform::RUBY
10
- s.authors = ['Grant Hutchins', 'Case Commons, LLC']
11
- s.email = %w[gems@nertzy.com casecommons-dev@googlegroups.com]
12
- s.homepage = 'https://github.com/Casecommons/pg_search'
13
- s.summary = "PgSearch builds Active Record named scopes that take advantage of PostgreSQL's full text search"
6
+ Gem::Specification.new do |s|
7
+ s.name = "pg_search"
8
+ s.version = PgSearch::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Grant Hutchins", "Case Commons, LLC"]
11
+ s.email = %w[gems@nertzy.com casecommons-dev@googlegroups.com]
12
+ s.homepage = "https://github.com/Casecommons/pg_search"
13
+ s.summary = "PgSearch builds Active Record named scopes that take advantage of PostgreSQL's full text search"
14
14
  s.description = "PgSearch builds Active Record named scopes that take advantage of PostgreSQL's full text search"
15
- s.licenses = ['MIT']
15
+ s.licenses = ["MIT"]
16
16
  s.metadata["rubygems_mfa_required"] = "true"
17
17
 
18
- s.files = `git ls-files`.split("\n")
19
- s.test_files = `git ls-files -- spec/*`.split("\n")
20
- s.require_paths = ['lib']
18
+ s.files = `git ls-files -z`.split("\x0")
19
+ s.require_paths = ["lib"]
21
20
 
22
- s.add_dependency 'activerecord', '>= 5.2'
23
- s.add_dependency 'activesupport', '>= 5.2'
21
+ s.add_dependency "activerecord", ">= 7.2"
22
+ s.add_dependency "activesupport", ">= 7.2"
24
23
 
25
- s.add_development_dependency 'pry'
26
- s.add_development_dependency 'rake'
27
- s.add_development_dependency 'rspec'
28
- s.add_development_dependency 'rubocop'
29
- s.add_development_dependency 'rubocop-performance'
30
- s.add_development_dependency 'rubocop-rails'
31
- s.add_development_dependency 'rubocop-rake'
32
- s.add_development_dependency 'rubocop-rspec'
33
- s.add_development_dependency 'simplecov'
34
- s.add_development_dependency 'simplecov-lcov'
35
- s.add_development_dependency 'undercover'
36
- s.add_development_dependency 'warning'
37
- s.add_development_dependency 'with_model'
38
-
39
- s.required_ruby_version = '>= 2.6'
24
+ s.required_ruby_version = ">= 3.3"
40
25
  end