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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +13 -8
- data/.gitignore +1 -0
- data/CHANGELOG.md +48 -19
- data/FUNDING.yml +1 -0
- data/Gemfile +2 -2
- data/README.md +71 -18
- data/lib/generators/scenic/materializable.rb +27 -1
- data/lib/generators/scenic/view/templates/db/migrate/update_view.erb +1 -1
- data/lib/scenic/adapters/postgres/index_creation.rb +68 -0
- data/lib/scenic/adapters/postgres/index_migration.rb +70 -0
- data/lib/scenic/adapters/postgres/index_reapplication.rb +3 -28
- data/lib/scenic/adapters/postgres/side_by_side.rb +50 -0
- data/lib/scenic/adapters/postgres/temporary_name.rb +34 -0
- data/lib/scenic/adapters/postgres/views.rb +83 -10
- data/lib/scenic/adapters/postgres.rb +41 -18
- data/lib/scenic/schema_dumper.rb +0 -14
- data/lib/scenic/statements.rb +46 -13
- data/lib/scenic/version.rb +1 -1
- data/scenic.gemspec +5 -1
- data/spec/acceptance/user_manages_views_spec.rb +11 -0
- data/spec/dummy/config/application.rb +4 -0
- data/spec/generators/scenic/view/view_generator_spec.rb +26 -0
- data/spec/scenic/adapters/postgres/index_creation_spec.rb +54 -0
- data/spec/scenic/adapters/postgres/index_migration_spec.rb +24 -0
- data/spec/scenic/adapters/postgres/side_by_side_spec.rb +24 -0
- data/spec/scenic/adapters/postgres/temporary_name_spec.rb +23 -0
- data/spec/scenic/adapters/postgres_spec.rb +44 -3
- data/spec/scenic/command_recorder_spec.rb +18 -0
- data/spec/scenic/schema_dumper_spec.rb +29 -8
- data/spec/scenic/statements_spec.rb +62 -4
- data/spec/spec_helper.rb +19 -4
- data/spec/support/database_schema_helpers.rb +28 -0
- metadata +19 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 772babaa9c177e3f24e45e0b61c7bed0475b5267806bea563d4a7ec1e97830c9
|
4
|
+
data.tar.gz: af0d81ab175baa0b0ac35d11216f76bb9d1663f3483fde733a862cff92067a01
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ac9d6aa1abb1bea6d69d50de6aad86e0a567abd2b1631645b2f62d1bbd687b362e5af7ebe6fde7ad94fc6203a76df57fb3893a4f677b0def61740bc7af1f92a5
|
7
|
+
data.tar.gz: 59b77cb6f2b51334a3cefb6ef121a25354c7d202bef2c9898202da84d4a6b889898cc6d6b5bcd7e38b37b020501cff80628c843467d6d3a03dee6f309fb0a860
|
data/.github/workflows/ci.yml
CHANGED
@@ -13,7 +13,7 @@ jobs:
|
|
13
13
|
|
14
14
|
steps:
|
15
15
|
- name: Checkout
|
16
|
-
uses: actions/checkout@
|
16
|
+
uses: actions/checkout@v4
|
17
17
|
|
18
18
|
- name: Run standardrb
|
19
19
|
uses: standardrb/standard-ruby-action@f533e61f461ccb766b2d9c235abf59be02aea793
|
@@ -26,16 +26,21 @@ jobs:
|
|
26
26
|
|
27
27
|
build:
|
28
28
|
name: Ruby ${{ matrix.ruby }}, Rails ${{ matrix.rails }}
|
29
|
+
continue-on-error: ${{ matrix.continue-on-error }}
|
29
30
|
|
30
31
|
strategy:
|
31
32
|
fail-fast: false
|
32
33
|
matrix:
|
33
|
-
ruby: ["
|
34
|
-
rails: ["
|
34
|
+
ruby: ["3.4", "3.3"]
|
35
|
+
rails: ["8.0", "7.2"]
|
35
36
|
continue-on-error: [false]
|
36
|
-
|
37
|
-
- ruby: "3.
|
38
|
-
rails: "
|
37
|
+
include:
|
38
|
+
- ruby: "3.4"
|
39
|
+
rails: "main"
|
40
|
+
continue-on-error: true
|
41
|
+
- ruby: "head"
|
42
|
+
rails: "main"
|
43
|
+
continue-on-error: true
|
39
44
|
|
40
45
|
runs-on: ubuntu-latest
|
41
46
|
|
@@ -60,7 +65,7 @@ jobs:
|
|
60
65
|
|
61
66
|
steps:
|
62
67
|
- name: Checkout
|
63
|
-
uses: actions/checkout@
|
68
|
+
uses: actions/checkout@v4
|
64
69
|
|
65
70
|
- name: Install Ruby ${{ matrix.ruby }}
|
66
71
|
uses: ruby/setup-ruby@v1
|
@@ -74,7 +79,7 @@ jobs:
|
|
74
79
|
run: bundle lock
|
75
80
|
|
76
81
|
- name: Cache dependencies
|
77
|
-
uses: actions/cache@
|
82
|
+
uses: actions/cache@v4
|
78
83
|
with:
|
79
84
|
path: vendor/bundle
|
80
85
|
key: bundle-${{ hashFiles('Gemfile.lock') }}
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -5,14 +5,30 @@ changelog, see the [commits] for each version via the version links.
|
|
5
5
|
|
6
6
|
[commits]: https://github.com/scenic-views/scenic/commits/master
|
7
7
|
|
8
|
+
## [1.9.0] - June 30, 2025
|
9
|
+
|
10
|
+
[1.9.0]: https://github.com/scenic-views/scenic/compare/v1.8.0...v1.9.0
|
11
|
+
|
12
|
+
### Added
|
13
|
+
|
14
|
+
- Added topological sorting of views when dumping schema. This should limit
|
15
|
+
unrelated changes to schema.rb once the views are sorted the first time.
|
16
|
+
- Added `side_by_side` mode to materialized view updates. This creates the
|
17
|
+
updated materialized view with a temporary name before swapping it with the
|
18
|
+
live view, minimizing the time the view is locked for updating.
|
19
|
+
|
20
|
+
### Fixed
|
21
|
+
|
22
|
+
- Only refresh concurrently if the materialized view is populated.
|
23
|
+
|
8
24
|
## [1.8.0] - March 28, 2024
|
9
25
|
|
10
26
|
[1.8.0]: https://github.com/scenic-views/scenic/compare/v1.7.0...v1.8.0
|
11
27
|
|
12
28
|
### Added
|
13
29
|
|
14
|
-
|
15
|
-
|
30
|
+
- Added `#populated?` to check the state of materialized views - _Daisuke
|
31
|
+
Fujimura_, _Dr Nic Williams_
|
16
32
|
|
17
33
|
## [1.7.0] - December 8, 2022
|
18
34
|
|
@@ -20,13 +36,13 @@ changelog, see the [commits] for each version via the version links.
|
|
20
36
|
|
21
37
|
### Added
|
22
38
|
|
23
|
-
|
24
|
-
`replace_view` schema statement -
|
39
|
+
- Added the `--replace` CLI flag to generate a migration that uses the
|
40
|
+
`replace_view` schema statement - _Dan Hixon_
|
25
41
|
|
26
42
|
### Fixed
|
27
43
|
|
28
|
-
|
29
|
-
generators -
|
44
|
+
- Fixed deprecation notice from newer versions of ERB when using scenic
|
45
|
+
generators - _Ali Ismayilov_
|
30
46
|
|
31
47
|
## [1.6.0] - February 13, 2022
|
32
48
|
|
@@ -34,9 +50,9 @@ changelog, see the [commits] for each version via the version links.
|
|
34
50
|
|
35
51
|
### Fixed
|
36
52
|
|
37
|
-
|
38
|
-
|
39
|
-
|
53
|
+
- Exclude pg*stat_statements_info (#349) 76bface - \_Caleb Hearth*
|
54
|
+
- Fix serialization of views with backslashes c625d1b - _Ben Sheldon_
|
55
|
+
- Handle ActiveRecord table name prefix and suffix b1544dc - _Derek Prior_
|
40
56
|
|
41
57
|
## [1.5.5] - December 15, 2021
|
42
58
|
|
@@ -109,7 +125,6 @@ changelog, see the [commits] for each version via the version links.
|
|
109
125
|
- Fixed a cascading refresh issue when the name of the view to trigger the
|
110
126
|
refresh is a substring of one of its dependencies.
|
111
127
|
|
112
|
-
|
113
128
|
[1.5.0]: https://github.com/scenic-views/scenic/compare/v1.4.1...v1.5.0
|
114
129
|
|
115
130
|
## [1.4.1] - December 15, 2017
|
@@ -144,26 +159,30 @@ changelog, see the [commits] for each version via the version links.
|
|
144
159
|
## [1.3.0] - May 27, 2016
|
145
160
|
|
146
161
|
### Added
|
162
|
+
|
147
163
|
- Add `replace_view` migration statement, which issues `CREATE OR REPLACE
|
148
|
-
|
164
|
+
VIEW` rather than `CREATE VIEW` or `DROP VIEW` and `CREATE VIEW`.
|
149
165
|
- Schema-qualify views outside the 'public' namespace, such as
|
150
166
|
`scenic.searches`
|
151
167
|
|
152
168
|
### Fixed
|
153
|
-
|
169
|
+
|
170
|
+
- Singularize generated model name when injecting into class.
|
154
171
|
Previously, pluralized names would issue a warning and Scenic would
|
155
172
|
attempt to insert model code into the pluralized model file.
|
156
|
-
|
173
|
+
- Convert shell-based smoke tests to RSpec syntax.
|
157
174
|
|
158
175
|
[1.3.0]: https://github.com/scenic-views/scenic/compare/v1.2.0...v1.3.0
|
159
176
|
|
160
177
|
## [1.2.0] - February 5, 2016
|
161
178
|
|
162
179
|
### Added
|
180
|
+
|
163
181
|
- The generators now accept namespaced view definitions. For example: `rails
|
164
|
-
|
182
|
+
generate scenic:view my_app.users`.
|
165
183
|
|
166
184
|
### Fixed
|
185
|
+
|
167
186
|
- Materialized view indexes are now properly dumped to `db/schema.rb`. This was
|
168
187
|
an oversight in previous releases, meaning `rake db:schema:load` was missing
|
169
188
|
indexes.
|
@@ -172,7 +191,7 @@ changelog, see the [commits] for each version via the version links.
|
|
172
191
|
returning no indexes.
|
173
192
|
|
174
193
|
**Note**: Dumping materialized view indexes will produce an invalid
|
175
|
-
`db/schema.rb` file
|
194
|
+
`db/schema.rb` file under Rails 5 beta 1 and beta 2. This is fixed on Rails
|
176
195
|
master.
|
177
196
|
|
178
197
|
[1.2.0]: https://github.com/scenic-views/scenic/compare/v1.1.1...v1.2.0
|
@@ -180,8 +199,9 @@ master.
|
|
180
199
|
## [1.1.1] - January 29, 2016
|
181
200
|
|
182
201
|
### Fixed
|
202
|
+
|
183
203
|
- Some schema operations were failing with a `PG::ConnectionBad: connection is
|
184
|
-
|
204
|
+
closed` error. This has been fixed by ensuring we grab a fresh connection for
|
185
205
|
all operations.
|
186
206
|
|
187
207
|
[1.1.1]: https://github.com/scenic-views/scenic/compare/v1.1.0...v1.1.1
|
@@ -189,12 +209,14 @@ master.
|
|
189
209
|
## [1.1.0] - January 8, 2016
|
190
210
|
|
191
211
|
### Added
|
212
|
+
|
192
213
|
- Added support for updating materialized view definitions while maintaining
|
193
214
|
existing indexes that are still applicable after the update.
|
194
215
|
- Added support for refreshing materialized views concurrently (requires
|
195
216
|
Postgres 9.4 or newer).
|
196
217
|
|
197
218
|
### Fixed
|
219
|
+
|
198
220
|
- The schema dumper will now dump views and materialized views together in the
|
199
221
|
order they are returned by Postgres. This fixes issues when loading views that
|
200
222
|
depend on other views via `rake db:schema:load`.
|
@@ -209,26 +231,30 @@ master.
|
|
209
231
|
## [1.0.0] - November 23, 2015
|
210
232
|
|
211
233
|
### Added
|
234
|
+
|
212
235
|
- Added support for [materialized views].
|
213
236
|
- Allow changing the database adapter via `Scenic::Configuration`.
|
214
237
|
|
215
238
|
### Fixed
|
239
|
+
|
216
240
|
- Improved formatting of the view when dumped to `schema.rb`.
|
217
241
|
- Fixed generation of namespaced models by using ActiveRecord's own model
|
218
242
|
generator.
|
219
243
|
- Eliminated `alias_method_chain` deprecation when running with Rails master
|
220
244
|
(5.0).
|
221
245
|
|
222
|
-
[materialized views]:https://github.com/scenic-views/scenic/blob/v1.0.0/README.md
|
246
|
+
[materialized views]: https://github.com/scenic-views/scenic/blob/v1.0.0/README.md
|
223
247
|
[1.0.0]: https://github.com/scenic-views/scenic/compare/v0.3.0...v1.0.0
|
224
248
|
|
225
249
|
## [0.3.0] - January 23, 2015
|
226
250
|
|
227
251
|
### Added
|
252
|
+
|
228
253
|
- Previous view definition is copied into new view definition file when updating
|
229
254
|
an existing view.
|
230
255
|
|
231
256
|
### Fixed
|
257
|
+
|
232
258
|
- We avoid dumping views that belong to Postgres extensions.
|
233
259
|
- `db/schema.rb` is prettier thanks to a blank line after each view definition.
|
234
260
|
|
@@ -237,6 +263,7 @@ master.
|
|
237
263
|
## [0.2.1] - January 5, 2015
|
238
264
|
|
239
265
|
### Fixed
|
266
|
+
|
240
267
|
- View generator will now create `db/views` directory if necessary.
|
241
268
|
|
242
269
|
[0.2.1]: https://github.com/scenic-views/scenic/compare/v0.2.0...v0.2.1
|
@@ -244,9 +271,11 @@ master.
|
|
244
271
|
## [0.2.0] - August 11, 2014
|
245
272
|
|
246
273
|
### Added
|
274
|
+
|
247
275
|
- Teach view generator to update existing views.
|
248
276
|
|
249
277
|
### Fixed
|
278
|
+
|
250
279
|
- Raise an error if view definition is empty.
|
251
280
|
|
252
281
|
[0.2.0]: https://github.com/scenic-views/scenic/compare/v0.1.0...v0.2.0
|
@@ -256,8 +285,8 @@ master.
|
|
256
285
|
Scenic makes it easier to work with Postgres views in Rails.
|
257
286
|
|
258
287
|
It introduces view methods to ActiveRecord::Migration and allows views to be
|
259
|
-
dumped to db/schema.rb.
|
260
|
-
and migrations.
|
288
|
+
dumped to db/schema.rb. It provides generators for models, view definitions,
|
289
|
+
and migrations. It is built around a basic versioning system for view
|
261
290
|
definition files.
|
262
291
|
|
263
292
|
In short, go add a view to your app.
|
data/FUNDING.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
github: [calebhearth, derekprior]
|
data/Gemfile
CHANGED
@@ -3,9 +3,9 @@ source "https://rubygems.org"
|
|
3
3
|
# Specify your gem's dependencies in scenic.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
rails_version = ENV.fetch("RAILS_VERSION", "
|
6
|
+
rails_version = ENV.fetch("RAILS_VERSION", "8.0")
|
7
7
|
|
8
|
-
rails_constraint = if rails_version == "
|
8
|
+
rails_constraint = if rails_version == "main"
|
9
9
|
{github: "rails/rails"}
|
10
10
|
else
|
11
11
|
"~> #{rails_version}.0"
|
data/README.md
CHANGED
@@ -24,7 +24,7 @@ Scenic ships with support for PostgreSQL. The adapter is configurable (see
|
|
24
24
|
|
25
25
|
If you're using Postgres, Add `gem "scenic"` to your Gemfile and run `bundle
|
26
26
|
install`. If you're using something other than Postgres, check out the available
|
27
|
-
[third
|
27
|
+
[third-party adapters](https://github.com/scenic-views/scenic#when-will-you-support-mysql-sqlite-or-other-databases).
|
28
28
|
|
29
29
|
## Great, how do I create a view?
|
30
30
|
|
@@ -91,7 +91,8 @@ hierarchies of dependent views.
|
|
91
91
|
|
92
92
|
Scenic offers a `replace_view` schema statement, resulting in a `CREATE OR
|
93
93
|
REPLACE VIEW` SQL query which will update the supplied view in place, retaining
|
94
|
-
all dependencies. Materialized views cannot be replaced in this fashion
|
94
|
+
all dependencies. Materialized views cannot be replaced in this fashion, though
|
95
|
+
the `side_by_side` update strategy may yield similar results (see below).
|
95
96
|
|
96
97
|
You can generate a migration that uses the `replace_view` schema statement by
|
97
98
|
passing the `--replace` option to the `scenic:view` generator:
|
@@ -137,7 +138,7 @@ end
|
|
137
138
|
```
|
138
139
|
|
139
140
|
Scenic even provides a `scenic:model` generator that is a superset of
|
140
|
-
`scenic:view`.
|
141
|
+
`scenic:view`. It will act identically to the Rails `model` generator except
|
141
142
|
that it will create a Scenic view migration rather than a table migration.
|
142
143
|
|
143
144
|
There is no special base class or mixin needed. If desired, any code the model
|
@@ -185,6 +186,44 @@ you would need to refresh view B first, then right after refresh view A. If you
|
|
185
186
|
would like this cascading refresh of materialized views, set `cascade: true`
|
186
187
|
when you refresh your materialized view.
|
187
188
|
|
189
|
+
## Can I update the definition of a materialized view without dropping it?
|
190
|
+
|
191
|
+
No, but Scenic can help you approximate this behavior with its `side_by_side`
|
192
|
+
update strategy.
|
193
|
+
|
194
|
+
Generally, changing the definition of a materialized view requires dropping it
|
195
|
+
and recreating it, either without data or with a non-concurrent refresh. The
|
196
|
+
materialized view will be locked for selects during the refresh process, which
|
197
|
+
can cause problems in your application if the refresh is not fast.
|
198
|
+
|
199
|
+
The `side_by_side` update strategy prepares the new version of the view under a
|
200
|
+
temporary name. This includes copying the indexes from the original view and
|
201
|
+
refreshing the data. Once prepared, the original view is dropped and the new
|
202
|
+
view is renamed to the original view's name. This process minimizes the time the
|
203
|
+
view is locked for selects at the cost of additional disk space.
|
204
|
+
|
205
|
+
You can generate a migration that uses the `side_by_side` strategy by passing
|
206
|
+
the `--side-by-side` option to the `scenic:view` generator:
|
207
|
+
|
208
|
+
```sh
|
209
|
+
$ rails generate scenic:view search_results --materialized --side-by-side
|
210
|
+
create db/views/search_results_v02.sql
|
211
|
+
create db/migrate/[TIMESTAMP]_update_search_results_to_version_2.rb
|
212
|
+
```
|
213
|
+
|
214
|
+
The migration will look something like this:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
class UpdateSearchResultsToVersion2 < ActiveRecord::Migration
|
218
|
+
def change
|
219
|
+
update_view :search_results,
|
220
|
+
version: 2,
|
221
|
+
revert_to_version: 1,
|
222
|
+
materialized: { side_by_side: true }
|
223
|
+
end
|
224
|
+
end
|
225
|
+
```
|
226
|
+
|
188
227
|
## I don't need this view anymore. Make it go away.
|
189
228
|
|
190
229
|
Scenic gives you `drop_view` too:
|
@@ -198,7 +237,7 @@ end
|
|
198
237
|
|
199
238
|
## FAQs
|
200
239
|
|
201
|
-
|
240
|
+
### Why do I get an error when querying a view-backed model with `find`, `last`, or `first`?
|
202
241
|
|
203
242
|
ActiveRecord's `find` method expects to query based on your model's primary key,
|
204
243
|
but views do not have primary keys. Additionally, the `first` and `last` methods
|
@@ -213,7 +252,7 @@ class People < ApplicationRecord
|
|
213
252
|
end
|
214
253
|
```
|
215
254
|
|
216
|
-
|
255
|
+
### Why is my view missing columns from the underlying table?
|
217
256
|
|
218
257
|
Did you create the view with `SELECT [table_name].*`? Most (possibly all)
|
219
258
|
relational databases freeze the view definition at the time of creation. New
|
@@ -226,7 +265,7 @@ add_column :posts, :title, :string
|
|
226
265
|
update_view :posts_with_aggregate_data, version: 2, revert_to_version: 2
|
227
266
|
```
|
228
267
|
|
229
|
-
|
268
|
+
### When will you support MySQL, SQLite, or other databases?
|
230
269
|
|
231
270
|
We have no plans to add first-party adapters for other relational databases at
|
232
271
|
this time because we (the maintainers) do not currently have a use for them.
|
@@ -234,7 +273,7 @@ It's our experience that maintaining a library effectively requires regular use
|
|
234
273
|
of its features. We're not in a good position to support MySQL, SQLite or other
|
235
274
|
database users.
|
236
275
|
|
237
|
-
Scenic
|
276
|
+
Scenic _does_ support configuring different database adapters and should be
|
238
277
|
extendable with adapter libraries. If you implement such an adapter, we're happy
|
239
278
|
to review and link to it. We're also happy to make changes that would better
|
240
279
|
accommodate adapter gems.
|
@@ -242,25 +281,39 @@ accommodate adapter gems.
|
|
242
281
|
We are aware of the following existing adapter libraries for Scenic which may
|
243
282
|
meet your needs:
|
244
283
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
284
|
+
- [`scenic_sqlite_adapter`](https://github.com/pdebelak/scenic_sqlite_adapter)
|
285
|
+
- [`scenic-mysql_adapter`](https://github.com/cainlevy/scenic-mysql_adapter)
|
286
|
+
- [`scenic-sqlserver-adapter`](https://github.com/ClickMechanic/scenic_sqlserver_adapter)
|
287
|
+
- [`scenic-oracle_adapter`](https://github.com/cdinger/scenic-oracle_adapter)
|
249
288
|
|
250
289
|
Please note that the maintainers of Scenic make no assertions about the
|
251
290
|
quality or security of the above adapters.
|
252
291
|
|
253
|
-
|
292
|
+
## About
|
293
|
+
|
294
|
+
### Used By
|
254
295
|
|
255
|
-
|
296
|
+
Scenic is used by some popular open source Rails apps:
|
297
|
+
[Mastodon](https://github.com/mastodon/mastodon/),
|
298
|
+
[Code.org](https://github.com/code-dot-org/code-dot-org), and
|
299
|
+
[Lobste.rs](https://github.com/lobsters/lobsters/).
|
300
|
+
|
301
|
+
### Related projects
|
302
|
+
|
303
|
+
- [`fx`](https://github.com/teoljungberg/fx) Versioned database functions and
|
256
304
|
triggers for Rails
|
257
305
|
|
258
|
-
|
306
|
+
### Media
|
259
307
|
|
260
|
-
|
261
|
-
|
262
|
-
[
|
263
|
-
[
|
308
|
+
Here are a few posts we've seen discussing Scenic:
|
309
|
+
|
310
|
+
- [Announcing Scenic - Versioned Database Views for Rails](https://thoughtbot.com/blog/announcing-scenic--versioned-database-views-for-rails) by Derek Prior for thoughtbot
|
311
|
+
- [Effectively Using Materialized Views in Ruby on Rails](https://pganalyze.com/blog/materialized-views-ruby-rails) by Leigh Halliday for pganalyze
|
312
|
+
- [Optimizing String Concatenation in Ruby on Rails](https://dev.to/pimp_my_ruby/from-slow-to-lightning-fast-optimizing-string-concatenation-in-ruby-on-rails-28nk)
|
313
|
+
- [Materialized Views In Ruby On Rails With Scenic](https://www.ideamotive.co/blog/materialized-views-ruby-rails-scenic) by Dawid Karczewski for Ideamotive
|
314
|
+
- [Using Scenic and SQL views to aggregate data](https://dev.to/weareredlight/using-scenic-and-sql-views-to-aggregate-data-226k) by André Perdigão for Redlight Software
|
315
|
+
|
316
|
+
### Maintainers
|
264
317
|
|
265
318
|
Scenic is maintained by [Derek Prior], [Caleb Hearth], and you, our
|
266
319
|
contributors.
|
@@ -14,7 +14,14 @@ module Scenic
|
|
14
14
|
type: :boolean,
|
15
15
|
required: false,
|
16
16
|
desc: "Adds WITH NO DATA when materialized view creates/updates",
|
17
|
-
default: false
|
17
|
+
default: false,
|
18
|
+
aliases: ["--no-data"]
|
19
|
+
class_option :side_by_side,
|
20
|
+
type: :boolean,
|
21
|
+
required: false,
|
22
|
+
desc: "Uses side-by-side strategy to update materialized view",
|
23
|
+
default: false,
|
24
|
+
aliases: ["--side-by-side"]
|
18
25
|
class_option :replace,
|
19
26
|
type: :boolean,
|
20
27
|
required: false,
|
@@ -35,6 +42,25 @@ module Scenic
|
|
35
42
|
def no_data?
|
36
43
|
options[:no_data]
|
37
44
|
end
|
45
|
+
|
46
|
+
def side_by_side?
|
47
|
+
options[:side_by_side]
|
48
|
+
end
|
49
|
+
|
50
|
+
def materialized_view_update_options
|
51
|
+
set_options = {no_data: no_data?, side_by_side: side_by_side?}
|
52
|
+
.select { |_, v| v }
|
53
|
+
|
54
|
+
if set_options.empty?
|
55
|
+
"true"
|
56
|
+
else
|
57
|
+
string_options = set_options.reduce("") do |memo, (key, value)|
|
58
|
+
memo + "#{key}: #{value}, "
|
59
|
+
end
|
60
|
+
|
61
|
+
"{ #{string_options.chomp(", ")} }"
|
62
|
+
end
|
63
|
+
end
|
38
64
|
end
|
39
65
|
end
|
40
66
|
end
|
@@ -5,7 +5,7 @@ class <%= migration_class_name %> < <%= activerecord_migration_class %>
|
|
5
5
|
<%= method_name %> <%= formatted_plural_name %>,
|
6
6
|
version: <%= version %>,
|
7
7
|
revert_to_version: <%= previous_version %>,
|
8
|
-
materialized: <%=
|
8
|
+
materialized: <%= materialized_view_update_options %>
|
9
9
|
<%- else -%>
|
10
10
|
<%= method_name %> <%= formatted_plural_name %>, version: <%= version %>, revert_to_version: <%= previous_version %>
|
11
11
|
<%- end -%>
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Scenic
|
2
|
+
module Adapters
|
3
|
+
class Postgres
|
4
|
+
# Used to resiliently create indexes on a materialized view. If the index
|
5
|
+
# cannot be applied to the view (e.g. the columns don't exist any longer),
|
6
|
+
# we log that information and continue rather than raising an error. It is
|
7
|
+
# left to the user to judge whether the index is necessary and recreate
|
8
|
+
# it.
|
9
|
+
#
|
10
|
+
# Used when updating a materialized view to ensure the new version has all
|
11
|
+
# apprioriate indexes.
|
12
|
+
#
|
13
|
+
# @api private
|
14
|
+
class IndexCreation
|
15
|
+
# Creates the index creation object.
|
16
|
+
#
|
17
|
+
# @param connection [Connection] The connection to execute SQL against.
|
18
|
+
# @param speaker [#say] (ActiveRecord::Migration) The object used for
|
19
|
+
# logging the results of creating indexes.
|
20
|
+
def initialize(connection:, speaker: ActiveRecord::Migration.new)
|
21
|
+
@connection = connection
|
22
|
+
@speaker = speaker
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates the provided indexes. If an index cannot be created, it is
|
26
|
+
# logged and the process continues.
|
27
|
+
#
|
28
|
+
# @param indexes [Array<Scenic::Index>] The indexes to create.
|
29
|
+
#
|
30
|
+
# @return [void]
|
31
|
+
def try_create(indexes)
|
32
|
+
Array(indexes).each(&method(:try_index_create))
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :connection, :speaker
|
38
|
+
|
39
|
+
def try_index_create(index)
|
40
|
+
success = with_savepoint(index.index_name) do
|
41
|
+
connection.execute(index.definition)
|
42
|
+
end
|
43
|
+
|
44
|
+
if success
|
45
|
+
say "index '#{index.index_name}' on '#{index.object_name}' has been created"
|
46
|
+
else
|
47
|
+
say "index '#{index.index_name}' on '#{index.object_name}' is no longer valid and has been dropped."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def with_savepoint(name)
|
52
|
+
connection.execute("SAVEPOINT #{name}")
|
53
|
+
yield
|
54
|
+
connection.execute("RELEASE SAVEPOINT #{name}")
|
55
|
+
true
|
56
|
+
rescue
|
57
|
+
connection.execute("ROLLBACK TO SAVEPOINT #{name}")
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
def say(message)
|
62
|
+
subitem = true
|
63
|
+
speaker.say(message, subitem)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Scenic
|
2
|
+
module Adapters
|
3
|
+
class Postgres
|
4
|
+
# Used during side-by-side materialized view updates to migrate indexes
|
5
|
+
# from the original view to the new view.
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class IndexMigration
|
9
|
+
# Creates the index migration object.
|
10
|
+
#
|
11
|
+
# @param connection [Connection] The connection to execute SQL against.
|
12
|
+
# @param speaker [#say] (ActiveRecord::Migration) The object used for
|
13
|
+
# logging the results of migrating indexes.
|
14
|
+
def initialize(connection:, speaker: ActiveRecord::Migration.new)
|
15
|
+
@connection = connection
|
16
|
+
@speaker = speaker
|
17
|
+
end
|
18
|
+
|
19
|
+
# Retreives the indexes on the original view, renames them to avoid
|
20
|
+
# collisions, retargets the indexes to the destination view, and then
|
21
|
+
# creates the retargeted indexes.
|
22
|
+
#
|
23
|
+
# @param from [String] The name of the original view.
|
24
|
+
# @param to [String] The name of the destination view.
|
25
|
+
#
|
26
|
+
# @return [void]
|
27
|
+
def migrate(from:, to:)
|
28
|
+
source_indexes = Indexes.new(connection: connection).on(from)
|
29
|
+
retargeted_indexes = source_indexes.map { |i| retarget(i, to: to) }
|
30
|
+
source_indexes.each(&method(:rename))
|
31
|
+
|
32
|
+
if source_indexes.any?
|
33
|
+
say "indexes on '#{from}' have been renamed to avoid collisions"
|
34
|
+
end
|
35
|
+
|
36
|
+
IndexCreation
|
37
|
+
.new(connection: connection, speaker: speaker)
|
38
|
+
.try_create(retargeted_indexes)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :connection, :speaker
|
44
|
+
|
45
|
+
def retarget(index, to:)
|
46
|
+
new_definition = index.definition.sub(
|
47
|
+
/ON (.*)\.#{index.object_name}/,
|
48
|
+
'ON \1.' + to + " "
|
49
|
+
)
|
50
|
+
|
51
|
+
Scenic::Index.new(
|
52
|
+
object_name: to,
|
53
|
+
index_name: index.index_name,
|
54
|
+
definition: new_definition
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
def rename(index)
|
59
|
+
temporary_name = TemporaryName.new(index.index_name).to_s
|
60
|
+
connection.rename_index(index.object_name, index.index_name, temporary_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def say(message)
|
64
|
+
subitem = true
|
65
|
+
speaker.say(message, subitem)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|