scenic 1.7.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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +29 -4
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +55 -17
  5. data/CONTRIBUTING.md +1 -0
  6. data/FUNDING.yml +1 -0
  7. data/Gemfile +4 -4
  8. data/README.md +84 -25
  9. data/Rakefile +1 -1
  10. data/bin/standardrb +27 -0
  11. data/lib/generators/scenic/materializable.rb +27 -1
  12. data/lib/generators/scenic/model/model_generator.rb +7 -16
  13. data/lib/generators/scenic/model/templates/model.erb +4 -0
  14. data/lib/generators/scenic/view/templates/db/migrate/update_view.erb +2 -2
  15. data/lib/generators/scenic/view/view_generator.rb +5 -5
  16. data/lib/scenic/adapters/postgres/index_creation.rb +68 -0
  17. data/lib/scenic/adapters/postgres/index_migration.rb +70 -0
  18. data/lib/scenic/adapters/postgres/index_reapplication.rb +3 -28
  19. data/lib/scenic/adapters/postgres/indexes.rb +1 -1
  20. data/lib/scenic/adapters/postgres/refresh_dependencies.rb +3 -3
  21. data/lib/scenic/adapters/postgres/side_by_side.rb +50 -0
  22. data/lib/scenic/adapters/postgres/temporary_name.rb +34 -0
  23. data/lib/scenic/adapters/postgres/views.rb +85 -12
  24. data/lib/scenic/adapters/postgres.rb +64 -16
  25. data/lib/scenic/definition.rb +1 -1
  26. data/lib/scenic/schema_dumper.rb +0 -14
  27. data/lib/scenic/statements.rb +49 -16
  28. data/lib/scenic/version.rb +1 -1
  29. data/scenic.gemspec +15 -11
  30. data/spec/acceptance/user_manages_views_spec.rb +11 -0
  31. data/spec/dummy/Rakefile +5 -5
  32. data/spec/dummy/bin/bundle +2 -2
  33. data/spec/dummy/bin/rails +3 -3
  34. data/spec/dummy/bin/rake +2 -2
  35. data/spec/dummy/config/application.rb +4 -0
  36. data/spec/dummy/config.ru +1 -1
  37. data/spec/dummy/db/migrate/20220112154220_add_pg_stat_statements_extension.rb +1 -1
  38. data/spec/dummy/db/schema.rb +0 -2
  39. data/spec/generators/scenic/model/model_generator_spec.rb +9 -1
  40. data/spec/generators/scenic/view/view_generator_spec.rb +28 -2
  41. data/spec/integration/revert_spec.rb +1 -1
  42. data/spec/scenic/adapters/postgres/connection_spec.rb +1 -1
  43. data/spec/scenic/adapters/postgres/index_creation_spec.rb +54 -0
  44. data/spec/scenic/adapters/postgres/index_migration_spec.rb +24 -0
  45. data/spec/scenic/adapters/postgres/refresh_dependencies_spec.rb +9 -9
  46. data/spec/scenic/adapters/postgres/side_by_side_spec.rb +24 -0
  47. data/spec/scenic/adapters/postgres/temporary_name_spec.rb +23 -0
  48. data/spec/scenic/adapters/postgres_spec.rb +95 -8
  49. data/spec/scenic/command_recorder/statement_arguments_spec.rb +4 -4
  50. data/spec/scenic/command_recorder_spec.rb +30 -12
  51. data/spec/scenic/schema_dumper_spec.rb +35 -14
  52. data/spec/scenic/statements_spec.rb +66 -8
  53. data/spec/spec_helper.rb +19 -4
  54. data/spec/support/database_schema_helpers.rb +28 -0
  55. data/spec/support/generator_spec_setup.rb +2 -2
  56. data/spec/support/view_definition_helpers.rb +1 -1
  57. metadata +35 -48
  58. data/.hound.yml +0 -2
  59. data/.rubocop.yml +0 -129
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b378292bd7d642dcd235d01f5a31d94c74065b798ab469a9c091f68e936f4d9c
4
- data.tar.gz: b97f6e156685d4c03761543d5748f1381447c94cfcc42f7d6ddb8714e16791d0
3
+ metadata.gz: 772babaa9c177e3f24e45e0b61c7bed0475b5267806bea563d4a7ec1e97830c9
4
+ data.tar.gz: af0d81ab175baa0b0ac35d11216f76bb9d1663f3483fde733a862cff92067a01
5
5
  SHA512:
6
- metadata.gz: 4ffb0c73e52da5122a8ce3092b7f280d1a1dcaac9ca03a5f8ea63fe361d1b011d3c71f54706ff17d8ee1768fbdc1ba32c38bfebbcda320271e0b751eaea340f3
7
- data.tar.gz: 846d53ffe8c0d0209ab6517c47e0447ee3b40a81e5f55f845f16a1b3f697cebd44ad38550a5fe8d10016ab19b3c1197f36ea9f3f5f7f42395a1431790684e0c1
6
+ metadata.gz: ac9d6aa1abb1bea6d69d50de6aad86e0a567abd2b1631645b2f62d1bbd687b362e5af7ebe6fde7ad94fc6203a76df57fb3893a4f677b0def61740bc7af1f92a5
7
+ data.tar.gz: 59b77cb6f2b51334a3cefb6ef121a25354c7d202bef2c9898202da84d4a6b889898cc6d6b5bcd7e38b37b020501cff80628c843467d6d3a03dee6f309fb0a860
@@ -7,15 +7,40 @@ on:
7
7
  branches: "*"
8
8
 
9
9
  jobs:
10
+ standard:
11
+ name: Lint with Standard
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Run standardrb
19
+ uses: standardrb/standard-ruby-action@f533e61f461ccb766b2d9c235abf59be02aea793
20
+ env:
21
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22
+
23
+ permissions:
24
+ checks: write
25
+ contents: read
26
+
10
27
  build:
11
28
  name: Ruby ${{ matrix.ruby }}, Rails ${{ matrix.rails }}
29
+ continue-on-error: ${{ matrix.continue-on-error }}
12
30
 
13
31
  strategy:
14
32
  fail-fast: false
15
33
  matrix:
16
- ruby: ["2.7", "3.0", "3.1"]
17
- rails: ["6.1", "7.0"]
34
+ ruby: ["3.4", "3.3"]
35
+ rails: ["8.0", "7.2"]
18
36
  continue-on-error: [false]
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
19
44
 
20
45
  runs-on: ubuntu-latest
21
46
 
@@ -40,7 +65,7 @@ jobs:
40
65
 
41
66
  steps:
42
67
  - name: Checkout
43
- uses: actions/checkout@v2
68
+ uses: actions/checkout@v4
44
69
 
45
70
  - name: Install Ruby ${{ matrix.ruby }}
46
71
  uses: ruby/setup-ruby@v1
@@ -54,7 +79,7 @@ jobs:
54
79
  run: bundle lock
55
80
 
56
81
  - name: Cache dependencies
57
- uses: actions/cache@v1
82
+ uses: actions/cache@v4
58
83
  with:
59
84
  path: vendor/bundle
60
85
  key: bundle-${{ hashFiles('Gemfile.lock') }}
data/.gitignore CHANGED
@@ -18,3 +18,4 @@ tmp
18
18
  gemfiles/*.lock
19
19
  .DS_Store
20
20
  .ruby-version
21
+ .vscode/
data/CHANGELOG.md CHANGED
@@ -5,19 +5,44 @@ 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
+
24
+ ## [1.8.0] - March 28, 2024
25
+
26
+ [1.8.0]: https://github.com/scenic-views/scenic/compare/v1.7.0...v1.8.0
27
+
28
+ ### Added
29
+
30
+ - Added `#populated?` to check the state of materialized views - _Daisuke
31
+ Fujimura_, _Dr Nic Williams_
32
+
8
33
  ## [1.7.0] - December 8, 2022
9
34
 
10
35
  [1.7.0]: https://github.com/scenic-views/scenic/compare/v1.6.0...v1.7.0
11
36
 
12
37
  ### Added
13
38
 
14
- * Added the `--replace` CLI flag to generate a migration that uses the
15
- `replace_view` schema statement - *Dan Hixon*
39
+ - Added the `--replace` CLI flag to generate a migration that uses the
40
+ `replace_view` schema statement - _Dan Hixon_
16
41
 
17
42
  ### Fixed
18
43
 
19
- * Fixed deprecation notice from newer versions of ERB when using scenic
20
- generators - *Ali Ismayilov*
44
+ - Fixed deprecation notice from newer versions of ERB when using scenic
45
+ generators - _Ali Ismayilov_
21
46
 
22
47
  ## [1.6.0] - February 13, 2022
23
48
 
@@ -25,9 +50,9 @@ changelog, see the [commits] for each version via the version links.
25
50
 
26
51
  ### Fixed
27
52
 
28
- * Exclude pg_stat_statements_info (#349) 76bface - *Caleb Hearth*
29
- * Fix serialization of views with backslashes c625d1b - *Ben Sheldon*
30
- * Handle ActiveRecord table name prefix and suffix b1544dc - *Derek Prior*
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_
31
56
 
32
57
  ## [1.5.5] - December 15, 2021
33
58
 
@@ -100,7 +125,6 @@ changelog, see the [commits] for each version via the version links.
100
125
  - Fixed a cascading refresh issue when the name of the view to trigger the
101
126
  refresh is a substring of one of its dependencies.
102
127
 
103
-
104
128
  [1.5.0]: https://github.com/scenic-views/scenic/compare/v1.4.1...v1.5.0
105
129
 
106
130
  ## [1.4.1] - December 15, 2017
@@ -135,26 +159,30 @@ changelog, see the [commits] for each version via the version links.
135
159
  ## [1.3.0] - May 27, 2016
136
160
 
137
161
  ### Added
162
+
138
163
  - Add `replace_view` migration statement, which issues `CREATE OR REPLACE
139
- VIEW` rather than `CREATE VIEW` or `DROP VIEW` and `CREATE VIEW`.
164
+ VIEW` rather than `CREATE VIEW` or `DROP VIEW` and `CREATE VIEW`.
140
165
  - Schema-qualify views outside the 'public' namespace, such as
141
166
  `scenic.searches`
142
167
 
143
168
  ### Fixed
144
- * Singularize generated model name when injecting into class.
169
+
170
+ - Singularize generated model name when injecting into class.
145
171
  Previously, pluralized names would issue a warning and Scenic would
146
172
  attempt to insert model code into the pluralized model file.
147
- * Convert shell-based smoke tests to RSpec syntax.
173
+ - Convert shell-based smoke tests to RSpec syntax.
148
174
 
149
175
  [1.3.0]: https://github.com/scenic-views/scenic/compare/v1.2.0...v1.3.0
150
176
 
151
177
  ## [1.2.0] - February 5, 2016
152
178
 
153
179
  ### Added
180
+
154
181
  - The generators now accept namespaced view definitions. For example: `rails
155
- generate scenic:view my_app.users`.
182
+ generate scenic:view my_app.users`.
156
183
 
157
184
  ### Fixed
185
+
158
186
  - Materialized view indexes are now properly dumped to `db/schema.rb`. This was
159
187
  an oversight in previous releases, meaning `rake db:schema:load` was missing
160
188
  indexes.
@@ -163,7 +191,7 @@ changelog, see the [commits] for each version via the version links.
163
191
  returning no indexes.
164
192
 
165
193
  **Note**: Dumping materialized view indexes will produce an invalid
166
- `db/schema.rb` file under Rails 5 beta 1 and beta 2. This is fixed on Rails
194
+ `db/schema.rb` file under Rails 5 beta 1 and beta 2. This is fixed on Rails
167
195
  master.
168
196
 
169
197
  [1.2.0]: https://github.com/scenic-views/scenic/compare/v1.1.1...v1.2.0
@@ -171,8 +199,9 @@ master.
171
199
  ## [1.1.1] - January 29, 2016
172
200
 
173
201
  ### Fixed
202
+
174
203
  - Some schema operations were failing with a `PG::ConnectionBad: connection is
175
- closed` error. This has been fixed by ensuring we grab a fresh connection for
204
+ closed` error. This has been fixed by ensuring we grab a fresh connection for
176
205
  all operations.
177
206
 
178
207
  [1.1.1]: https://github.com/scenic-views/scenic/compare/v1.1.0...v1.1.1
@@ -180,12 +209,14 @@ master.
180
209
  ## [1.1.0] - January 8, 2016
181
210
 
182
211
  ### Added
212
+
183
213
  - Added support for updating materialized view definitions while maintaining
184
214
  existing indexes that are still applicable after the update.
185
215
  - Added support for refreshing materialized views concurrently (requires
186
216
  Postgres 9.4 or newer).
187
217
 
188
218
  ### Fixed
219
+
189
220
  - The schema dumper will now dump views and materialized views together in the
190
221
  order they are returned by Postgres. This fixes issues when loading views that
191
222
  depend on other views via `rake db:schema:load`.
@@ -200,26 +231,30 @@ master.
200
231
  ## [1.0.0] - November 23, 2015
201
232
 
202
233
  ### Added
234
+
203
235
  - Added support for [materialized views].
204
236
  - Allow changing the database adapter via `Scenic::Configuration`.
205
237
 
206
238
  ### Fixed
239
+
207
240
  - Improved formatting of the view when dumped to `schema.rb`.
208
241
  - Fixed generation of namespaced models by using ActiveRecord's own model
209
242
  generator.
210
243
  - Eliminated `alias_method_chain` deprecation when running with Rails master
211
244
  (5.0).
212
245
 
213
- [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
214
247
  [1.0.0]: https://github.com/scenic-views/scenic/compare/v0.3.0...v1.0.0
215
248
 
216
249
  ## [0.3.0] - January 23, 2015
217
250
 
218
251
  ### Added
252
+
219
253
  - Previous view definition is copied into new view definition file when updating
220
254
  an existing view.
221
255
 
222
256
  ### Fixed
257
+
223
258
  - We avoid dumping views that belong to Postgres extensions.
224
259
  - `db/schema.rb` is prettier thanks to a blank line after each view definition.
225
260
 
@@ -228,6 +263,7 @@ master.
228
263
  ## [0.2.1] - January 5, 2015
229
264
 
230
265
  ### Fixed
266
+
231
267
  - View generator will now create `db/views` directory if necessary.
232
268
 
233
269
  [0.2.1]: https://github.com/scenic-views/scenic/compare/v0.2.0...v0.2.1
@@ -235,9 +271,11 @@ master.
235
271
  ## [0.2.0] - August 11, 2014
236
272
 
237
273
  ### Added
274
+
238
275
  - Teach view generator to update existing views.
239
276
 
240
277
  ### Fixed
278
+
241
279
  - Raise an error if view definition is empty.
242
280
 
243
281
  [0.2.0]: https://github.com/scenic-views/scenic/compare/v0.1.0...v0.2.0
@@ -247,8 +285,8 @@ master.
247
285
  Scenic makes it easier to work with Postgres views in Rails.
248
286
 
249
287
  It introduces view methods to ActiveRecord::Migration and allows views to be
250
- dumped to db/schema.rb. It provides generators for models, view definitions,
251
- and migrations. It is built around a basic versioning system for view
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
252
290
  definition files.
253
291
 
254
292
  In short, go add a view to your app.
data/CONTRIBUTING.md CHANGED
@@ -13,6 +13,7 @@ agree to abide by our [code of conduct].
13
13
  3. Run `rake` to verify that the tests pass against the version of Rails you are
14
14
  running locally.
15
15
  4. Make your change with new passing tests, following existing style.
16
+ 5. Run `standardrb --fix` to ensure your code is formatted correctly.
16
17
  5. Write a [good commit message], push your fork, and submit a pull request.
17
18
  6. CI will run the test suite on all configured versions of Ruby and Rails.
18
19
  Address any failures.
data/FUNDING.yml ADDED
@@ -0,0 +1 @@
1
+ github: [calebhearth, derekprior]
data/Gemfile CHANGED
@@ -3,12 +3,12 @@ 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.1")
6
+ rails_version = ENV.fetch("RAILS_VERSION", "8.0")
7
7
 
8
- if rails_version == "master"
9
- rails_constraint = { github: "rails/rails" }
8
+ rails_constraint = if rails_version == "main"
9
+ {github: "rails/rails"}
10
10
  else
11
- rails_constraint = "~> #{rails_version}.0"
11
+ "~> #{rails_version}.0"
12
12
  end
13
13
 
14
14
  gem "rails", rails_constraint
data/README.md CHANGED
@@ -4,7 +4,6 @@
4
4
 
5
5
  [![Build Status](https://github.com/scenic-views/scenic/actions/workflows/ci.yml/badge.svg)](https://github.com/scenic-views/scenic/actions/workflows/ci.yml)
6
6
  [![Documentation Quality](http://inch-ci.org/github/scenic-views/scenic.svg?branch=master)](http://inch-ci.org/github/scenic-views/scenic)
7
- [![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
8
7
 
9
8
  Scenic adds methods to `ActiveRecord::Migration` to create and manage database
10
9
  views in Rails.
@@ -25,7 +24,7 @@ Scenic ships with support for PostgreSQL. The adapter is configurable (see
25
24
 
26
25
  If you're using Postgres, Add `gem "scenic"` to your Gemfile and run `bundle
27
26
  install`. If you're using something other than Postgres, check out the available
28
- [third party adapters](https://github.com/scenic-views/scenic#faqs).
27
+ [third-party adapters](https://github.com/scenic-views/scenic#when-will-you-support-mysql-sqlite-or-other-databases).
29
28
 
30
29
  ## Great, how do I create a view?
31
30
 
@@ -86,14 +85,17 @@ schema in the new definition and run the `update_view` migration.
86
85
 
87
86
  ## What if I want to change a view without dropping it?
88
87
 
89
- The `update_view` statement used by default will drop your view then create
90
- a new version of it.
88
+ The `update_view` statement used by default will drop your view then create a
89
+ new version of it. This may not be desirable when you have complicated
90
+ hierarchies of dependent views.
91
91
 
92
- This is not desirable when you have complicated hierarchies of views, especially
93
- when some of those views may be materialized and take a long time to recreate.
92
+ Scenic offers a `replace_view` schema statement, resulting in a `CREATE OR
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, though
95
+ the `side_by_side` update strategy may yield similar results (see below).
94
96
 
95
- You can use `replace_view` to generate a CREATE OR REPLACE VIEW SQL statement
96
- instead by adding the `--replace` option to the generate command:
97
+ You can generate a migration that uses the `replace_view` schema statement by
98
+ passing the `--replace` option to the `scenic:view` generator:
97
99
 
98
100
  ```sh
99
101
  $ rails generate scenic:view search_results --replace
@@ -101,9 +103,6 @@ $ rails generate scenic:view search_results --replace
101
103
  create db/migrate/[TIMESTAMP]_update_search_results_to_version_2.rb
102
104
  ```
103
105
 
104
- See Postgres documentation on how this works:
105
- http://www.postgresql.org/docs/current/static/sql-createview.html
106
-
107
106
  The migration will look something like this:
108
107
 
109
108
  ```ruby
@@ -114,8 +113,6 @@ class UpdateSearchResultsToVersion2 < ActiveRecord::Migration
114
113
  end
115
114
  ```
116
115
 
117
- You can run the migration and the view will be replaced instead.
118
-
119
116
  ## Can I use this view to back a model?
120
117
 
121
118
  You bet! Using view-backed models can help promote concepts hidden in your
@@ -127,6 +124,11 @@ no different than a table.
127
124
  class SearchResult < ApplicationRecord
128
125
  belongs_to :searchable, polymorphic: true
129
126
 
127
+ # If you want to be able to call +Model.find+, you
128
+ # must declare the primary key. It can not be
129
+ # inferred from column information.
130
+ # self.primary_key = :id
131
+
130
132
  # this isn't strictly necessary, but it will prevent
131
133
  # rails from calling save, which would fail anyway.
132
134
  def readonly?
@@ -136,7 +138,7 @@ end
136
138
  ```
137
139
 
138
140
  Scenic even provides a `scenic:model` generator that is a superset of
139
- `scenic:view`. It will act identically to the Rails `model` generator except
141
+ `scenic:view`. It will act identically to the Rails `model` generator except
140
142
  that it will create a Scenic view migration rather than a table migration.
141
143
 
142
144
  There is no special base class or mixin needed. If desired, any code the model
@@ -184,6 +186,44 @@ you would need to refresh view B first, then right after refresh view A. If you
184
186
  would like this cascading refresh of materialized views, set `cascade: true`
185
187
  when you refresh your materialized view.
186
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
+
187
227
  ## I don't need this view anymore. Make it go away.
188
228
 
189
229
  Scenic gives you `drop_view` too:
@@ -197,7 +237,7 @@ end
197
237
 
198
238
  ## FAQs
199
239
 
200
- **Why do I get an error when querying a view-backed model with `find`, `last`, or `first`?**
240
+ ### Why do I get an error when querying a view-backed model with `find`, `last`, or `first`?
201
241
 
202
242
  ActiveRecord's `find` method expects to query based on your model's primary key,
203
243
  but views do not have primary keys. Additionally, the `first` and `last` methods
@@ -212,7 +252,7 @@ class People < ApplicationRecord
212
252
  end
213
253
  ```
214
254
 
215
- **Why is my view missing columns from the underlying table?**
255
+ ### Why is my view missing columns from the underlying table?
216
256
 
217
257
  Did you create the view with `SELECT [table_name].*`? Most (possibly all)
218
258
  relational databases freeze the view definition at the time of creation. New
@@ -225,7 +265,7 @@ add_column :posts, :title, :string
225
265
  update_view :posts_with_aggregate_data, version: 2, revert_to_version: 2
226
266
  ```
227
267
 
228
- **When will you support MySQL, SQLite, or other databases?**
268
+ ### When will you support MySQL, SQLite, or other databases?
229
269
 
230
270
  We have no plans to add first-party adapters for other relational databases at
231
271
  this time because we (the maintainers) do not currently have a use for them.
@@ -233,7 +273,7 @@ It's our experience that maintaining a library effectively requires regular use
233
273
  of its features. We're not in a good position to support MySQL, SQLite or other
234
274
  database users.
235
275
 
236
- Scenic *does* support configuring different database adapters and should be
276
+ Scenic _does_ support configuring different database adapters and should be
237
277
  extendable with adapter libraries. If you implement such an adapter, we're happy
238
278
  to review and link to it. We're also happy to make changes that would better
239
279
  accommodate adapter gems.
@@ -241,20 +281,39 @@ accommodate adapter gems.
241
281
  We are aware of the following existing adapter libraries for Scenic which may
242
282
  meet your needs:
243
283
 
244
- * [`scenic_sqlite_adapter`](<https://github.com/pdebelak/scenic_sqlite_adapter>)
245
- * [`scenic-mysql_adapter`](<https://github.com/EmpaticoOrg/scenic-mysql_adapter>)
246
- * [`scenic-sqlserver-adapter`](<https://github.com/ClickMechanic/scenic_sqlserver_adapter>)
247
- * [`scenic-oracle_adapter`](<https://github.com/cdinger/scenic-oracle_adapter>)
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)
248
288
 
249
289
  Please note that the maintainers of Scenic make no assertions about the
250
290
  quality or security of the above adapters.
251
291
 
252
292
  ## About
253
293
 
294
+ ### Used By
295
+
254
296
  Scenic is used by some popular open source Rails apps:
255
- [Mastodon](<https://github.com/mastodon/mastodon/>),
256
- [Code.org](<https://github.com/code-dot-org/code-dot-org>), and
257
- [Lobste.rs](<https://github.com/lobsters/lobsters/>).
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
304
+ triggers for Rails
305
+
306
+ ### Media
307
+
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
258
317
 
259
318
  Scenic is maintained by [Derek Prior], [Caleb Hearth], and you, our
260
319
  contributors.
data/Rakefile CHANGED
@@ -26,4 +26,4 @@ RSpec::Core::RakeTask.new("spec:acceptance") do |task|
26
26
  end
27
27
 
28
28
  desc "Run the specs and acceptance tests"
29
- task default: %w(spec spec:acceptance)
29
+ task default: %w[spec spec:acceptance]
data/bin/standardrb ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'standardrb' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("standard", "standardrb")
@@ -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
@@ -15,7 +15,7 @@ module Scenic
15
15
  [file_path.singularize],
16
16
  options.merge(
17
17
  fixture_replacement: false,
18
- migration: false,
18
+ migration: false
19
19
  )
20
20
  end
21
21
 
@@ -34,23 +34,14 @@ module Scenic
34
34
  private
35
35
 
36
36
  def evaluate_template(source)
37
- source = File.expand_path(find_in_source_paths(source.to_s))
37
+ source = File.expand_path(find_in_source_paths(source.to_s))
38
38
  context = instance_eval("binding", __FILE__, __LINE__)
39
39
 
40
- if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+
41
- erb = ERB.new(
42
- ::File.binread(source),
43
- trim_mode: "-",
44
- eoutvar: "@output_buffer",
45
- )
46
- else
47
- erb = ERB.new(
48
- ::File.binread(source),
49
- nil,
50
- "-",
51
- "@output_buffer",
52
- )
53
- end
40
+ erb = ERB.new(
41
+ ::File.binread(source),
42
+ trim_mode: "-",
43
+ eoutvar: "@output_buffer"
44
+ )
54
45
 
55
46
  erb.result(context)
56
47
  end
@@ -1,3 +1,7 @@
1
1
  def self.refresh
2
2
  Scenic.database.refresh_materialized_view(table_name, concurrently: false, cascade: false)
3
3
  end
4
+
5
+ def self.populated?
6
+ Scenic.database.populated?(table_name)
7
+ end
@@ -1,11 +1,11 @@
1
1
  class <%= migration_class_name %> < <%= activerecord_migration_class %>
2
2
  def change
3
- <% method_name = replace_view? ? 'replace_view' : 'update_view' %>
3
+ <%- method_name = replace_view? ? 'replace_view' : 'update_view' -%>
4
4
  <%- if materialized? -%>
5
5
  <%= method_name %> <%= formatted_plural_name %>,
6
6
  version: <%= version %>,
7
7
  revert_to_version: <%= previous_version %>,
8
- materialized: <%= no_data? ? "{ no_data: true }" : true %>
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 -%>