umbrellio-sequel-plugins 0.17.2 → 0.19.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a04b57476dd8a38bb6ba7a40a38603ce3dfa807f0a8072c76f4446081b48f97e
4
- data.tar.gz: 89fc7a911c67c32321cb0290b5a408effaf948717f0d60ceaa5437c8f7718006
3
+ metadata.gz: f247c8695f1d06e2ada379cf25e1b387871ff0249816099ff9dd9aaebec32fc8
4
+ data.tar.gz: 12cc3acf1bb7106fd70a8e4d89ba243e09c766316e2cdd60297e67f3bfafd887
5
5
  SHA512:
6
- metadata.gz: 4bdbfa1d179d94da6b41d8f1f17556ec1617d420f23c91a0d84d4cc1e3d56b5bb63500018f482ecb92ff459737e62f3692190a3932f7006c505de182deb6b0e2
7
- data.tar.gz: 4fd0e9eaa31182ed0df4c7c4c7afa32d231f2d89b59621399558fce7145553889cfc784ba02a13f4c1ff50a6453c002d5d1e5e24435f916b3bc425eae2293d77
6
+ metadata.gz: 90bf1d35960c9464c3a55e045e91952d0722fd189102e3d4f7b2890cc7c5942ed97f98c4c9000c24f4ed30f44cc2f7c05223ca8f43a6af30253baba48bc9ae28
7
+ data.tar.gz: a8f770ea411c1dc6688c0136d67106652f08c836f3fb2622a10200d02cae90345cb013ece33577010a41a08dcf55ca34152ec1522bf147956caf41787cdc981d
@@ -30,7 +30,7 @@ jobs:
30
30
  strategy:
31
31
  fail-fast: false
32
32
  matrix:
33
- ruby: ["3.0", "3.1", "3.2", "3.3"]
33
+ ruby: ["3.3", "3.4"]
34
34
 
35
35
  name: ${{ matrix.ruby }}
36
36
 
@@ -0,0 +1,54 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+
8
+ jobs:
9
+ release:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Extract version
17
+ id: version
18
+ run: |
19
+ version=$(grep -oP '(?<=version = ")[^"]+' umbrellio-sequel-plugins.gemspec)
20
+ echo "tag=v$version" >> "$GITHUB_OUTPUT"
21
+
22
+ - name: Check if release already exists
23
+ id: check
24
+ run: |
25
+ if gh release view "${{ steps.version.outputs.tag }}" &>/dev/null; then
26
+ echo "exists=true" >> "$GITHUB_OUTPUT"
27
+ else
28
+ echo "exists=false" >> "$GITHUB_OUTPUT"
29
+ fi
30
+ env:
31
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32
+
33
+ - uses: ruby/setup-ruby@v1
34
+ if: steps.check.outputs.exists == 'false'
35
+ with:
36
+ ruby-version: "3.4"
37
+ bundler-cache: true
38
+
39
+ - name: Build gem
40
+ if: steps.check.outputs.exists == 'false'
41
+ run: gem build umbrellio-sequel-plugins.gemspec
42
+
43
+ - name: Push gem to RubyGems
44
+ if: steps.check.outputs.exists == 'false'
45
+ run: gem push *.gem
46
+ env:
47
+ GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }}
48
+
49
+ - uses: softprops/action-gh-release@v3
50
+ if: steps.check.outputs.exists == 'false'
51
+ with:
52
+ tag_name: ${{ steps.version.outputs.tag }}
53
+ files: "*.gem"
54
+ generate_release_notes: true
data/.rubocop.yml CHANGED
@@ -3,7 +3,7 @@ inherit_gem:
3
3
 
4
4
  AllCops:
5
5
  DisplayCopNames: true
6
- TargetRubyVersion: 3.0
6
+ TargetRubyVersion: 3.3
7
7
 
8
8
  Naming/FileName:
9
9
  Exclude:
data/Gemfile CHANGED
@@ -6,6 +6,7 @@ gemspec
6
6
 
7
7
  gem "async"
8
8
  gem "money"
9
+ gem "opentelemetry-api"
9
10
  gem "pg"
10
11
  gem "pry"
11
12
  gem "rake"
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- umbrellio-sequel-plugins (0.17.2)
4
+ umbrellio-sequel-plugins (0.19.0)
5
+ concurrent-ruby
5
6
  sequel
6
7
 
7
8
  GEM
@@ -40,12 +41,15 @@ GEM
40
41
  concurrent-ruby (~> 1.0)
41
42
  json (2.7.2)
42
43
  language_server-protocol (3.17.0.3)
44
+ logger (1.7.0)
43
45
  method_source (1.1.0)
44
46
  minitest (5.25.1)
45
47
  money (6.19.0)
46
48
  i18n (>= 0.6.4, <= 2)
47
49
  mutex_m (0.2.0)
48
50
  nio4r (2.7.3)
51
+ opentelemetry-api (1.10.0)
52
+ logger
49
53
  parallel (1.26.3)
50
54
  parser (3.3.5.0)
51
55
  ast (~> 2.4.1)
@@ -139,6 +143,7 @@ PLATFORMS
139
143
  DEPENDENCIES
140
144
  async
141
145
  money
146
+ opentelemetry-api
142
147
  pg
143
148
  pry
144
149
  rake
@@ -18,13 +18,13 @@ module Clickhouse
18
18
  migrator(target: target.to_i).run
19
19
  end
20
20
 
21
- def migrator(**opts)
21
+ def migrator(**)
22
22
  Sequel::TimestampMigrator.new(
23
23
  DB,
24
24
  Rails.root.join("db/migrate/clickhouse"),
25
25
  table: :clickhouse_migrations,
26
26
  use_transactions: false,
27
- **opts,
27
+ **,
28
28
  )
29
29
  end
30
30
  end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent"
4
+
5
+ module Sequel
6
+ # https://github.com/jeremyevans/sequel/blob/master/lib/sequel/extensions/async_thread_pool.rb
7
+ module Database::ConcurrentThreadPool
8
+ # Base proxy: delegates all method calls to the resolved async value.
9
+ class BaseProxy < BasicObject
10
+ def method_missing(...)
11
+ __value.public_send(...)
12
+ end
13
+
14
+ def respond_to_missing?(*)
15
+ __value.respond_to?(*)
16
+ end
17
+
18
+ [:!, :==, :!=, :instance_eval, :instance_exec].each do |method|
19
+ define_method(method) do |*args, &block|
20
+ __value.public_send(method, *args, &block)
21
+ end
22
+ end
23
+ end
24
+
25
+ # Default proxy: schedules block via Concurrent::Future, blocks on first access.
26
+ class Proxy < BaseProxy
27
+ def initialize(executor, &)
28
+ super()
29
+
30
+ @future = Concurrent::Promises.future_on(executor, &)
31
+ end
32
+
33
+ def __value
34
+ @future.value!
35
+ end
36
+ end
37
+
38
+ # Preemptable proxy: calling thread runs the block if the pool hasn't started it yet.
39
+ class PreemptableProxy < BaseProxy
40
+ def initialize(executor, &block)
41
+ super()
42
+
43
+ @mutex = Mutex.new
44
+ @block = block
45
+ @done = false
46
+ @result = nil
47
+ @error = nil
48
+
49
+ executor.post { __run }
50
+ end
51
+
52
+ def __value
53
+ error, result = @mutex.synchronize do
54
+ __execute unless @done
55
+ [@error, @result]
56
+ end
57
+ ::Kernel.raise error if error
58
+ result
59
+ end
60
+
61
+ private
62
+
63
+ def __run
64
+ @mutex.synchronize { __execute unless @done }
65
+ end
66
+
67
+ def __execute
68
+ @result = @block.call
69
+ rescue StandardError => error
70
+ @error = error
71
+ ensure
72
+ @done = true
73
+ end
74
+ end
75
+
76
+ module DatabaseMethods
77
+ class << self
78
+ def make_finalizer(executor) = proc { executor.shutdown }
79
+
80
+ def extended(db)
81
+ db.instance_exec do
82
+ case pool.pool_type
83
+ when :single, :sharded_single
84
+ raise Error, "cannot load concurrent_thread_pool extension " \
85
+ "if using single or sharded_single connection pool"
86
+ end
87
+
88
+ executor, owned = choose_executor(opts)
89
+ proxy_klass =
90
+ typecast_value_boolean(opts[:preempt_async_thread]) ? PreemptableProxy : Proxy
91
+
92
+ define_singleton_method(:async_job_class) { proxy_klass }
93
+ define_singleton_method(:async_thread_executor) { executor }
94
+
95
+ finalizer =
96
+ Sequel::Database::ConcurrentThreadPool::DatabaseMethods.make_finalizer(executor)
97
+ ObjectSpace.define_finalizer(db, finalizer) if owned
98
+
99
+ extend_datasets(DatasetMethods)
100
+ end
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def choose_executor(opts)
107
+ if opts[:async_thread_executor]
108
+ [opts[:async_thread_executor], false]
109
+ else
110
+ num = opts[:num_async_threads] ? typecast_value_integer(opts[:num_async_threads]) :
111
+ Integer(opts[:max_connections] || 4)
112
+ raise Error, "must have positive number for num_async_threads" if num <= 0
113
+ [Concurrent::ThreadPoolExecutor.new(
114
+ min_threads: num,
115
+ max_threads: num,
116
+ max_queue: 0,
117
+ fallback_policy: :abort,
118
+ ), true]
119
+ end
120
+ end
121
+
122
+ def async_run(&)
123
+ otel_context = OpenTelemetry::Context.current if defined?(OpenTelemetry::Context)
124
+
125
+ if otel_context && !otel_context.equal?(OpenTelemetry::Context::ROOT)
126
+ async_job_class.new(async_thread_executor) do
127
+ OpenTelemetry::Context.with_current(otel_context, &)
128
+ end
129
+ else
130
+ async_job_class.new(async_thread_executor, &)
131
+ end
132
+ end
133
+ end
134
+
135
+ ASYNC_METHODS = ((
136
+ [:all?, :any?, :drop, :entries, :grep_v, :include?, :inject, :member?, :minmax,
137
+ :none?, :one?, :reduce, :sort, :take, :tally, :to_a, :to_h, :uniq, :zip] &
138
+ Enumerable.instance_methods
139
+ ) + (Dataset::ACTION_METHODS - [:map, :paged_each])).freeze
140
+
141
+ ASYNC_BLOCK_METHODS = ((
142
+ [:collect, :collect_concat, :detect, :drop_while, :each_cons, :each_entry, :each_slice,
143
+ :each_with_index, :each_with_object, :filter_map, :find, :find_all, :find_index,
144
+ :flat_map, :max_by, :min_by, :minmax_by, :partition, :reject, :reverse_each,
145
+ :sort_by, :take_while] & Enumerable.instance_methods
146
+ ) + [:paged_each]).freeze
147
+
148
+ ASYNC_ARGS_OR_BLOCK_METHODS = [:map].freeze
149
+
150
+ module DatasetMethods
151
+ def self.define_async_method(mod, method)
152
+ mod.send(:define_method, method) do |*args, &block|
153
+ if @opts[:async]
154
+ ds = sync
155
+ db.send(:async_run) { ds.send(method, *args, &block) }
156
+ else
157
+ super(*args, &block)
158
+ end
159
+ end
160
+ end
161
+
162
+ def self.define_async_block_method(mod, method)
163
+ mod.send(:define_method, method) do |*args, &block|
164
+ if block && @opts[:async]
165
+ ds = sync
166
+ db.send(:async_run) { ds.send(method, *args, &block) }
167
+ else
168
+ super(*args, &block)
169
+ end
170
+ end
171
+ end
172
+
173
+ def self.define_async_args_or_block_method(mod, method)
174
+ mod.send(:define_method, method) do |*args, &block|
175
+ if (block || !args.empty?) && @opts[:async]
176
+ ds = sync
177
+ db.send(:async_run) { ds.send(method, *args, &block) }
178
+ else
179
+ super(*args, &block)
180
+ end
181
+ end
182
+ end
183
+
184
+ ASYNC_METHODS.each { |m| define_async_method(self, m) }
185
+ ASYNC_BLOCK_METHODS.each { |m| define_async_block_method(self, m) }
186
+ ASYNC_ARGS_OR_BLOCK_METHODS.each { |m| define_async_args_or_block_method(self, m) }
187
+
188
+ def async
189
+ cached_dataset(:_async) { clone(async: true) }
190
+ end
191
+
192
+ def sync
193
+ cached_dataset(:_sync) { clone(async: false) }
194
+ end
195
+ end
196
+ end
197
+
198
+ Database.register_extension(
199
+ :concurrent_thread_pool, Database::ConcurrentThreadPool::DatabaseMethods
200
+ )
201
+ end
@@ -11,9 +11,9 @@ module SimpleMigrationExtension
11
11
  end
12
12
 
13
13
  module MigratorExtension
14
- def checked_transaction(migration, &block)
14
+ def checked_transaction(migration, &)
15
15
  if _use_transaction?(migration)
16
- _transaction(migration, &block)
16
+ _transaction(migration, &)
17
17
  else
18
18
  yield
19
19
  end
@@ -38,11 +38,11 @@ module MigratorExtension
38
38
  end
39
39
  end
40
40
 
41
- def _transaction(migration, &block)
41
+ def _transaction(migration, &)
42
42
  if migration.transaction_opts.nil?
43
- db.transaction(&block)
43
+ db.transaction(&)
44
44
  else
45
- db.transaction(migration.transaction_opts, &block)
45
+ db.transaction(migration.transaction_opts, &)
46
46
  end
47
47
  end
48
48
  end
@@ -24,9 +24,9 @@ module Sequel
24
24
  def synchronize_with(*args, timeout: 10, savepoint: false, skip_if_locked: false)
25
25
  key = lock_key_for(args)
26
26
 
27
- transaction(savepoint: savepoint) do
27
+ transaction(savepoint:) do
28
28
  hash = key_hash(key)
29
- if get_lock(key, hash, timeout: timeout, skip_if_locked: skip_if_locked)
29
+ if get_lock(key, hash, timeout:, skip_if_locked:)
30
30
  log_info("locked with #{key} (#{hash})")
31
31
  yield
32
32
  end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # Concurrent eager loading using concurrent_thread_pool extension.
6
+ # Adapted from Sequel's built-in concurrent_eager_loading plugin but uses
7
+ # our concurrent_thread_pool extension instead of async_thread_pool.
8
+ #
9
+ # Usage:
10
+ #
11
+ # DB.extension(:concurrent_thread_pool)
12
+ # Album.plugin :concurrent_thread_pool_eager_loading
13
+ # Album.eager_load_concurrently.eager(:artist, :genre, :tracks).all
14
+ #
15
+ # # Always concurrent by default:
16
+ # Album.plugin :concurrent_thread_pool_eager_loading, always: true
17
+ module ConcurrentThreadPoolEagerLoading
18
+ def self.configure(mod, opts = OPTS)
19
+ if opts.key?(:always)
20
+ mod.instance_variable_set(:@always_eager_load_concurrently, opts[:always])
21
+ end
22
+ end
23
+
24
+ module ClassMethods
25
+ Plugins.inherited_instance_variables(self, :@always_eager_load_concurrently => nil)
26
+ Plugins.def_dataset_methods(self, [:eager_load_concurrently, :eager_load_serially])
27
+
28
+ def always_eager_load_concurrently?
29
+ @always_eager_load_concurrently
30
+ end
31
+ end
32
+
33
+ module DatasetMethods
34
+ def eager_load_concurrently
35
+ cached_dataset(:_eager_load_concurrently) do
36
+ clone(eager_load_concurrently: true)
37
+ end
38
+ end
39
+
40
+ def eager_load_serially
41
+ cached_dataset(:_eager_load_serially) do
42
+ clone(eager_load_concurrently: false)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def eager_load_concurrently?
49
+ v = @opts[:eager_load_concurrently]
50
+ v.nil? ? model.always_eager_load_concurrently? : v
51
+ end
52
+
53
+ def perform_eager_loads(eager_load_data)
54
+ return super if !eager_load_concurrently? || eager_load_data.length < 2
55
+
56
+ mutex = Mutex.new
57
+ eager_load_data.each_value do |elo|
58
+ elo[:mutex] = mutex
59
+ end
60
+
61
+ super.each do |v|
62
+ if Sequel::Database::ConcurrentThreadPool::BaseProxy === v # rubocop:disable Style/CaseEquality
63
+ v.__value
64
+ end
65
+ end
66
+ end
67
+
68
+ def perform_eager_load(loader, elo)
69
+ elo[:mutex] ? db.send(:async_run) { super } : super
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -16,7 +16,7 @@ module Sequel::Plugins::Upsert
16
16
  where_spec = cols.map { |x| Sequel::Plugins::Upsert.distinct_expr(table_name, x) }.reduce(:|)
17
17
 
18
18
  dataset.insert_conflict(
19
- target: target,
19
+ target:,
20
20
  update: update_spec,
21
21
  update_where: where_spec,
22
22
  )
@@ -25,21 +25,21 @@ module Sequel::Plugins::Upsert
25
25
  # Executes the upsert request
26
26
  #
27
27
  # @param row [Hash] values
28
- # @param options [Hash] options
28
+ # @param target [Symbol] target column
29
29
  #
30
30
  # @example
31
- # User.upsert(name: "John", email: "jd@test.com", target: :email)
31
+ # User.upsert({name: "John", email: "jd@test.com"}, target: :email)
32
32
  # @return [Sequel::Model]
33
- def upsert(row, **options)
34
- upsert_dataset(**options).insert(sequel_values(row))
33
+ def upsert(row, **)
34
+ upsert_dataset(**).insert(sequel_values(row))
35
35
  end
36
36
 
37
37
  # Executes the upsert request for multiple rows
38
38
  # @see #upsert
39
39
  # @see #upsert_dataset
40
- def multi_upsert(rows, **options)
40
+ def multi_upsert(rows, **)
41
41
  rows = rows.map { |row| sequel_values(row) }
42
- upsert_dataset(**options).multi_insert(rows)
42
+ upsert_dataset(**).multi_insert(rows)
43
43
  end
44
44
 
45
45
  # Returns formatted row values
@@ -10,7 +10,7 @@ module Sequel::Plugins::WithLock
10
10
  @__locked = true
11
11
 
12
12
  begin
13
- db.transaction(savepoint: savepoint) do
13
+ db.transaction(savepoint:) do
14
14
  lock!(mode)
15
15
  yield
16
16
  end
@@ -15,7 +15,7 @@ namespace :sequel do
15
15
 
16
16
  migrations = Rails.root.glob(migrations_path).map do |file|
17
17
  filename = file.basename.to_s
18
- { version: filename.to_i, filename: filename, source: file.read }
18
+ { version: filename.to_i, filename:, source: file.read }
19
19
  end
20
20
 
21
21
  conflict_options = {
@@ -21,7 +21,7 @@ namespace :sequel do
21
21
 
22
22
  migrator_args = {
23
23
  table: args[:migration_table],
24
- use_transactions: use_transactions,
24
+ use_transactions:,
25
25
  allow_missing_migration_files: false,
26
26
  }.compact
27
27
  migrator = Sequel::TimestampMigrator.new(DB, tmpdir, migrator_args)
@@ -23,7 +23,7 @@ namespace :sequel do
23
23
  path = Rails.root.join("db/migrate")
24
24
  migrator_args = {
25
25
  table: args[:table],
26
- use_transactions: use_transactions,
26
+ use_transactions:,
27
27
  allow_missing_migration_files: false,
28
28
  }.compact
29
29
  migrator = Sequel::TimestampMigrator.new(DB, path, migrator_args)
@@ -5,8 +5,8 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "umbrellio-sequel-plugins"
8
- spec.version = "0.17.2"
9
- spec.required_ruby_version = ">= 3.0"
8
+ spec.version = "0.19.0"
9
+ spec.required_ruby_version = ">= 3.3"
10
10
 
11
11
  spec.authors = ["Team Umbrellio"]
12
12
  spec.email = ["oss@umbrellio.biz"]
@@ -18,5 +18,6 @@ Gem::Specification.new do |spec|
18
18
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
19
  spec.require_paths = ["lib"]
20
20
 
21
+ spec.add_runtime_dependency "concurrent-ruby"
21
22
  spec.add_runtime_dependency "sequel"
22
23
  end
metadata CHANGED
@@ -1,15 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: umbrellio-sequel-plugins
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.2
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Team Umbrellio
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-03-05 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: concurrent-ruby
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'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: sequel
15
28
  requirement: !ruby/object:Gem::Requirement
@@ -32,6 +45,7 @@ extensions: []
32
45
  extra_rdoc_files: []
33
46
  files:
34
47
  - ".github/workflows/ci.yml"
48
+ - ".github/workflows/release.yml"
35
49
  - ".gitignore"
36
50
  - ".rspec"
37
51
  - ".rubocop.yml"
@@ -43,6 +57,7 @@ files:
43
57
  - bin/console
44
58
  - bin/setup
45
59
  - lib/clickhouse/migrator.rb
60
+ - lib/sequel/extensions/concurrent_thread_pool.rb
46
61
  - lib/sequel/extensions/currency_rates.rb
47
62
  - lib/sequel/extensions/deferrable_foreign_keys.rb
48
63
  - lib/sequel/extensions/fibered_connection_pool.rb
@@ -54,6 +69,7 @@ files:
54
69
  - lib/sequel/extensions/synchronize.rb
55
70
  - lib/sequel/plugins/attr_encrypted.rb
56
71
  - lib/sequel/plugins/attr_encrypted/simple_crypt.rb
72
+ - lib/sequel/plugins/concurrent_thread_pool_eager_loading.rb
57
73
  - lib/sequel/plugins/duplicate.rb
58
74
  - lib/sequel/plugins/get_column_value.rb
59
75
  - lib/sequel/plugins/money_accessors.rb
@@ -77,7 +93,6 @@ homepage: https://github.com/umbrellio/umbrellio-sequel-plugins
77
93
  licenses:
78
94
  - MIT
79
95
  metadata: {}
80
- post_install_message:
81
96
  rdoc_options: []
82
97
  require_paths:
83
98
  - lib
@@ -85,15 +100,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
85
100
  requirements:
86
101
  - - ">="
87
102
  - !ruby/object:Gem::Version
88
- version: '3.0'
103
+ version: '3.3'
89
104
  required_rubygems_version: !ruby/object:Gem::Requirement
90
105
  requirements:
91
106
  - - ">="
92
107
  - !ruby/object:Gem::Version
93
108
  version: '0'
94
109
  requirements: []
95
- rubygems_version: 3.5.22
96
- signing_key:
110
+ rubygems_version: 3.6.9
97
111
  specification_version: 4
98
112
  summary: Sequel plugins
99
113
  test_files: []