scenic 1.4.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.devcontainer/Dockerfile +6 -0
- data/.devcontainer/devcontainer.json +11 -0
- data/.devcontainer/docker-compose.yml +24 -0
- data/.github/workflows/ci.yml +71 -0
- data/.gitignore +2 -0
- data/.hound.yml +2 -4
- data/.rubocop.yml +129 -0
- data/{NEWS.md → CHANGELOG.md} +122 -13
- data/CODE_OF_CONDUCT.md +76 -0
- data/CONTRIBUTING.md +7 -9
- data/Gemfile +13 -1
- data/LICENSE.txt +1 -1
- data/README.md +44 -42
- data/Rakefile +2 -2
- data/SECURITY.md +14 -0
- data/bin/setup +9 -4
- data/lib/generators/scenic/materializable.rb +18 -0
- data/lib/generators/scenic/model/model_generator.rb +19 -8
- data/lib/generators/scenic/view/USAGE +1 -0
- data/lib/generators/scenic/view/templates/db/migrate/create_view.erb +1 -1
- data/lib/generators/scenic/view/templates/db/migrate/update_view.erb +4 -3
- data/lib/generators/scenic/view/view_generator.rb +19 -9
- data/lib/scenic/adapters/postgres/index_reapplication.rb +1 -1
- data/lib/scenic/adapters/postgres/refresh_dependencies.rb +21 -7
- data/lib/scenic/adapters/postgres/views.rb +11 -1
- data/lib/scenic/adapters/postgres.rb +20 -7
- data/lib/scenic/command_recorder/statement_arguments.rb +20 -2
- data/lib/scenic/command_recorder.rb +6 -1
- data/lib/scenic/configuration.rb +1 -1
- data/lib/scenic/definition.rb +4 -2
- data/lib/scenic/schema_dumper.rb +2 -2
- data/lib/scenic/statements.rb +24 -6
- data/lib/scenic/unaffixed_name.rb +31 -0
- data/lib/scenic/version.rb +1 -1
- data/lib/scenic/view.rb +7 -4
- data/lib/scenic.rb +1 -0
- data/scenic.gemspec +21 -23
- data/spec/acceptance/user_manages_views_spec.rb +2 -1
- data/spec/dummy/app/models/application_record.rb +5 -0
- data/spec/dummy/config/database.yml +5 -0
- data/spec/dummy/db/migrate/20220112154220_add_pg_stat_statements_extension.rb +5 -0
- data/spec/dummy/db/schema.rb +19 -0
- data/spec/generators/scenic/model/model_generator_spec.rb +1 -1
- data/spec/generators/scenic/view/view_generator_spec.rb +22 -4
- data/spec/scenic/adapters/postgres/refresh_dependencies_spec.rb +66 -26
- data/spec/scenic/adapters/postgres_spec.rb +23 -3
- data/spec/scenic/command_recorder_spec.rb +15 -1
- data/spec/scenic/definition_spec.rb +15 -1
- data/spec/scenic/schema_dumper_spec.rb +48 -3
- data/spec/scenic/statements_spec.rb +49 -14
- data/spec/spec_helper.rb +5 -1
- data/spec/support/generator_spec_setup.rb +1 -1
- data/spec/support/rails_configuration_helpers.rb +10 -0
- metadata +32 -41
- data/.travis.yml +0 -44
- data/Appraisals +0 -33
- data/bin/appraisal +0 -16
- data/gemfiles/rails40.gemfile +0 -8
- data/gemfiles/rails41.gemfile +0 -8
- data/gemfiles/rails42.gemfile +0 -8
- data/gemfiles/rails42_with_fg_rails.gemfile +0 -10
- data/gemfiles/rails50.gemfile +0 -8
- data/gemfiles/rails51.gemfile +0 -8
- data/gemfiles/rails_edge.gemfile +0 -8
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# Scenic
|
2
2
|
|
3
|
-
![Scenic Landscape](https://images.
|
3
|
+
![Scenic Landscape](https://user-images.githubusercontent.com/152152/49344534-a8817480-f646-11e8-8431-3d95d349c070.png)
|
4
4
|
|
5
|
-
[![Build Status](https://
|
6
|
-
[![
|
7
|
-
[![
|
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
|
+
[![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
8
|
|
9
9
|
Scenic adds methods to `ActiveRecord::Migration` to create and manage database
|
10
10
|
views in Rails.
|
@@ -21,6 +21,12 @@ Scenic ships with support for PostgreSQL. The adapter is configurable (see
|
|
21
21
|
`Scenic::Configuration`) and has a minimal interface (see
|
22
22
|
`Scenic::Adapters::Postgres`) that other gems can provide.
|
23
23
|
|
24
|
+
## So how do I install this?
|
25
|
+
|
26
|
+
If you're using Postgres, Add `gem "scenic"` to your Gemfile and run `bundle
|
27
|
+
install`. If you're using something other than Postgres, check out the available
|
28
|
+
[third party adapters](https://github.com/scenic-views/scenic#faqs).
|
29
|
+
|
24
30
|
## Great, how do I create a view?
|
25
31
|
|
26
32
|
You've got this great idea for a view you'd like to call `search_results`. You
|
@@ -86,30 +92,19 @@ a new version of it.
|
|
86
92
|
This is not desirable when you have complicated hierarchies of views, especially
|
87
93
|
when some of those views may be materialized and take a long time to recreate.
|
88
94
|
|
89
|
-
You can use `replace_view` to generate a CREATE OR REPLACE VIEW SQL statement
|
90
|
-
|
91
|
-
See postgresql documentation on how this works:
|
92
|
-
http://www.postgresql.org/docs/current/static/sql-createview.html
|
93
|
-
|
94
|
-
To start replacing a view run the generator like for a regular change:
|
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:
|
95
97
|
|
96
98
|
```sh
|
97
|
-
$ rails generate scenic:view search_results
|
99
|
+
$ rails generate scenic:view search_results --replace
|
98
100
|
create db/views/search_results_v02.sql
|
99
101
|
create db/migrate/[TIMESTAMP]_update_search_results_to_version_2.rb
|
100
102
|
```
|
101
103
|
|
102
|
-
|
103
|
-
|
104
|
-
```ruby
|
105
|
-
class UpdateSearchResultsToVersion2 < ActiveRecord::Migration
|
106
|
-
def change
|
107
|
-
update_view :search_results, version: 2, revert_to_version: 1
|
108
|
-
end
|
109
|
-
end
|
110
|
-
```
|
104
|
+
See Postgres documentation on how this works:
|
105
|
+
http://www.postgresql.org/docs/current/static/sql-createview.html
|
111
106
|
|
112
|
-
|
107
|
+
The migration will look something like this:
|
113
108
|
|
114
109
|
```ruby
|
115
110
|
class UpdateSearchResultsToVersion2 < ActiveRecord::Migration
|
@@ -119,7 +114,7 @@ class UpdateSearchResultsToVersion2 < ActiveRecord::Migration
|
|
119
114
|
end
|
120
115
|
```
|
121
116
|
|
122
|
-
|
117
|
+
You can run the migration and the view will be replaced instead.
|
123
118
|
|
124
119
|
## Can I use this view to back a model?
|
125
120
|
|
@@ -129,11 +124,9 @@ ActiveRecord or ARel queries. As far as ActiveRecord is concerned, a view is
|
|
129
124
|
no different than a table.
|
130
125
|
|
131
126
|
```ruby
|
132
|
-
class SearchResult <
|
127
|
+
class SearchResult < ApplicationRecord
|
133
128
|
belongs_to :searchable, polymorphic: true
|
134
129
|
|
135
|
-
private
|
136
|
-
|
137
130
|
# this isn't strictly necessary, but it will prevent
|
138
131
|
# rails from calling save, which would fail anyway.
|
139
132
|
def readonly?
|
@@ -198,6 +191,7 @@ Scenic gives you `drop_view` too:
|
|
198
191
|
```ruby
|
199
192
|
def change
|
200
193
|
drop_view :search_results, revert_to_version: 2
|
194
|
+
drop_view :materialized_admin_reports, revert_to_version: 3, materialized: true
|
201
195
|
end
|
202
196
|
```
|
203
197
|
|
@@ -213,7 +207,7 @@ You can get around these issues by setting the primary key column on your Rails
|
|
213
207
|
model like so:
|
214
208
|
|
215
209
|
```ruby
|
216
|
-
class People <
|
210
|
+
class People < ApplicationRecord
|
217
211
|
self.primary_key = :my_unique_identifier_field
|
218
212
|
end
|
219
213
|
```
|
@@ -231,31 +225,39 @@ add_column :posts, :title, :string
|
|
231
225
|
update_view :posts_with_aggregate_data, version: 2, revert_to_version: 2
|
232
226
|
```
|
233
227
|
|
234
|
-
**When will you support MySQL?**
|
228
|
+
**When will you support MySQL, SQLite, or other databases?**
|
235
229
|
|
236
|
-
We have no plans to add first-party
|
237
|
-
(the maintainers) do not currently have a use for
|
238
|
-
maintaining a library effectively requires regular use
|
239
|
-
not in a good position to support MySQL
|
230
|
+
We have no plans to add first-party adapters for other relational databases at
|
231
|
+
this time because we (the maintainers) do not currently have a use for them.
|
232
|
+
It's our experience that maintaining a library effectively requires regular use
|
233
|
+
of its features. We're not in a good position to support MySQL, SQLite or other
|
234
|
+
database users.
|
240
235
|
|
241
236
|
Scenic *does* support configuring different database adapters and should be
|
242
237
|
extendable with adapter libraries. If you implement such an adapter, we're happy
|
243
238
|
to review and link to it. We're also happy to make changes that would better
|
244
239
|
accommodate adapter gems.
|
245
240
|
|
246
|
-
|
241
|
+
We are aware of the following existing adapter libraries for Scenic which may
|
242
|
+
meet your needs:
|
247
243
|
|
248
|
-
|
249
|
-
|
250
|
-
|
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>)
|
251
248
|
|
252
|
-
|
253
|
-
|
249
|
+
Please note that the maintainers of Scenic make no assertions about the
|
250
|
+
quality or security of the above adapters.
|
254
251
|
|
255
|
-
|
252
|
+
## About
|
256
253
|
|
257
|
-
|
258
|
-
|
254
|
+
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/>).
|
259
258
|
|
260
|
-
[
|
261
|
-
|
259
|
+
Scenic is maintained by [Derek Prior], [Caleb Hearth], and you, our
|
260
|
+
contributors.
|
261
|
+
|
262
|
+
[Derek Prior]: http://prioritized.net
|
263
|
+
[Caleb Hearth]: http://calebhearth.com
|
data/Rakefile
CHANGED
data/SECURITY.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Security Policy
|
2
|
+
|
3
|
+
## Supported Versions
|
4
|
+
|
5
|
+
Scenic maintainenance is a volunteer effort. We will do our best to fix
|
6
|
+
forward but do not offer backported fixes. As such, the only "supported" version of Scenic is whichever was most recently released.
|
7
|
+
|
8
|
+
## Reporting a Vulnerability
|
9
|
+
|
10
|
+
Please report any discovered security vulnerabilities to Scenic's primary
|
11
|
+
volunteer maintainers, derekprior@gmail.com and caleb@calebhearth.com.
|
12
|
+
|
13
|
+
We will respond as soon as possible with any follow-up questions or details
|
14
|
+
on how we plan to handle the issue.
|
data/bin/setup
CHANGED
@@ -2,12 +2,17 @@
|
|
2
2
|
|
3
3
|
set -e
|
4
4
|
|
5
|
+
# CI-specific setup
|
6
|
+
if [ -n "$GITHUB_ACTIONS" ]; then
|
7
|
+
bundle config path vendor/bundle
|
8
|
+
bundle config jobs 4
|
9
|
+
bundle config retry 3
|
10
|
+
git config --global user.name 'GitHub Actions'
|
11
|
+
git config --global user.email 'github-actions@example.com'
|
12
|
+
fi
|
13
|
+
|
5
14
|
gem install bundler --conservative
|
6
15
|
bundle check || bundle install
|
7
16
|
|
8
|
-
if [ -z "$CI" ]; then
|
9
|
-
bundle exec appraisal install
|
10
|
-
fi
|
11
|
-
|
12
17
|
bundle exec rake dummy:db:drop
|
13
18
|
bundle exec rake dummy:db:create
|
@@ -10,6 +10,16 @@ module Scenic
|
|
10
10
|
required: false,
|
11
11
|
desc: "Makes the view materialized",
|
12
12
|
default: false
|
13
|
+
class_option :no_data,
|
14
|
+
type: :boolean,
|
15
|
+
required: false,
|
16
|
+
desc: "Adds WITH NO DATA when materialized view creates/updates",
|
17
|
+
default: false
|
18
|
+
class_option :replace,
|
19
|
+
type: :boolean,
|
20
|
+
required: false,
|
21
|
+
desc: "Uses replace_view instead of update_view",
|
22
|
+
default: false
|
13
23
|
end
|
14
24
|
|
15
25
|
private
|
@@ -17,6 +27,14 @@ module Scenic
|
|
17
27
|
def materialized?
|
18
28
|
options[:materialized]
|
19
29
|
end
|
30
|
+
|
31
|
+
def replace_view?
|
32
|
+
options[:replace]
|
33
|
+
end
|
34
|
+
|
35
|
+
def no_data?
|
36
|
+
options[:no_data]
|
37
|
+
end
|
20
38
|
end
|
21
39
|
end
|
22
40
|
end
|
@@ -8,7 +8,7 @@ module Scenic
|
|
8
8
|
# @api private
|
9
9
|
class ModelGenerator < Rails::Generators::NamedBase
|
10
10
|
include Scenic::Generators::Materializable
|
11
|
-
source_root File.expand_path("
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
12
12
|
|
13
13
|
def invoke_rails_model_generator
|
14
14
|
invoke "model",
|
@@ -35,13 +35,24 @@ module Scenic
|
|
35
35
|
|
36
36
|
def evaluate_template(source)
|
37
37
|
source = File.expand_path(find_in_source_paths(source.to_s))
|
38
|
-
context = instance_eval("binding")
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
38
|
+
context = instance_eval("binding", __FILE__, __LINE__)
|
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
|
54
|
+
|
55
|
+
erb.result(context)
|
45
56
|
end
|
46
57
|
|
47
58
|
def generating?
|
@@ -1,12 +1,13 @@
|
|
1
1
|
class <%= migration_class_name %> < <%= activerecord_migration_class %>
|
2
2
|
def change
|
3
|
+
<% method_name = replace_view? ? 'replace_view' : 'update_view' %>
|
3
4
|
<%- if materialized? -%>
|
4
|
-
|
5
|
+
<%= method_name %> <%= formatted_plural_name %>,
|
5
6
|
version: <%= version %>,
|
6
7
|
revert_to_version: <%= previous_version %>,
|
7
|
-
materialized: true
|
8
|
+
materialized: <%= no_data? ? "{ no_data: true }" : true %>
|
8
9
|
<%- else -%>
|
9
|
-
|
10
|
+
<%= method_name %> <%= formatted_plural_name %>, version: <%= version %>, revert_to_version: <%= previous_version %>
|
10
11
|
<%- end -%>
|
11
12
|
end
|
12
13
|
end
|
@@ -8,7 +8,7 @@ module Scenic
|
|
8
8
|
class ViewGenerator < Rails::Generators::NamedBase
|
9
9
|
include Rails::Generators::Migration
|
10
10
|
include Scenic::Generators::Materializable
|
11
|
-
source_root File.expand_path("
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
12
12
|
|
13
13
|
def create_views_directory
|
14
14
|
unless views_directory_path.exist?
|
@@ -56,7 +56,7 @@ module Scenic
|
|
56
56
|
|
57
57
|
def migration_class_name
|
58
58
|
if creating_new_view?
|
59
|
-
"Create#{class_name.
|
59
|
+
"Create#{class_name.tr('.', '').pluralize}"
|
60
60
|
else
|
61
61
|
"Update#{class_name.pluralize}ToVersion#{version}"
|
62
62
|
end
|
@@ -64,7 +64,7 @@ module Scenic
|
|
64
64
|
|
65
65
|
def activerecord_migration_class
|
66
66
|
if ActiveRecord::Migration.respond_to?(:current_version)
|
67
|
-
"ActiveRecord::Migration[
|
67
|
+
"ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]"
|
68
68
|
else
|
69
69
|
"ActiveRecord::Migration"
|
70
70
|
end
|
@@ -73,8 +73,14 @@ module Scenic
|
|
73
73
|
|
74
74
|
private
|
75
75
|
|
76
|
+
alias singular_name file_name
|
77
|
+
|
78
|
+
def file_name
|
79
|
+
super.tr(".", "_")
|
80
|
+
end
|
81
|
+
|
76
82
|
def views_directory_path
|
77
|
-
@views_directory_path ||= Rails.root.join(
|
83
|
+
@views_directory_path ||= Rails.root.join("db", "views")
|
78
84
|
end
|
79
85
|
|
80
86
|
def version_regex
|
@@ -82,7 +88,7 @@ module Scenic
|
|
82
88
|
end
|
83
89
|
|
84
90
|
def creating_new_view?
|
85
|
-
previous_version
|
91
|
+
previous_version.zero?
|
86
92
|
end
|
87
93
|
|
88
94
|
def definition
|
@@ -93,10 +99,6 @@ module Scenic
|
|
93
99
|
Scenic::Definition.new(plural_file_name, previous_version)
|
94
100
|
end
|
95
101
|
|
96
|
-
def plural_file_name
|
97
|
-
@plural_file_name ||= file_name.pluralize.gsub(".", "_")
|
98
|
-
end
|
99
|
-
|
100
102
|
def destroying?
|
101
103
|
behavior == :revoke
|
102
104
|
end
|
@@ -109,6 +111,14 @@ module Scenic
|
|
109
111
|
end
|
110
112
|
end
|
111
113
|
|
114
|
+
def create_view_options
|
115
|
+
if materialized?
|
116
|
+
", materialized: #{no_data? ? '{ no_data: true }' : true}"
|
117
|
+
else
|
118
|
+
""
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
112
122
|
def destroying_initial_view?
|
113
123
|
destroying? && version == 1
|
114
124
|
end
|
@@ -13,7 +13,7 @@ module Scenic
|
|
13
13
|
# @param connection [Connection] The connection to execute SQL against.
|
14
14
|
# @param speaker [#say] (ActiveRecord::Migration) The object used for
|
15
15
|
# logging the results of reapplying indexes.
|
16
|
-
def initialize(connection:, speaker: ActiveRecord::Migration)
|
16
|
+
def initialize(connection:, speaker: ActiveRecord::Migration.new)
|
17
17
|
@connection = connection
|
18
18
|
@speaker = speaker
|
19
19
|
end
|
@@ -2,25 +2,29 @@ module Scenic
|
|
2
2
|
module Adapters
|
3
3
|
class Postgres
|
4
4
|
class RefreshDependencies
|
5
|
-
def self.call(name, adapter, connection)
|
6
|
-
new(name, adapter, connection).call
|
5
|
+
def self.call(name, adapter, connection, concurrently: false)
|
6
|
+
new(name, adapter, connection, concurrently: concurrently).call
|
7
7
|
end
|
8
8
|
|
9
|
-
def initialize(name, adapter, connection)
|
9
|
+
def initialize(name, adapter, connection, concurrently:)
|
10
10
|
@name = name
|
11
11
|
@adapter = adapter
|
12
12
|
@connection = connection
|
13
|
+
@concurrently = concurrently
|
13
14
|
end
|
14
15
|
|
15
16
|
def call
|
16
17
|
dependencies.each do |dependency|
|
17
|
-
adapter.refresh_materialized_view(
|
18
|
+
adapter.refresh_materialized_view(
|
19
|
+
dependency,
|
20
|
+
concurrently: concurrently,
|
21
|
+
)
|
18
22
|
end
|
19
23
|
end
|
20
24
|
|
21
25
|
private
|
22
26
|
|
23
|
-
attr_reader :name, :adapter, :connection
|
27
|
+
attr_reader :name, :adapter, :connection, :concurrently
|
24
28
|
|
25
29
|
class DependencyParser
|
26
30
|
def initialize(raw_dependencies, view_to_refresh)
|
@@ -46,10 +50,20 @@ module Scenic
|
|
46
50
|
def to_sorted_array
|
47
51
|
dependency_hash = parse_to_hash(raw_dependencies)
|
48
52
|
sorted_arr = tsort(dependency_hash)
|
53
|
+
|
49
54
|
idx = sorted_arr.find_index do |dep|
|
50
|
-
|
55
|
+
if view_to_refresh.to_s.include?(".")
|
56
|
+
dep == view_to_refresh.to_s
|
57
|
+
else
|
58
|
+
dep.ends_with?(".#{view_to_refresh}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if idx.present?
|
63
|
+
sorted_arr[0...idx]
|
64
|
+
else
|
65
|
+
[]
|
51
66
|
end
|
52
|
-
sorted_arr[0...idx]
|
53
67
|
end
|
54
68
|
|
55
69
|
private
|
@@ -34,6 +34,7 @@ module Scenic
|
|
34
34
|
WHERE
|
35
35
|
c.relkind IN ('m', 'v')
|
36
36
|
AND c.relname NOT IN (SELECT extname FROM pg_extension)
|
37
|
+
AND c.relname != 'pg_stat_statements_info'
|
37
38
|
AND n.nspname = ANY (current_schemas(false))
|
38
39
|
ORDER BY c.oid
|
39
40
|
SQL
|
@@ -57,7 +58,16 @@ module Scenic
|
|
57
58
|
|
58
59
|
def pg_identifier(name)
|
59
60
|
return name if name =~ /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
60
|
-
|
61
|
+
|
62
|
+
pgconn.quote_ident(name)
|
63
|
+
end
|
64
|
+
|
65
|
+
def pgconn
|
66
|
+
if defined?(PG::Connection)
|
67
|
+
PG::Connection
|
68
|
+
else
|
69
|
+
PGconn
|
70
|
+
end
|
61
71
|
end
|
62
72
|
end
|
63
73
|
end
|
@@ -33,7 +33,7 @@ module Scenic
|
|
33
33
|
#
|
34
34
|
# @example
|
35
35
|
# Scenic.configure do |config|
|
36
|
-
# config.
|
36
|
+
# config.database = Scenic::Adapters::Postgres.new
|
37
37
|
# end
|
38
38
|
def initialize(connectable = ActiveRecord::Base)
|
39
39
|
@connectable = connectable
|
@@ -122,6 +122,9 @@ module Scenic
|
|
122
122
|
#
|
123
123
|
# @param name The name of the materialized view to create
|
124
124
|
# @param sql_definition The SQL schema that defines the materialized view.
|
125
|
+
# @param no_data [Boolean] Default: false. Set to true to create
|
126
|
+
# materialized view without running the associated query. You will need
|
127
|
+
# to perform a non-concurrent refresh to populate with data.
|
125
128
|
#
|
126
129
|
# This is typically called in a migration via {Statements#create_view}.
|
127
130
|
#
|
@@ -129,9 +132,14 @@ module Scenic
|
|
129
132
|
# in use does not support materialized views.
|
130
133
|
#
|
131
134
|
# @return [void]
|
132
|
-
def create_materialized_view(name, sql_definition)
|
135
|
+
def create_materialized_view(name, sql_definition, no_data: false)
|
133
136
|
raise_unless_materialized_views_supported
|
134
|
-
|
137
|
+
|
138
|
+
execute <<-SQL
|
139
|
+
CREATE MATERIALIZED VIEW #{quote_table_name(name)} AS
|
140
|
+
#{sql_definition.rstrip.chomp(';')}
|
141
|
+
#{'WITH NO DATA' if no_data};
|
142
|
+
SQL
|
135
143
|
end
|
136
144
|
|
137
145
|
# Updates a materialized view in the database.
|
@@ -144,17 +152,20 @@ module Scenic
|
|
144
152
|
#
|
145
153
|
# @param name The name of the view to update
|
146
154
|
# @param sql_definition The SQL schema for the updated view.
|
155
|
+
# @param no_data [Boolean] Default: false. Set to true to create
|
156
|
+
# materialized view without running the associated query. You will need
|
157
|
+
# to perform a non-concurrent refresh to populate with data.
|
147
158
|
#
|
148
159
|
# @raise [MaterializedViewsNotSupportedError] if the version of Postgres
|
149
160
|
# in use does not support materialized views.
|
150
161
|
#
|
151
162
|
# @return [void]
|
152
|
-
def update_materialized_view(name, sql_definition)
|
163
|
+
def update_materialized_view(name, sql_definition, no_data: false)
|
153
164
|
raise_unless_materialized_views_supported
|
154
165
|
|
155
166
|
IndexReapplication.new(connection: connection).on(name) do
|
156
167
|
drop_materialized_view(name)
|
157
|
-
create_materialized_view(name, sql_definition)
|
168
|
+
create_materialized_view(name, sql_definition, no_data: no_data)
|
158
169
|
end
|
159
170
|
end
|
160
171
|
|
@@ -198,8 +209,9 @@ module Scenic
|
|
198
209
|
# @return [void]
|
199
210
|
def refresh_materialized_view(name, concurrently: false, cascade: false)
|
200
211
|
raise_unless_materialized_views_supported
|
212
|
+
|
201
213
|
if cascade
|
202
|
-
refresh_dependencies_for(name)
|
214
|
+
refresh_dependencies_for(name, concurrently: concurrently)
|
203
215
|
end
|
204
216
|
|
205
217
|
if concurrently
|
@@ -231,11 +243,12 @@ module Scenic
|
|
231
243
|
end
|
232
244
|
end
|
233
245
|
|
234
|
-
def refresh_dependencies_for(name)
|
246
|
+
def refresh_dependencies_for(name, concurrently: false)
|
235
247
|
Scenic::Adapters::Postgres::RefreshDependencies.call(
|
236
248
|
name,
|
237
249
|
self,
|
238
250
|
connection,
|
251
|
+
concurrently: concurrently,
|
239
252
|
)
|
240
253
|
end
|
241
254
|
end
|
@@ -22,8 +22,12 @@ module Scenic
|
|
22
22
|
StatementArguments.new([view, options_for_revert])
|
23
23
|
end
|
24
24
|
|
25
|
+
def remove_version
|
26
|
+
StatementArguments.new([view, options_without_version])
|
27
|
+
end
|
28
|
+
|
25
29
|
def to_a
|
26
|
-
@args.to_a
|
30
|
+
@args.to_a.dup.delete_if(&:empty?)
|
27
31
|
end
|
28
32
|
|
29
33
|
private
|
@@ -32,11 +36,25 @@ module Scenic
|
|
32
36
|
@options ||= @args[1] || {}
|
33
37
|
end
|
34
38
|
|
39
|
+
def keyword_hash(hash)
|
40
|
+
if Hash.respond_to? :ruby2_keywords_hash
|
41
|
+
Hash.ruby2_keywords_hash(hash)
|
42
|
+
else
|
43
|
+
hash
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
35
47
|
def options_for_revert
|
36
|
-
options.clone.tap do |revert_options|
|
48
|
+
opts = options.clone.tap do |revert_options|
|
37
49
|
revert_options[:version] = revert_to_version
|
38
50
|
revert_options.delete(:revert_to_version)
|
39
51
|
end
|
52
|
+
|
53
|
+
keyword_hash(opts)
|
54
|
+
end
|
55
|
+
|
56
|
+
def options_without_version
|
57
|
+
keyword_hash(options.except(:version))
|
40
58
|
end
|
41
59
|
end
|
42
60
|
end
|
@@ -6,21 +6,26 @@ module Scenic
|
|
6
6
|
def create_view(*args)
|
7
7
|
record(:create_view, args)
|
8
8
|
end
|
9
|
+
ruby2_keywords :create_view if respond_to?(:ruby2_keywords, true)
|
9
10
|
|
10
11
|
def drop_view(*args)
|
11
12
|
record(:drop_view, args)
|
12
13
|
end
|
14
|
+
ruby2_keywords :drop_view if respond_to?(:ruby2_keywords, true)
|
13
15
|
|
14
16
|
def update_view(*args)
|
15
17
|
record(:update_view, args)
|
16
18
|
end
|
19
|
+
ruby2_keywords :update_view if respond_to?(:ruby2_keywords, true)
|
17
20
|
|
18
21
|
def replace_view(*args)
|
19
22
|
record(:replace_view, args)
|
20
23
|
end
|
24
|
+
ruby2_keywords :replace_view if respond_to?(:ruby2_keywords, true)
|
21
25
|
|
22
26
|
def invert_create_view(args)
|
23
|
-
|
27
|
+
drop_view_args = StatementArguments.new(args).remove_version.to_a
|
28
|
+
[:drop_view, drop_view_args]
|
24
29
|
end
|
25
30
|
|
26
31
|
def invert_drop_view(args)
|
data/lib/scenic/configuration.rb
CHANGED
data/lib/scenic/definition.rb
CHANGED
@@ -2,7 +2,7 @@ module Scenic
|
|
2
2
|
# @api private
|
3
3
|
class Definition
|
4
4
|
def initialize(name, version)
|
5
|
-
@name = name
|
5
|
+
@name = name.to_s
|
6
6
|
@version = version.to_i
|
7
7
|
end
|
8
8
|
|
@@ -28,8 +28,10 @@ module Scenic
|
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
+
attr_reader :name
|
32
|
+
|
31
33
|
def filename
|
32
|
-
"#{
|
34
|
+
"#{UnaffixedName.for(name).tr('.', '_')}_v#{version}.sql"
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|