scenic 1.4.1 → 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 +1 -0
- data/.hound.yml +2 -4
- data/.rubocop.yml +129 -0
- data/{NEWS.md → CHANGELOG.md} +113 -15
- data/CODE_OF_CONDUCT.md +76 -0
- data/CONTRIBUTING.md +7 -9
- data/Gemfile +13 -1
- data/LICENSE.txt +1 -1
- data/README.md +34 -38
- 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 +18 -8
- 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 +19 -6
- 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 -40
- 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/rails50.gemfile +0 -8
- data/gemfiles/rails51.gemfile +0 -8
- data/gemfiles/rails_edge.gemfile +0 -8
data/lib/scenic/statements.rb
CHANGED
@@ -9,8 +9,9 @@ module Scenic
|
|
9
9
|
# @param sql_definition [String] The SQL query for the view schema. An error
|
10
10
|
# will be raised if `sql_definition` and `version` are both set,
|
11
11
|
# as they are mutually exclusive.
|
12
|
-
# @param materialized [Boolean] Set to true to create a materialized
|
13
|
-
#
|
12
|
+
# @param materialized [Boolean, Hash] Set to true to create a materialized
|
13
|
+
# view. Set to { no_data: true } to create materialized view without
|
14
|
+
# loading data. Defaults to false.
|
14
15
|
# @return The database response from executing the create statement.
|
15
16
|
#
|
16
17
|
# @example Create from `db/views/searches_v02.sql`
|
@@ -36,7 +37,11 @@ module Scenic
|
|
36
37
|
sql_definition ||= definition(name, version)
|
37
38
|
|
38
39
|
if materialized
|
39
|
-
Scenic.database.create_materialized_view(
|
40
|
+
Scenic.database.create_materialized_view(
|
41
|
+
name,
|
42
|
+
sql_definition,
|
43
|
+
no_data: no_data(materialized),
|
44
|
+
)
|
40
45
|
else
|
41
46
|
Scenic.database.create_view(name, sql_definition)
|
42
47
|
end
|
@@ -75,8 +80,9 @@ module Scenic
|
|
75
80
|
# as they are mutually exclusive.
|
76
81
|
# @param revert_to_version [Fixnum] The version number to rollback to on
|
77
82
|
# `rake db rollback`
|
78
|
-
# @param materialized [Boolean] True if updating a materialized view.
|
79
|
-
#
|
83
|
+
# @param materialized [Boolean, Hash] True if updating a materialized view.
|
84
|
+
# Set to { no_data: true } to update materialized view without loading
|
85
|
+
# data. Defaults to false.
|
80
86
|
# @return The database response from executing the create statement.
|
81
87
|
#
|
82
88
|
# @example
|
@@ -100,7 +106,11 @@ module Scenic
|
|
100
106
|
sql_definition ||= definition(name, version)
|
101
107
|
|
102
108
|
if materialized
|
103
|
-
Scenic.database.update_materialized_view(
|
109
|
+
Scenic.database.update_materialized_view(
|
110
|
+
name,
|
111
|
+
sql_definition,
|
112
|
+
no_data: no_data(materialized),
|
113
|
+
)
|
104
114
|
else
|
105
115
|
Scenic.database.update_view(name, sql_definition)
|
106
116
|
end
|
@@ -141,5 +151,13 @@ module Scenic
|
|
141
151
|
def definition(name, version)
|
142
152
|
Scenic::Definition.new(name, version).to_sql
|
143
153
|
end
|
154
|
+
|
155
|
+
def no_data(materialized)
|
156
|
+
if materialized.is_a?(Hash)
|
157
|
+
materialized.fetch(:no_data, false)
|
158
|
+
else
|
159
|
+
false
|
160
|
+
end
|
161
|
+
end
|
144
162
|
end
|
145
163
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Scenic
|
2
|
+
# The name of a view or table according to rails.
|
3
|
+
#
|
4
|
+
# This removes any table name prefix or suffix that is configured via
|
5
|
+
# ActiveRecord. This allows, for example, the SchemaDumper to dump a view with
|
6
|
+
# its unaffixed name, consistent with how rails handles table dumping.
|
7
|
+
class UnaffixedName
|
8
|
+
# Gets the unaffixed name for the provided string
|
9
|
+
# @return [String]
|
10
|
+
#
|
11
|
+
# @param name [String] The (potentially) affixed view name
|
12
|
+
def self.for(name)
|
13
|
+
new(name, config: ActiveRecord::Base).call
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(name, config:)
|
17
|
+
@name = name
|
18
|
+
@config = config
|
19
|
+
end
|
20
|
+
|
21
|
+
def call
|
22
|
+
prefix = Regexp.escape(config.table_name_prefix)
|
23
|
+
suffix = Regexp.escape(config.table_name_suffix)
|
24
|
+
name.sub(/\A#{prefix}(.+)#{suffix}\z/, "\\1")
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :name, :config
|
30
|
+
end
|
31
|
+
end
|
data/lib/scenic/version.rb
CHANGED
data/lib/scenic/view.rb
CHANGED
@@ -26,7 +26,7 @@ module Scenic
|
|
26
26
|
#
|
27
27
|
# @param name [String] The name of the view.
|
28
28
|
# @param definition [String] The SQL for the query that defines the view.
|
29
|
-
# @param materialized [
|
29
|
+
# @param materialized [Boolean] `true` if the view is materialized.
|
30
30
|
def initialize(name:, definition:, materialized:)
|
31
31
|
@name = name
|
32
32
|
@definition = definition
|
@@ -45,11 +45,14 @@ module Scenic
|
|
45
45
|
materialized_option = materialized ? "materialized: true, " : ""
|
46
46
|
|
47
47
|
<<-DEFINITION
|
48
|
-
create_view #{name.inspect}, #{materialized_option}
|
49
|
-
#{
|
48
|
+
create_view #{UnaffixedName.for(name).inspect}, #{materialized_option}sql_definition: <<-\SQL
|
49
|
+
#{escaped_definition.indent(2)}
|
50
50
|
SQL
|
51
|
-
|
52
51
|
DEFINITION
|
53
52
|
end
|
53
|
+
|
54
|
+
def escaped_definition
|
55
|
+
definition.gsub("\\", "\\\\\\")
|
56
|
+
end
|
54
57
|
end
|
55
58
|
end
|
data/lib/scenic.rb
CHANGED
data/scenic.gemspec
CHANGED
@@ -1,38 +1,36 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
3
|
+
require "scenic/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
6
|
+
spec.name = "scenic"
|
8
7
|
spec.version = Scenic::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
11
|
-
spec.summary =
|
8
|
+
spec.authors = ["Derek Prior", "Caleb Hearth"]
|
9
|
+
spec.email = ["derekprior@gmail.com", "caleb@calebhearth.com"]
|
10
|
+
spec.summary = "Support for database views in Rails migrations"
|
12
11
|
spec.description = <<-DESCRIPTION
|
13
12
|
Adds methods to ActiveRecord::Migration to create and manage database views
|
14
13
|
in Rails
|
15
14
|
DESCRIPTION
|
16
|
-
spec.homepage =
|
17
|
-
spec.license =
|
15
|
+
spec.homepage = "https://github.com/scenic-views/scenic"
|
16
|
+
spec.license = "MIT"
|
18
17
|
|
19
18
|
spec.files = `git ls-files -z`.split("\x0")
|
20
19
|
spec.test_files = spec.files.grep(%r{^spec/})
|
21
|
-
spec.require_paths = [
|
20
|
+
spec.require_paths = ["lib"]
|
22
21
|
|
23
|
-
spec.add_development_dependency
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
29
|
-
spec.add_development_dependency
|
30
|
-
spec.add_development_dependency
|
31
|
-
spec.add_development_dependency
|
32
|
-
spec.add_development_dependency 'redcarpet'
|
22
|
+
spec.add_development_dependency "bundler", ">= 1.5"
|
23
|
+
spec.add_development_dependency "database_cleaner"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec", ">= 3.3"
|
26
|
+
spec.add_development_dependency "pg", "~> 0.19"
|
27
|
+
spec.add_development_dependency "pry"
|
28
|
+
spec.add_development_dependency "ammeter", ">= 1.1.3"
|
29
|
+
spec.add_development_dependency "yard"
|
30
|
+
spec.add_development_dependency "redcarpet"
|
33
31
|
|
34
|
-
spec.add_dependency
|
35
|
-
spec.add_dependency
|
32
|
+
spec.add_dependency "activerecord", ">= 4.0.0"
|
33
|
+
spec.add_dependency "railties", ">= 4.0.0"
|
36
34
|
|
37
|
-
spec.required_ruby_version =
|
35
|
+
spec.required_ruby_version = ">= 2.3.0"
|
38
36
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require "acceptance_helper"
|
2
|
+
require "English"
|
2
3
|
|
3
4
|
describe "User manages views" do
|
4
5
|
it "handles simple views" do
|
@@ -56,7 +57,7 @@ describe "User manages views" do
|
|
56
57
|
|
57
58
|
def successfully(command)
|
58
59
|
`RAILS_ENV=test #{command}`
|
59
|
-
expect(
|
60
|
+
expect($CHILD_STATUS.exitstatus).to eq(0), "'#{command}' was unsuccessful"
|
60
61
|
end
|
61
62
|
|
62
63
|
def write_definition(file, contents)
|
@@ -2,7 +2,12 @@ development: &default
|
|
2
2
|
adapter: postgresql
|
3
3
|
database: dummy_development
|
4
4
|
encoding: unicode
|
5
|
+
host: localhost
|
5
6
|
pool: 5
|
7
|
+
<% if ENV.fetch("GITHUB_ACTIONS", false) || ENV.fetch("CODESPACES", false) %>
|
8
|
+
username: <%= ENV.fetch("POSTGRES_USER") %>
|
9
|
+
password: <%= ENV.fetch("POSTGRES_PASSWORD") %>
|
10
|
+
<% end %>
|
6
11
|
|
7
12
|
test:
|
8
13
|
<<: *default
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# This file is auto-generated from the current state of the database. Instead
|
2
|
+
# of editing this file, please use the migrations feature of Active Record to
|
3
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
4
|
+
#
|
5
|
+
# This file is the source Rails uses to define your schema when running `bin/rails
|
6
|
+
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
|
7
|
+
# be faster and is potentially less error prone than running all of your
|
8
|
+
# migrations from scratch. Old migrations may fail to apply correctly if those
|
9
|
+
# migrations use external dependencies or application code.
|
10
|
+
#
|
11
|
+
# It's strongly recommended that you check this file into your version control system.
|
12
|
+
|
13
|
+
ActiveRecord::Schema.define(version: 2022_01_12_154220) do
|
14
|
+
|
15
|
+
# These are extensions that must be enabled in order to support this database
|
16
|
+
enable_extension "pg_stat_statements"
|
17
|
+
enable_extension "plpgsql"
|
18
|
+
|
19
|
+
end
|
@@ -31,21 +31,39 @@ describe Scenic::Generators::ViewGenerator, :generator do
|
|
31
31
|
|
32
32
|
run_generator ["aired_episode", "--materialized"]
|
33
33
|
migration = migration_file(
|
34
|
-
"db/migrate/update_aired_episodes_to_version_2.rb"
|
34
|
+
"db/migrate/update_aired_episodes_to_version_2.rb",
|
35
35
|
)
|
36
36
|
expect(migration).to contain "materialized: true"
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
it "uses 'replace_view' instead of 'update_view' if replace flag is set" do
|
41
|
+
with_view_definition("aired_episodes", 1, "hello") do
|
42
|
+
allow(Dir).to receive(:entries).and_return(["aired_episodes_v01.sql"])
|
43
|
+
|
44
|
+
run_generator ["aired_episode", "--replace"]
|
45
|
+
migration = migration_file(
|
46
|
+
"db/migrate/update_aired_episodes_to_version_2.rb",
|
47
|
+
)
|
48
|
+
expect(migration).to contain "replace_view"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
40
52
|
context "for views created in a schema other than 'public'" do
|
41
|
-
it "creates view definition
|
42
|
-
migration = file("db/migrate/create_non_public_searches.rb")
|
53
|
+
it "creates a view definition" do
|
43
54
|
view_definition = file("db/views/non_public_searches_v01.sql")
|
44
55
|
|
45
56
|
run_generator ["non_public.search"]
|
46
57
|
|
47
|
-
expect(migration).to be_a_migration
|
48
58
|
expect(view_definition).to exist
|
49
59
|
end
|
60
|
+
|
61
|
+
it "creates a migration file" do
|
62
|
+
run_generator ["non_public.search"]
|
63
|
+
|
64
|
+
migration = migration_file("db/migrate/create_non_public_searches.rb")
|
65
|
+
expect(migration).to contain(/class CreateNonPublicSearches/)
|
66
|
+
expect(migration).to contain(/create_view "non_public.searches"/)
|
67
|
+
end
|
50
68
|
end
|
51
69
|
end
|
@@ -3,39 +3,79 @@ require "spec_helper"
|
|
3
3
|
module Scenic
|
4
4
|
module Adapters
|
5
5
|
describe Postgres::RefreshDependencies, :db do
|
6
|
-
|
7
|
-
adapter
|
6
|
+
context "view has dependencies" do
|
7
|
+
let(:adapter) { Postgres.new }
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
before do
|
10
|
+
adapter.create_materialized_view(
|
11
|
+
"first",
|
12
|
+
"SELECT text 'hi' AS greeting",
|
13
|
+
)
|
14
|
+
adapter.create_materialized_view(
|
15
|
+
"second",
|
16
|
+
"SELECT * FROM first",
|
17
|
+
)
|
18
|
+
adapter.create_materialized_view(
|
19
|
+
"third",
|
20
|
+
"SELECT * FROM first UNION SELECT * FROM second",
|
21
|
+
)
|
22
|
+
adapter.create_materialized_view(
|
23
|
+
"fourth_1",
|
24
|
+
"SELECT * FROM third",
|
25
|
+
)
|
26
|
+
adapter.create_materialized_view(
|
27
|
+
"x_fourth",
|
28
|
+
"SELECT * FROM fourth_1",
|
29
|
+
)
|
30
|
+
adapter.create_materialized_view(
|
31
|
+
"fourth",
|
32
|
+
"SELECT * FROM fourth_1 UNION SELECT * FROM x_fourth",
|
33
|
+
)
|
13
34
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
35
|
+
expect(adapter).to receive(:refresh_materialized_view)
|
36
|
+
.with("public.first", concurrently: true).ordered
|
37
|
+
expect(adapter).to receive(:refresh_materialized_view)
|
38
|
+
.with("public.second", concurrently: true).ordered
|
39
|
+
expect(adapter).to receive(:refresh_materialized_view)
|
40
|
+
.with("public.third", concurrently: true).ordered
|
41
|
+
expect(adapter).to receive(:refresh_materialized_view)
|
42
|
+
.with("public.fourth_1", concurrently: true).ordered
|
43
|
+
expect(adapter).to receive(:refresh_materialized_view)
|
44
|
+
.with("public.x_fourth", concurrently: true).ordered
|
45
|
+
end
|
18
46
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
47
|
+
it "refreshes in the right order when called without namespace" do
|
48
|
+
described_class.call(
|
49
|
+
:fourth,
|
50
|
+
adapter,
|
51
|
+
ActiveRecord::Base.connection,
|
52
|
+
concurrently: true,
|
53
|
+
)
|
54
|
+
end
|
23
55
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
56
|
+
it "refreshes in the right order when called with namespace" do
|
57
|
+
described_class.call(
|
58
|
+
"public.fourth",
|
59
|
+
adapter,
|
60
|
+
ActiveRecord::Base.connection,
|
61
|
+
concurrently: true,
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
31
65
|
|
32
|
-
|
33
|
-
|
66
|
+
context "view has no dependencies" do
|
67
|
+
it "does not raise an error" do
|
68
|
+
adapter = Postgres.new
|
34
69
|
|
35
|
-
|
36
|
-
|
70
|
+
adapter.create_materialized_view(
|
71
|
+
"first",
|
72
|
+
"SELECT text 'hi' AS greeting",
|
73
|
+
)
|
37
74
|
|
38
|
-
|
75
|
+
expect {
|
76
|
+
described_class.call(:first, adapter, ActiveRecord::Base.connection)
|
77
|
+
}.not_to raise_error
|
78
|
+
end
|
39
79
|
end
|
40
80
|
end
|
41
81
|
end
|
@@ -27,6 +27,20 @@ module Scenic
|
|
27
27
|
expect(view.materialized).to eq true
|
28
28
|
end
|
29
29
|
|
30
|
+
it "handles semicolon in definition when using `with no data`" do
|
31
|
+
adapter = Postgres.new
|
32
|
+
|
33
|
+
adapter.create_materialized_view(
|
34
|
+
"greetings",
|
35
|
+
"SELECT text 'hi' AS greeting; \n",
|
36
|
+
no_data: true,
|
37
|
+
)
|
38
|
+
|
39
|
+
view = adapter.views.first
|
40
|
+
expect(view.name).to eq("greetings")
|
41
|
+
expect(view.materialized).to eq true
|
42
|
+
end
|
43
|
+
|
30
44
|
it "raises an exception if the version of PostgreSQL is too old" do
|
31
45
|
connection = double("Connection", supports_materialized_views?: false)
|
32
46
|
connectable = double("Connectable", connection: connection)
|
@@ -104,9 +118,15 @@ module Scenic
|
|
104
118
|
connection = double("Connection").as_null_object
|
105
119
|
connectable = double("Connectable", connection: connection)
|
106
120
|
adapter = Postgres.new(connectable)
|
107
|
-
expect(Scenic::Adapters::Postgres::RefreshDependencies)
|
108
|
-
to receive(:call)
|
109
|
-
|
121
|
+
expect(Scenic::Adapters::Postgres::RefreshDependencies)
|
122
|
+
.to receive(:call)
|
123
|
+
.with(:tests, adapter, connection, concurrently: true)
|
124
|
+
|
125
|
+
adapter.refresh_materialized_view(
|
126
|
+
:tests,
|
127
|
+
cascade: true,
|
128
|
+
concurrently: true,
|
129
|
+
)
|
110
130
|
end
|
111
131
|
|
112
132
|
context "refreshing concurrently" do
|
@@ -8,11 +8,25 @@ describe Scenic::CommandRecorder do
|
|
8
8
|
expect(recorder.commands).to eq [[:create_view, [:greetings], nil]]
|
9
9
|
end
|
10
10
|
|
11
|
-
it "reverts to drop_view" do
|
11
|
+
it "reverts to drop_view when not passed a version" do
|
12
12
|
recorder.revert { recorder.create_view :greetings }
|
13
13
|
|
14
14
|
expect(recorder.commands).to eq [[:drop_view, [:greetings]]]
|
15
15
|
end
|
16
|
+
|
17
|
+
it "reverts to drop_view when passed a version" do
|
18
|
+
recorder.revert { recorder.create_view :greetings, version: 2 }
|
19
|
+
|
20
|
+
expect(recorder.commands).to eq [[:drop_view, [:greetings]]]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "reverts materialized views appropriately" do
|
24
|
+
recorder.revert { recorder.create_view :greetings, materialized: true }
|
25
|
+
|
26
|
+
expect(recorder.commands).to eq [
|
27
|
+
[:drop_view, [:greetings, materialized: true]],
|
28
|
+
]
|
29
|
+
end
|
16
30
|
end
|
17
31
|
|
18
32
|
describe "#drop_view" do
|
@@ -22,13 +22,27 @@ module Scenic
|
|
22
22
|
end
|
23
23
|
|
24
24
|
describe "path" do
|
25
|
-
it "returns a sql file in db/views with padded version and view name"
|
25
|
+
it "returns a sql file in db/views with padded version and view name" do
|
26
26
|
expected = "db/views/searches_v01.sql"
|
27
27
|
|
28
28
|
definition = Definition.new("searches", 1)
|
29
29
|
|
30
30
|
expect(definition.path).to eq expected
|
31
31
|
end
|
32
|
+
|
33
|
+
it "handles schema qualified view names" do
|
34
|
+
definition = Definition.new("non_public.searches", 1)
|
35
|
+
|
36
|
+
expect(definition.path).to eq "db/views/non_public_searches_v01.sql"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "handles active record view prefix and suffixing" do
|
40
|
+
with_affixed_tables(prefix: "foo_", suffix: "_bar") do
|
41
|
+
definition = Definition.new("foo_searches_bar", 1)
|
42
|
+
|
43
|
+
expect(definition.path).to eq "db/views/searches_v01.sql"
|
44
|
+
end
|
45
|
+
end
|
32
46
|
end
|
33
47
|
|
34
48
|
describe "full_path" do
|
@@ -15,7 +15,8 @@ describe Scenic::SchemaDumper, :db do
|
|
15
15
|
ActiveRecord::SchemaDumper.dump(Search.connection, stream)
|
16
16
|
|
17
17
|
output = stream.string
|
18
|
-
|
18
|
+
|
19
|
+
expect(output).to include 'create_view "searches", sql_definition: <<-SQL'
|
19
20
|
expect(output).to include view_definition
|
20
21
|
|
21
22
|
Search.connection.drop_view :searches
|
@@ -25,6 +26,35 @@ describe Scenic::SchemaDumper, :db do
|
|
25
26
|
expect(Search.first.haystack).to eq "needle"
|
26
27
|
end
|
27
28
|
|
29
|
+
it "accurately dumps create view statements with a regular expression" do
|
30
|
+
view_definition = "SELECT 'needle'::text AS haystack WHERE 'a2z' ~ '\\d+'"
|
31
|
+
Search.connection.create_view :searches, sql_definition: view_definition
|
32
|
+
stream = StringIO.new
|
33
|
+
|
34
|
+
ActiveRecord::SchemaDumper.dump(Search.connection, stream)
|
35
|
+
|
36
|
+
output = stream.string
|
37
|
+
expect(output).to include "~ '\\\\d+'::text"
|
38
|
+
|
39
|
+
Search.connection.drop_view :searches
|
40
|
+
silence_stream(STDOUT) { eval(output) }
|
41
|
+
|
42
|
+
expect(Search.first.haystack).to eq "needle"
|
43
|
+
end
|
44
|
+
|
45
|
+
it "dumps a create_view for a materialized view in the database" do
|
46
|
+
view_definition = "SELECT 'needle'::text AS haystack"
|
47
|
+
Search.connection.create_view :searches, materialized: true, sql_definition: view_definition
|
48
|
+
stream = StringIO.new
|
49
|
+
|
50
|
+
ActiveRecord::SchemaDumper.dump(Search.connection, stream)
|
51
|
+
|
52
|
+
output = stream.string
|
53
|
+
|
54
|
+
expect(output).to include 'create_view "searches", materialized: true, sql_definition: <<-SQL'
|
55
|
+
expect(output).to include view_definition
|
56
|
+
end
|
57
|
+
|
28
58
|
context "with views in non public schemas" do
|
29
59
|
it "dumps a create_view including namespace for a view in the database" do
|
30
60
|
view_definition = "SELECT 'needle'::text AS haystack"
|
@@ -41,6 +71,20 @@ describe Scenic::SchemaDumper, :db do
|
|
41
71
|
end
|
42
72
|
end
|
43
73
|
|
74
|
+
it "handles active record table name prefixes and suffixes" do
|
75
|
+
with_affixed_tables(prefix: "a_", suffix: "_z") do
|
76
|
+
view_definition = "SELECT 'needle'::text AS haystack"
|
77
|
+
Search.connection.create_view :a_searches_z, sql_definition: view_definition
|
78
|
+
stream = StringIO.new
|
79
|
+
|
80
|
+
ActiveRecord::SchemaDumper.dump(Search.connection, stream)
|
81
|
+
|
82
|
+
output = stream.string
|
83
|
+
|
84
|
+
expect(output).to include 'create_view "searches"'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
44
88
|
it "ignores tables internal to Rails" do
|
45
89
|
view_definition = "SELECT 'needle'::text AS haystack"
|
46
90
|
Search.connection.create_view :searches, sql_definition: view_definition
|
@@ -51,7 +95,7 @@ describe Scenic::SchemaDumper, :db do
|
|
51
95
|
output = stream.string
|
52
96
|
|
53
97
|
expect(output).to include 'create_view "searches"'
|
54
|
-
expect(output).not_to include "
|
98
|
+
expect(output).not_to include "pg_stat_statements_info"
|
55
99
|
expect(output).not_to include "schema_migrations"
|
56
100
|
end
|
57
101
|
|
@@ -79,7 +123,8 @@ describe Scenic::SchemaDumper, :db do
|
|
79
123
|
it "dumps a create_view for a view in the database" do
|
80
124
|
view_definition = "SELECT 'needle'::text AS haystack"
|
81
125
|
Search.connection.execute(
|
82
|
-
"CREATE SCHEMA scenic; SET search_path TO scenic, public"
|
126
|
+
"CREATE SCHEMA scenic; SET search_path TO scenic, public",
|
127
|
+
)
|
83
128
|
Search.connection.create_view 'scenic."search in a haystack"',
|
84
129
|
sql_definition: view_definition
|
85
130
|
stream = StringIO.new
|