scenic 1.8.0 → 1.9.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +13 -8
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +48 -19
  5. data/FUNDING.yml +1 -0
  6. data/Gemfile +2 -2
  7. data/README.md +71 -18
  8. data/lib/generators/scenic/materializable.rb +27 -1
  9. data/lib/generators/scenic/view/templates/db/migrate/update_view.erb +1 -1
  10. data/lib/scenic/adapters/postgres/index_creation.rb +68 -0
  11. data/lib/scenic/adapters/postgres/index_migration.rb +70 -0
  12. data/lib/scenic/adapters/postgres/index_reapplication.rb +3 -28
  13. data/lib/scenic/adapters/postgres/side_by_side.rb +50 -0
  14. data/lib/scenic/adapters/postgres/temporary_name.rb +34 -0
  15. data/lib/scenic/adapters/postgres/views.rb +83 -10
  16. data/lib/scenic/adapters/postgres.rb +41 -18
  17. data/lib/scenic/schema_dumper.rb +0 -14
  18. data/lib/scenic/statements.rb +46 -13
  19. data/lib/scenic/version.rb +1 -1
  20. data/scenic.gemspec +5 -1
  21. data/spec/acceptance/user_manages_views_spec.rb +11 -0
  22. data/spec/dummy/config/application.rb +4 -0
  23. data/spec/generators/scenic/view/view_generator_spec.rb +26 -0
  24. data/spec/scenic/adapters/postgres/index_creation_spec.rb +54 -0
  25. data/spec/scenic/adapters/postgres/index_migration_spec.rb +24 -0
  26. data/spec/scenic/adapters/postgres/side_by_side_spec.rb +24 -0
  27. data/spec/scenic/adapters/postgres/temporary_name_spec.rb +23 -0
  28. data/spec/scenic/adapters/postgres_spec.rb +44 -3
  29. data/spec/scenic/command_recorder_spec.rb +18 -0
  30. data/spec/scenic/schema_dumper_spec.rb +29 -8
  31. data/spec/scenic/statements_spec.rb +62 -4
  32. data/spec/spec_helper.rb +19 -4
  33. data/spec/support/database_schema_helpers.rb +28 -0
  34. metadata +19 -11
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ module Scenic
4
+ module Adapters
5
+ describe Postgres::IndexMigration, :db, :silence do
6
+ it "moves indexes from the old view to the new view" do
7
+ create_materialized_view("hi", "SELECT 'hi' AS greeting")
8
+ create_materialized_view("hi_temp", "SELECT 'hi' AS greeting")
9
+ add_index(:hi, :greeting, name: "hi_greeting_idx")
10
+
11
+ Postgres::IndexMigration
12
+ .new(connection: ActiveRecord::Base.connection)
13
+ .migrate(from: "hi", to: "hi_temp")
14
+ indexes_for_original = indexes_for("hi")
15
+ indexes_for_temporary = indexes_for("hi_temp")
16
+
17
+ expect(indexes_for_original.length).to eq 1
18
+ expect(indexes_for_original.first.index_name).not_to eq "hi_greeting_idx"
19
+ expect(indexes_for_temporary.length).to eq 1
20
+ expect(indexes_for_temporary.first.index_name).to eq "hi_greeting_idx"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ module Scenic
4
+ module Adapters
5
+ describe Postgres::SideBySide, :db, :silence do
6
+ it "updates the materialized view to the new version" do
7
+ adapter = Postgres.new
8
+ create_materialized_view("hi", "SELECT 'hi' AS greeting")
9
+ add_index(:hi, :greeting, name: "hi_greeting_idx")
10
+ new_definition = "SELECT 'hola' AS greeting"
11
+
12
+ Postgres::SideBySide
13
+ .new(adapter: adapter, name: "hi", definition: new_definition)
14
+ .update
15
+ result = ar_connection.execute("SELECT * FROM hi").first["greeting"]
16
+ indexes = indexes_for("hi")
17
+
18
+ expect(result).to eq "hola"
19
+ expect(indexes.length).to eq 1
20
+ expect(indexes.first.index_name).to eq "hi_greeting_idx"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ module Scenic
4
+ module Adapters
5
+ describe Postgres::TemporaryName do
6
+ it "generates a temporary name based on a SHA1 hash of the original" do
7
+ name = "my_materialized_view"
8
+
9
+ temporary_name = Postgres::TemporaryName.new(name).to_s
10
+
11
+ expect(temporary_name).to match(/_scenic_sbs_[0-9a-f]{40}/)
12
+ end
13
+
14
+ it "does not overflow the 63 character limit for object names" do
15
+ name = "long_view_name_" * 10
16
+
17
+ temporary_name = Postgres::TemporaryName.new(name).to_s
18
+
19
+ expect(temporary_name.length).to eq 52
20
+ end
21
+ end
22
+ end
23
+ end
@@ -149,6 +149,14 @@ module Scenic
149
149
  adapter.refresh_materialized_view(:tests, concurrently: true)
150
150
  }.to raise_error e
151
151
  end
152
+
153
+ it "falls back to non-concurrent refresh if not populated" do
154
+ adapter = Postgres.new
155
+ adapter.create_materialized_view(:testing, "SELECT unnest('{1, 2}'::int[])", no_data: true)
156
+
157
+ expect { adapter.refresh_materialized_view(:testing, concurrently: true) }
158
+ .not_to raise_error
159
+ end
152
160
  end
153
161
  end
154
162
 
@@ -176,8 +184,8 @@ module Scenic
176
184
  SQL
177
185
 
178
186
  expect(adapter.views.map(&:name)).to eq [
179
- "parents",
180
187
  "children",
188
+ "parents",
181
189
  "people",
182
190
  "people_with_names"
183
191
  ]
@@ -193,13 +201,13 @@ module Scenic
193
201
 
194
202
  ActiveRecord::Base.connection.execute <<-SQL
195
203
  CREATE SCHEMA scenic;
196
- CREATE VIEW scenic.parents AS SELECT text 'Maarten' AS name;
204
+ CREATE VIEW scenic.more_parents AS SELECT text 'Maarten' AS name;
197
205
  SET search_path TO scenic, public;
198
206
  SQL
199
207
 
200
208
  expect(adapter.views.map(&:name)).to eq [
201
209
  "parents",
202
- "scenic.parents"
210
+ "scenic.more_parents"
203
211
  ]
204
212
  end
205
213
  end
@@ -250,6 +258,39 @@ module Scenic
250
258
  expect { adapter.populated?("greetings") }.to raise_error err
251
259
  end
252
260
  end
261
+
262
+ describe "#update_materialized_view" do
263
+ it "updates the definition of a materialized view in place" do
264
+ adapter = Postgres.new
265
+ create_materialized_view("hi", "SELECT 'hi' AS greeting")
266
+ new_definition = "SELECT 'hello' AS greeting"
267
+
268
+ adapter.update_materialized_view("hi", new_definition)
269
+ result = adapter.connection.execute("SELECT * FROM hi").first["greeting"]
270
+
271
+ expect(result).to eq "hello"
272
+ end
273
+
274
+ it "updates the definition of a materialized view side by side", :silence do
275
+ adapter = Postgres.new
276
+ create_materialized_view("hi", "SELECT 'hi' AS greeting")
277
+ new_definition = "SELECT 'hello' AS greeting"
278
+
279
+ adapter.update_materialized_view("hi", new_definition, side_by_side: true)
280
+ result = adapter.connection.execute("SELECT * FROM hi").first["greeting"]
281
+
282
+ expect(result).to eq "hello"
283
+ end
284
+
285
+ it "raises an exception if the version of PostgreSQL is too old" do
286
+ connection = double("Connection", supports_materialized_views?: false)
287
+ connectable = double("Connectable", connection: connection)
288
+ adapter = Postgres.new(connectable)
289
+
290
+ expect { adapter.create_materialized_view("greetings", "select 1") }
291
+ .to raise_error Postgres::MaterializedViewsNotSupportedError
292
+ end
293
+ end
253
294
  end
254
295
  end
255
296
  end
@@ -77,6 +77,24 @@ describe Scenic::CommandRecorder do
77
77
  expect { recorder.revert { recorder.update_view(*args) } }
78
78
  .to raise_error(ActiveRecord::IrreversibleMigration)
79
79
  end
80
+
81
+ it "reverts materialized views with no_data option appropriately" do
82
+ args = [:users, {version: 2, revert_to_version: 1, materialized: {no_data: true}}]
83
+ revert_args = [:users, {version: 1, materialized: {no_data: true}}]
84
+
85
+ recorder.revert { recorder.update_view(*args) }
86
+
87
+ expect(recorder.commands).to eq [[:update_view, revert_args]]
88
+ end
89
+
90
+ it "reverts materialized views with side_by_side option appropriately" do
91
+ args = [:users, {version: 2, revert_to_version: 1, materialized: {side_by_side: true}}]
92
+ revert_args = [:users, {version: 1, materialized: {side_by_side: true}}]
93
+
94
+ recorder.revert { recorder.update_view(*args) }
95
+
96
+ expect(recorder.commands).to eq [[:update_view, revert_args]]
97
+ end
80
98
  end
81
99
 
82
100
  describe "#replace_view" do
@@ -12,7 +12,7 @@ describe Scenic::SchemaDumper, :db do
12
12
  Search.connection.create_view :searches, sql_definition: view_definition
13
13
  stream = StringIO.new
14
14
 
15
- ActiveRecord::SchemaDumper.dump(Search.connection, stream)
15
+ dump_schema(stream)
16
16
 
17
17
  output = stream.string
18
18
 
@@ -31,7 +31,7 @@ describe Scenic::SchemaDumper, :db do
31
31
  Search.connection.create_view :searches, sql_definition: view_definition
32
32
  stream = StringIO.new
33
33
 
34
- ActiveRecord::SchemaDumper.dump(Search.connection, stream)
34
+ dump_schema(stream)
35
35
 
36
36
  output = stream.string
37
37
  expect(output).to include "~ '\\\\d+'::text"
@@ -47,7 +47,7 @@ describe Scenic::SchemaDumper, :db do
47
47
  Search.connection.create_view :searches, materialized: true, sql_definition: view_definition
48
48
  stream = StringIO.new
49
49
 
50
- ActiveRecord::SchemaDumper.dump(Search.connection, stream)
50
+ dump_schema(stream)
51
51
 
52
52
  output = stream.string
53
53
 
@@ -62,13 +62,29 @@ describe Scenic::SchemaDumper, :db do
62
62
  Search.connection.create_view :"scenic.searches", sql_definition: view_definition
63
63
  stream = StringIO.new
64
64
 
65
- ActiveRecord::SchemaDumper.dump(Search.connection, stream)
65
+ dump_schema(stream)
66
66
 
67
67
  output = stream.string
68
68
  expect(output).to include 'create_view "scenic.searches",'
69
69
 
70
70
  Search.connection.drop_view :"scenic.searches"
71
71
  end
72
+
73
+ it "sorts dependency order when views exist in a non-public schema" do
74
+ Search.connection.execute("CREATE SCHEMA IF NOT EXISTS scenic; SET search_path TO public, scenic")
75
+ Search.connection.execute("CREATE VIEW scenic.apples AS SELECT 1;")
76
+ Search.connection.execute("CREATE VIEW scenic.bananas AS SELECT 2;")
77
+ Search.connection.execute("CREATE OR REPLACE VIEW scenic.apples AS SELECT * FROM scenic.bananas;")
78
+ stream = StringIO.new
79
+
80
+ dump_schema(stream)
81
+ views = stream.string.lines.grep(/create_view/).map do |view_line|
82
+ view_line.match('create_view "(?<name>.*)"')[:name]
83
+ end
84
+ expect(views).to eq(%w[scenic.bananas scenic.apples])
85
+
86
+ Search.connection.execute("DROP SCHEMA IF EXISTS scenic CASCADE; SET search_path TO public")
87
+ end
72
88
  end
73
89
 
74
90
  it "handles active record table name prefixes and suffixes" do
@@ -77,7 +93,7 @@ describe Scenic::SchemaDumper, :db do
77
93
  Search.connection.create_view :a_searches_z, sql_definition: view_definition
78
94
  stream = StringIO.new
79
95
 
80
- ActiveRecord::SchemaDumper.dump(Search.connection, stream)
96
+ dump_schema(stream)
81
97
 
82
98
  output = stream.string
83
99
 
@@ -90,7 +106,7 @@ describe Scenic::SchemaDumper, :db do
90
106
  Search.connection.create_view :searches, sql_definition: view_definition
91
107
  stream = StringIO.new
92
108
 
93
- ActiveRecord::SchemaDumper.dump(Search.connection, stream)
109
+ dump_schema(stream)
94
110
 
95
111
  output = stream.string
96
112
 
@@ -105,7 +121,7 @@ describe Scenic::SchemaDumper, :db do
105
121
  Search.connection.create_view '"search in a haystack"', sql_definition: view_definition
106
122
  stream = StringIO.new
107
123
 
108
- ActiveRecord::SchemaDumper.dump(Search.connection, stream)
124
+ dump_schema(stream)
109
125
 
110
126
  output = stream.string
111
127
  expect(output).to include 'create_view "\"search in a haystack\"",'
@@ -129,7 +145,7 @@ describe Scenic::SchemaDumper, :db do
129
145
  sql_definition: view_definition
130
146
  stream = StringIO.new
131
147
 
132
- ActiveRecord::SchemaDumper.dump(Search.connection, stream)
148
+ dump_schema(stream)
133
149
 
134
150
  output = stream.string
135
151
  expect(output).to include 'create_view "scenic.\"search in a haystack\"",'
@@ -137,6 +153,11 @@ describe Scenic::SchemaDumper, :db do
137
153
 
138
154
  Search.connection.drop_view :'scenic."search in a haystack"'
139
155
 
156
+ case ActiveRecord.gem_version
157
+ when Gem::Requirement.new(">= 7.1")
158
+ Search.connection.drop_schema "scenic"
159
+ end
160
+
140
161
  silence_stream($stdout) { eval(output) } # standard:disable Security/Eval
141
162
 
142
163
  expect(SearchInAHaystack.take.haystack).to eq "needle"
@@ -125,7 +125,7 @@ module Scenic
125
125
  connection.update_view(:name, version: 3, materialized: true)
126
126
 
127
127
  expect(Scenic.database).to have_received(:update_materialized_view)
128
- .with(:name, definition.to_sql, no_data: false)
128
+ .with(:name, definition.to_sql, no_data: false, side_by_side: false)
129
129
  end
130
130
 
131
131
  it "updates the materialized view in the database with NO DATA" do
@@ -141,7 +141,23 @@ module Scenic
141
141
  )
142
142
 
143
143
  expect(Scenic.database).to have_received(:update_materialized_view)
144
- .with(:name, definition.to_sql, no_data: true)
144
+ .with(:name, definition.to_sql, no_data: true, side_by_side: false)
145
+ end
146
+
147
+ it "updates the materialized view with side-by-side mode" do
148
+ definition = instance_double("Definition", to_sql: "definition")
149
+ allow(Definition).to receive(:new)
150
+ .with(:name, 3)
151
+ .and_return(definition)
152
+
153
+ connection.update_view(
154
+ :name,
155
+ version: 3,
156
+ materialized: {side_by_side: true}
157
+ )
158
+
159
+ expect(Scenic.database).to have_received(:update_materialized_view)
160
+ .with(:name, definition.to_sql, no_data: false, side_by_side: true)
145
161
  end
146
162
 
147
163
  it "raises an error if not supplied a version or sql_defintion" do
@@ -160,6 +176,36 @@ module Scenic
160
176
  )
161
177
  end.to raise_error ArgumentError, /cannot both be set/
162
178
  end
179
+
180
+ it "raises an error is no_data and side_by_side are both set" do
181
+ definition = instance_double("Definition", to_sql: "definition")
182
+ allow(Definition).to receive(:new)
183
+ .with(:name, 3)
184
+ .and_return(definition)
185
+
186
+ expect do
187
+ connection.update_view(
188
+ :name,
189
+ version: 3,
190
+ materialized: {no_data: true, side_by_side: true}
191
+ )
192
+ end.to raise_error ArgumentError, /cannot be combined/
193
+ end
194
+
195
+ it "raises an error if not in a transaction" do
196
+ definition = instance_double("Definition", to_sql: "definition")
197
+ allow(Definition).to receive(:new)
198
+ .with(:name, 3)
199
+ .and_return(definition)
200
+
201
+ expect do
202
+ connection(transactions_enabled: false).update_view(
203
+ :name,
204
+ version: 3,
205
+ materialized: {side_by_side: true}
206
+ )
207
+ end.to raise_error RuntimeError, /transaction is required/
208
+ end
163
209
  end
164
210
 
165
211
  describe "replace_view" do
@@ -192,8 +238,20 @@ module Scenic
192
238
  end
193
239
  end
194
240
 
195
- def connection
196
- Class.new { extend Statements }
241
+ def connection(transactions_enabled: true)
242
+ DummyConnection.new(transactions_enabled: transactions_enabled)
243
+ end
244
+ end
245
+
246
+ class DummyConnection
247
+ include Statements
248
+
249
+ def initialize(transactions_enabled:)
250
+ @transactions_enabled = transactions_enabled
251
+ end
252
+
253
+ def transaction_open?
254
+ @transactions_enabled
197
255
  end
198
256
  end
199
257
  end
data/spec/spec_helper.rb CHANGED
@@ -2,24 +2,39 @@ ENV["RAILS_ENV"] = "test"
2
2
  require "database_cleaner"
3
3
 
4
4
  require File.expand_path("dummy/config/environment", __dir__)
5
- require "support/rails_configuration_helpers"
6
- require "support/generator_spec_setup"
7
- require "support/view_definition_helpers"
5
+
6
+ Dir.glob("#{__dir__}/support/**/*.rb").each { |f| require f }
8
7
 
9
8
  RSpec.configure do |config|
10
9
  config.order = "random"
10
+ config.include DatabaseSchemaHelpers
11
11
  config.include ViewDefinitionHelpers
12
12
  config.include RailsConfigurationHelpers
13
13
  DatabaseCleaner.strategy = :transaction
14
14
 
15
15
  config.around(:each, db: true) do |example|
16
- ActiveRecord::SchemaMigration.create_table
16
+ case ActiveRecord.gem_version
17
+ when Gem::Requirement.new(">= 7.2")
18
+ ActiveRecord::SchemaMigration
19
+ .new(ActiveRecord::Tasks::DatabaseTasks.migration_connection_pool)
20
+ .create_table
21
+ when Gem::Requirement.new("~> 7.1.0")
22
+ ActiveRecord::SchemaMigration
23
+ .new(ActiveRecord::Tasks::DatabaseTasks.migration_connection)
24
+ .create_table
25
+ when Gem::Requirement.new("< 7.1")
26
+ ActiveRecord::SchemaMigration.create_table
27
+ end
17
28
 
18
29
  DatabaseCleaner.start
19
30
  example.run
20
31
  DatabaseCleaner.clean
21
32
  end
22
33
 
34
+ config.before(:each, silence: true) do |example|
35
+ allow_any_instance_of(ActiveRecord::Migration).to receive(:say)
36
+ end
37
+
23
38
  if defined? ActiveSupport::Testing::Stream
24
39
  config.include ActiveSupport::Testing::Stream
25
40
  end
@@ -0,0 +1,28 @@
1
+ module DatabaseSchemaHelpers
2
+ def dump_schema(stream)
3
+ case ActiveRecord.gem_version
4
+ when Gem::Requirement.new(">= 7.2")
5
+ ActiveRecord::SchemaDumper.dump(Search.connection_pool, stream)
6
+ else
7
+ ActiveRecord::SchemaDumper.dump(Search.connection, stream)
8
+ end
9
+ end
10
+
11
+ def ar_connection
12
+ ActiveRecord::Base.connection
13
+ end
14
+
15
+ def create_materialized_view(name, sql)
16
+ ar_connection.execute("CREATE MATERIALIZED VIEW #{name} AS #{sql}")
17
+ end
18
+
19
+ def add_index(view, columns, name: nil)
20
+ ar_connection.add_index(view, columns, name: name)
21
+ end
22
+
23
+ def indexes_for(view_name)
24
+ Scenic::Adapters::Postgres::Indexes
25
+ .new(connection: ar_connection)
26
+ .on(view_name)
27
+ end
28
+ end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scenic
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Derek Prior
8
8
  - Caleb Hearth
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2024-03-28 00:00:00.000000000 Z
11
+ date: 2025-06-30 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: bundler
@@ -71,16 +70,16 @@ dependencies:
71
70
  name: pg
72
71
  requirement: !ruby/object:Gem::Requirement
73
72
  requirements:
74
- - - "~>"
73
+ - - ">="
75
74
  - !ruby/object:Gem::Version
76
- version: '0.19'
75
+ version: '0'
77
76
  type: :development
78
77
  prerelease: false
79
78
  version_requirements: !ruby/object:Gem::Requirement
80
79
  requirements:
81
- - - "~>"
80
+ - - ">="
82
81
  - !ruby/object:Gem::Version
83
- version: '0.19'
82
+ version: '0'
84
83
  - !ruby/object:Gem::Dependency
85
84
  name: pry
86
85
  requirement: !ruby/object:Gem::Requirement
@@ -198,6 +197,7 @@ files:
198
197
  - CHANGELOG.md
199
198
  - CODE_OF_CONDUCT.md
200
199
  - CONTRIBUTING.md
200
+ - FUNDING.yml
201
201
  - Gemfile
202
202
  - LICENSE.txt
203
203
  - README.md
@@ -221,9 +221,13 @@ files:
221
221
  - lib/scenic/adapters/postgres.rb
222
222
  - lib/scenic/adapters/postgres/connection.rb
223
223
  - lib/scenic/adapters/postgres/errors.rb
224
+ - lib/scenic/adapters/postgres/index_creation.rb
225
+ - lib/scenic/adapters/postgres/index_migration.rb
224
226
  - lib/scenic/adapters/postgres/index_reapplication.rb
225
227
  - lib/scenic/adapters/postgres/indexes.rb
226
228
  - lib/scenic/adapters/postgres/refresh_dependencies.rb
229
+ - lib/scenic/adapters/postgres/side_by_side.rb
230
+ - lib/scenic/adapters/postgres/temporary_name.rb
227
231
  - lib/scenic/adapters/postgres/views.rb
228
232
  - lib/scenic/command_recorder.rb
229
233
  - lib/scenic/command_recorder/statement_arguments.rb
@@ -258,7 +262,11 @@ files:
258
262
  - spec/generators/scenic/view/view_generator_spec.rb
259
263
  - spec/integration/revert_spec.rb
260
264
  - spec/scenic/adapters/postgres/connection_spec.rb
265
+ - spec/scenic/adapters/postgres/index_creation_spec.rb
266
+ - spec/scenic/adapters/postgres/index_migration_spec.rb
261
267
  - spec/scenic/adapters/postgres/refresh_dependencies_spec.rb
268
+ - spec/scenic/adapters/postgres/side_by_side_spec.rb
269
+ - spec/scenic/adapters/postgres/temporary_name_spec.rb
262
270
  - spec/scenic/adapters/postgres/views_spec.rb
263
271
  - spec/scenic/adapters/postgres_spec.rb
264
272
  - spec/scenic/command_recorder/statement_arguments_spec.rb
@@ -268,14 +276,15 @@ files:
268
276
  - spec/scenic/schema_dumper_spec.rb
269
277
  - spec/scenic/statements_spec.rb
270
278
  - spec/spec_helper.rb
279
+ - spec/support/database_schema_helpers.rb
271
280
  - spec/support/generator_spec_setup.rb
272
281
  - spec/support/rails_configuration_helpers.rb
273
282
  - spec/support/view_definition_helpers.rb
274
283
  homepage: https://github.com/scenic-views/scenic
275
284
  licenses:
276
285
  - MIT
277
- metadata: {}
278
- post_install_message:
286
+ metadata:
287
+ funding-uri: https://github.com/scenic-views/scenic
279
288
  rdoc_options: []
280
289
  require_paths:
281
290
  - lib
@@ -290,8 +299,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
290
299
  - !ruby/object:Gem::Version
291
300
  version: '0'
292
301
  requirements: []
293
- rubygems_version: 3.5.3
294
- signing_key:
302
+ rubygems_version: 3.6.2
295
303
  specification_version: 4
296
304
  summary: Support for database views in Rails migrations
297
305
  test_files: []