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
@@ -9,9 +9,11 @@ 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, 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.
12
+ # @param materialized [Boolean, Hash] Set to a truthy value to create a
13
+ # materialized view. Hash
14
+ # @option materialized [Boolean] :no_data (false) Set to true to create
15
+ # materialized view without running the associated query. You will need
16
+ # to perform a non-concurrent refresh to populate with data.
15
17
  # @return The database response from executing the create statement.
16
18
  #
17
19
  # @example Create from `db/views/searches_v02.sql`
@@ -26,7 +28,7 @@ module Scenic
26
28
  if version.present? && sql_definition.present?
27
29
  raise(
28
30
  ArgumentError,
29
- "sql_definition and version cannot both be set",
31
+ "sql_definition and version cannot both be set"
30
32
  )
31
33
  end
32
34
 
@@ -37,10 +39,12 @@ module Scenic
37
39
  sql_definition ||= definition(name, version)
38
40
 
39
41
  if materialized
42
+ options = materialized_options(materialized)
43
+
40
44
  Scenic.database.create_materialized_view(
41
45
  name,
42
46
  sql_definition,
43
- no_data: no_data(materialized),
47
+ no_data: options[:no_data]
44
48
  )
45
49
  else
46
50
  Scenic.database.create_view(name, sql_definition)
@@ -80,36 +84,59 @@ module Scenic
80
84
  # as they are mutually exclusive.
81
85
  # @param revert_to_version [Fixnum] The version number to rollback to on
82
86
  # `rake db rollback`
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.
87
+ # @param materialized [Boolean, Hash] True or a Hash if updating a
88
+ # materialized view.
89
+ # @option materialized [Boolean] :no_data (false) Set to true to update
90
+ # a materialized view without loading data. You will need to perform a
91
+ # refresh to populate with data. Cannot be combined with the :side_by_side
92
+ # option.
93
+ # @option materialized [Boolean] :side_by_side (false) Set to true to update
94
+ # update a materialized view using our side-by-side strategy, which will
95
+ # limit the time the view is locked at the cost of increasing disk usage.
96
+ # The view is initially updated with a temporary name and atomically
97
+ # swapped once it is successfully created with data. Cannot be combined
98
+ # with the :no_data option.
86
99
  # @return The database response from executing the create statement.
87
100
  #
88
101
  # @example
89
102
  # update_view :engagement_reports, version: 3, revert_to_version: 2
90
- #
103
+ # update_view :comments, version: 2, revert_to_version: 1, materialized: { side_by_side: true }
91
104
  def update_view(name, version: nil, sql_definition: nil, revert_to_version: nil, materialized: false)
92
105
  if version.blank? && sql_definition.blank?
93
106
  raise(
94
107
  ArgumentError,
95
- "sql_definition or version must be specified",
108
+ "sql_definition or version must be specified"
96
109
  )
97
110
  end
98
111
 
99
112
  if version.present? && sql_definition.present?
100
113
  raise(
101
114
  ArgumentError,
102
- "sql_definition and version cannot both be set",
115
+ "sql_definition and version cannot both be set"
103
116
  )
104
117
  end
105
118
 
106
119
  sql_definition ||= definition(name, version)
107
120
 
108
121
  if materialized
122
+ options = materialized_options(materialized)
123
+
124
+ if options[:no_data] && options[:side_by_side]
125
+ raise(
126
+ ArgumentError,
127
+ "no_data and side_by_side options cannot be combined"
128
+ )
129
+ end
130
+
131
+ if options[:side_by_side] && !transaction_open?
132
+ raise "a transaction is required to perform a side-by-side update"
133
+ end
134
+
109
135
  Scenic.database.update_materialized_view(
110
136
  name,
111
137
  sql_definition,
112
- no_data: no_data(materialized),
138
+ no_data: options[:no_data],
139
+ side_by_side: options[:side_by_side]
113
140
  )
114
141
  else
115
142
  Scenic.database.update_view(name, sql_definition)
@@ -152,11 +179,17 @@ module Scenic
152
179
  Scenic::Definition.new(name, version).to_sql
153
180
  end
154
181
 
155
- def no_data(materialized)
156
- if materialized.is_a?(Hash)
157
- materialized.fetch(:no_data, false)
182
+ def materialized_options(materialized)
183
+ if materialized.is_a? Hash
184
+ {
185
+ no_data: materialized.fetch(:no_data, false),
186
+ side_by_side: materialized.fetch(:side_by_side, false)
187
+ }
158
188
  else
159
- false
189
+ {
190
+ no_data: false,
191
+ side_by_side: false
192
+ }
160
193
  end
161
194
  end
162
195
  end
@@ -1,3 +1,3 @@
1
1
  module Scenic
2
- VERSION = "1.7.0".freeze
2
+ VERSION = "1.9.0".freeze
3
3
  end
data/scenic.gemspec CHANGED
@@ -3,31 +3,35 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
  require "scenic/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "scenic"
7
- spec.version = Scenic::VERSION
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"
11
- spec.description = <<-DESCRIPTION
6
+ spec.name = "scenic"
7
+ spec.version = Scenic::VERSION
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"
11
+ spec.description = <<-DESCRIPTION
12
12
  Adds methods to ActiveRecord::Migration to create and manage database views
13
13
  in Rails
14
14
  DESCRIPTION
15
- spec.homepage = "https://github.com/scenic-views/scenic"
16
- spec.license = "MIT"
15
+ spec.homepage = "https://github.com/scenic-views/scenic"
16
+ spec.license = "MIT"
17
17
 
18
- spec.files = `git ls-files -z`.split("\x0")
19
- spec.test_files = spec.files.grep(%r{^spec/})
18
+ spec.files = `git ls-files -z`.split("\x0")
20
19
  spec.require_paths = ["lib"]
21
20
 
21
+ spec.metadata = {
22
+ "funding-uri" => "https://github.com/scenic-views/scenic"
23
+ }
24
+
22
25
  spec.add_development_dependency "bundler", ">= 1.5"
23
26
  spec.add_development_dependency "database_cleaner"
24
27
  spec.add_development_dependency "rake"
25
28
  spec.add_development_dependency "rspec", ">= 3.3"
26
- spec.add_development_dependency "pg", "~> 0.19"
29
+ spec.add_development_dependency "pg"
27
30
  spec.add_development_dependency "pry"
28
31
  spec.add_development_dependency "ammeter", ">= 1.1.3"
29
32
  spec.add_development_dependency "yard"
30
33
  spec.add_development_dependency "redcarpet"
34
+ spec.add_development_dependency "standard"
31
35
 
32
36
  spec.add_dependency "activerecord", ">= 4.0.0"
33
37
  spec.add_dependency "railties", ">= 4.0.0"
@@ -45,6 +45,17 @@ describe "User manages views" do
45
45
  verify_result "Child.take.name", "Elliot"
46
46
  verify_schema_contains 'add_index "children"'
47
47
 
48
+ successfully "rails generate scenic:view child --materialized --side-by-side"
49
+ verify_identical_view_definitions "children_v02", "children_v03"
50
+
51
+ write_definition "children_v03", "SELECT 'Juniper'::text AS name"
52
+ successfully "rake db:migrate"
53
+
54
+ successfully "rake db:reset"
55
+ verify_result "Child.take.name", "Juniper"
56
+ verify_schema_contains 'add_index "children"'
57
+
58
+ successfully "rake db:rollback"
48
59
  successfully "rake db:rollback"
49
60
  successfully "rake db:rollback"
50
61
  successfully "rails destroy scenic:model child"
data/spec/dummy/Rakefile CHANGED
@@ -1,13 +1,13 @@
1
1
  # Add your own tasks in files placed in lib/tasks ending in .rake,
2
2
  # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
3
 
4
- require File.expand_path('../config/application', __FILE__)
4
+ require File.expand_path("../config/application", __FILE__)
5
5
 
6
6
  Rails.application.load_tasks
7
7
 
8
- unless Rake::Task.task_defined?('db:environment:set')
9
- desc 'dummy task for rails versions where this task does not exist'
10
- task 'db:environment:set' do
11
- #no op
8
+ unless Rake::Task.task_defined?("db:environment:set")
9
+ desc "dummy task for rails versions where this task does not exist"
10
+ task "db:environment:set" do
11
+ # no op
12
12
  end
13
13
  end
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env ruby
2
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
- load Gem.bin_path('bundler', 'bundle')
2
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
3
+ load Gem.bin_path("bundler", "bundle")
data/spec/dummy/bin/rails CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- APP_PATH = File.expand_path('../../config/application', __FILE__)
3
- require_relative '../config/boot'
4
- require 'rails/commands'
2
+ APP_PATH = File.expand_path("../../config/application", __FILE__)
3
+ require_relative "../config/boot"
4
+ require "rails/commands"
data/spec/dummy/bin/rake CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- require_relative '../config/boot'
3
- require 'rake'
2
+ require_relative "../config/boot"
3
+ require "rake"
4
4
  Rake.application.run
@@ -11,5 +11,9 @@ module Dummy
11
11
  config.cache_classes = true
12
12
  config.eager_load = false
13
13
  config.active_support.deprecation = :stderr
14
+
15
+ if config.active_support.respond_to?(:to_time_preserves_timezone)
16
+ config.active_support.to_time_preserves_timezone = :zone
17
+ end
14
18
  end
15
19
  end
data/spec/dummy/config.ru CHANGED
@@ -1,4 +1,4 @@
1
1
  # This file is used by Rack-based servers to start the application.
2
2
 
3
- require ::File.expand_path('../config/environment', __FILE__)
3
+ require ::File.expand_path("../config/environment", __FILE__)
4
4
  run Rails.application
@@ -1,5 +1,5 @@
1
1
  class AddPgStatStatementsExtension < ActiveRecord::Migration[6.1]
2
2
  def change
3
- enable_extension 'pg_stat_statements'
3
+ enable_extension "pg_stat_statements"
4
4
  end
5
5
  end
@@ -11,9 +11,7 @@
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
13
  ActiveRecord::Schema.define(version: 2022_01_12_154220) do
14
-
15
14
  # These are extensions that must be enabled in order to support this database
16
15
  enable_extension "pg_stat_statements"
17
16
  enable_extension "plpgsql"
18
-
19
17
  end
@@ -6,7 +6,7 @@ module Scenic::Generators
6
6
  before do
7
7
  allow(ViewGenerator).to receive(:new)
8
8
  .and_return(
9
- instance_double("Scenic::Generators::ViewGenerator").as_null_object,
9
+ instance_double("Scenic::Generators::ViewGenerator").as_null_object
10
10
  )
11
11
  end
12
12
 
@@ -32,5 +32,13 @@ module Scenic::Generators
32
32
  expect(model_definition).to contain("self.refresh")
33
33
  expect(model_definition).to have_correct_syntax
34
34
  end
35
+
36
+ it "adds a populated? method to materialized models" do
37
+ run_generator ["active_user", "--materialized"]
38
+ model_definition = file("app/models/active_user.rb")
39
+
40
+ expect(model_definition).to contain("self.populated?")
41
+ expect(model_definition).to have_correct_syntax
42
+ end
35
43
  end
36
44
  end
@@ -31,19 +31,45 @@ 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 "sets the no_data option when updating a materialized view" 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", "--materialized", "--no-data"]
45
+ migration = migration_file(
46
+ "db/migrate/update_aired_episodes_to_version_2.rb"
47
+ )
48
+ expect(migration).to contain "materialized: { no_data: true }"
49
+ expect(migration).not_to contain "side_by_side"
50
+ end
51
+ end
52
+
53
+ it "sets the side-by-side option when updating a materialized view" do
54
+ with_view_definition("aired_episodes", 1, "hello") do
55
+ allow(Dir).to receive(:entries).and_return(["aired_episodes_v01.sql"])
56
+
57
+ run_generator ["aired_episode", "--materialized", "--side-by-side"]
58
+ migration = migration_file(
59
+ "db/migrate/update_aired_episodes_to_version_2.rb"
60
+ )
61
+ expect(migration).to contain "materialized: { side_by_side: true }"
62
+ expect(migration).not_to contain "no_data"
63
+ end
64
+ end
65
+
40
66
  it "uses 'replace_view' instead of 'update_view' if replace flag is set" do
41
67
  with_view_definition("aired_episodes", 1, "hello") do
42
68
  allow(Dir).to receive(:entries).and_return(["aired_episodes_v01.sql"])
43
69
 
44
70
  run_generator ["aired_episode", "--replace"]
45
71
  migration = migration_file(
46
- "db/migrate/update_aired_episodes_to_version_2.rb",
72
+ "db/migrate/update_aired_episodes_to_version_2.rb"
47
73
  )
48
74
  expect(migration).to contain "replace_view"
49
75
  end
@@ -61,7 +61,7 @@ describe "Reverting scenic schema statements", :db do
61
61
  end
62
62
 
63
63
  def run_migration(migration, directions)
64
- silence_stream(STDOUT) do
64
+ silence_stream($stdout) do
65
65
  Array.wrap(directions).each do |direction|
66
66
  migration.migrate(direction)
67
67
  end
@@ -9,7 +9,7 @@ module Scenic
9
9
  base_response = double("response from base connection")
10
10
  base_connection = double(
11
11
  "Connection",
12
- supports_materialized_views?: base_response,
12
+ supports_materialized_views?: base_response
13
13
  )
14
14
 
15
15
  connection = Postgres::Connection.new(base_connection)
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ module Scenic
4
+ module Adapters
5
+ describe Postgres::IndexCreation, :db do
6
+ it "successfully recreates applicable indexes" do
7
+ create_materialized_view("hi", "SELECT 'hi' AS greeting")
8
+ speaker = DummySpeaker.new
9
+
10
+ index = Scenic::Index.new(
11
+ object_name: "hi",
12
+ index_name: "hi_greeting_idx",
13
+ definition: "CREATE INDEX hi_greeting_idx ON hi (greeting)"
14
+ )
15
+
16
+ Postgres::IndexCreation
17
+ .new(connection: ActiveRecord::Base.connection, speaker: speaker)
18
+ .try_create([index])
19
+
20
+ expect(indexes_for("hi")).not_to be_empty
21
+ expect(speaker.messages).to include(/index 'hi_greeting_idx' .* has been created/)
22
+ end
23
+
24
+ it "skips indexes that are not applicable" do
25
+ create_materialized_view("hi", "SELECT 'hi' AS greeting")
26
+ speaker = DummySpeaker.new
27
+ index = Scenic::Index.new(
28
+ object_name: "hi",
29
+ index_name: "hi_person_idx",
30
+ definition: "CREATE INDEX hi_person_idx ON hi (person)"
31
+ )
32
+
33
+ Postgres::IndexCreation
34
+ .new(connection: ActiveRecord::Base.connection, speaker: speaker)
35
+ .try_create([index])
36
+
37
+ expect(indexes_for("hi")).to be_empty
38
+ expect(speaker.messages).to include(/index 'hi_person_idx' .* has been dropped/)
39
+ end
40
+ end
41
+
42
+ class DummySpeaker
43
+ attr_reader :messages
44
+
45
+ def initialize
46
+ @messages = []
47
+ end
48
+
49
+ def say(message, bool = false)
50
+ @messages << message
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ module Scenic
4
+ module Adapters
5
+ describe Postgres::IndexMigration, :db, :silence do
6
+ it "moves indexes from the old view to the new view" do
7
+ create_materialized_view("hi", "SELECT 'hi' AS greeting")
8
+ create_materialized_view("hi_temp", "SELECT 'hi' AS greeting")
9
+ add_index(:hi, :greeting, name: "hi_greeting_idx")
10
+
11
+ Postgres::IndexMigration
12
+ .new(connection: ActiveRecord::Base.connection)
13
+ .migrate(from: "hi", to: "hi_temp")
14
+ indexes_for_original = indexes_for("hi")
15
+ indexes_for_temporary = indexes_for("hi_temp")
16
+
17
+ expect(indexes_for_original.length).to eq 1
18
+ expect(indexes_for_original.first.index_name).not_to eq "hi_greeting_idx"
19
+ expect(indexes_for_temporary.length).to eq 1
20
+ expect(indexes_for_temporary.first.index_name).to eq "hi_greeting_idx"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -9,27 +9,27 @@ module Scenic
9
9
  before do
10
10
  adapter.create_materialized_view(
11
11
  "first",
12
- "SELECT text 'hi' AS greeting",
12
+ "SELECT text 'hi' AS greeting"
13
13
  )
14
14
  adapter.create_materialized_view(
15
15
  "second",
16
- "SELECT * FROM first",
16
+ "SELECT * FROM first"
17
17
  )
18
18
  adapter.create_materialized_view(
19
19
  "third",
20
- "SELECT * FROM first UNION SELECT * FROM second",
20
+ "SELECT * FROM first UNION SELECT * FROM second"
21
21
  )
22
22
  adapter.create_materialized_view(
23
23
  "fourth_1",
24
- "SELECT * FROM third",
24
+ "SELECT * FROM third"
25
25
  )
26
26
  adapter.create_materialized_view(
27
27
  "x_fourth",
28
- "SELECT * FROM fourth_1",
28
+ "SELECT * FROM fourth_1"
29
29
  )
30
30
  adapter.create_materialized_view(
31
31
  "fourth",
32
- "SELECT * FROM fourth_1 UNION SELECT * FROM x_fourth",
32
+ "SELECT * FROM fourth_1 UNION SELECT * FROM x_fourth"
33
33
  )
34
34
 
35
35
  expect(adapter).to receive(:refresh_materialized_view)
@@ -49,7 +49,7 @@ module Scenic
49
49
  :fourth,
50
50
  adapter,
51
51
  ActiveRecord::Base.connection,
52
- concurrently: true,
52
+ concurrently: true
53
53
  )
54
54
  end
55
55
 
@@ -58,7 +58,7 @@ module Scenic
58
58
  "public.fourth",
59
59
  adapter,
60
60
  ActiveRecord::Base.connection,
61
- concurrently: true,
61
+ concurrently: true
62
62
  )
63
63
  end
64
64
  end
@@ -69,7 +69,7 @@ module Scenic
69
69
 
70
70
  adapter.create_materialized_view(
71
71
  "first",
72
- "SELECT text 'hi' AS greeting",
72
+ "SELECT text 'hi' AS greeting"
73
73
  )
74
74
 
75
75
  expect {
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ module Scenic
4
+ module Adapters
5
+ describe Postgres::SideBySide, :db, :silence do
6
+ it "updates the materialized view to the new version" do
7
+ adapter = Postgres.new
8
+ create_materialized_view("hi", "SELECT 'hi' AS greeting")
9
+ add_index(:hi, :greeting, name: "hi_greeting_idx")
10
+ new_definition = "SELECT 'hola' AS greeting"
11
+
12
+ Postgres::SideBySide
13
+ .new(adapter: adapter, name: "hi", definition: new_definition)
14
+ .update
15
+ result = ar_connection.execute("SELECT * FROM hi").first["greeting"]
16
+ indexes = indexes_for("hi")
17
+
18
+ expect(result).to eq "hola"
19
+ expect(indexes.length).to eq 1
20
+ expect(indexes.first.index_name).to eq "hi_greeting_idx"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ module Scenic
4
+ module Adapters
5
+ describe Postgres::TemporaryName do
6
+ it "generates a temporary name based on a SHA1 hash of the original" do
7
+ name = "my_materialized_view"
8
+
9
+ temporary_name = Postgres::TemporaryName.new(name).to_s
10
+
11
+ expect(temporary_name).to match(/_scenic_sbs_[0-9a-f]{40}/)
12
+ end
13
+
14
+ it "does not overflow the 63 character limit for object names" do
15
+ name = "long_view_name_" * 10
16
+
17
+ temporary_name = Postgres::TemporaryName.new(name).to_s
18
+
19
+ expect(temporary_name.length).to eq 52
20
+ end
21
+ end
22
+ end
23
+ end