scenic 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.hound.yml +2 -0
- data/.travis.yml +2 -0
- data/NEWS.md +8 -0
- data/Rakefile +5 -1
- data/lib/generators/scenic/view/USAGE +9 -1
- data/lib/generators/scenic/view/templates/db/migrate/update_view.erb +5 -0
- data/lib/generators/scenic/view/view_generator.rb +55 -5
- data/lib/scenic.rb +13 -14
- data/lib/scenic/adapters/postgres.rb +27 -0
- data/lib/scenic/command_recorder.rb +42 -0
- data/lib/scenic/command_recorder/statement_arguments.rb +42 -0
- data/lib/scenic/definition.rb +34 -0
- data/lib/scenic/schema_dumper.rb +40 -0
- data/lib/scenic/statements.rb +84 -0
- data/lib/scenic/version.rb +1 -1
- data/lib/scenic/view.rb +24 -0
- data/spec/dummy/db/migrate/.keep +0 -0
- data/spec/generators/scenic/model/model_generator_spec.rb +17 -15
- data/spec/generators/scenic/view/view_generator_spec.rb +14 -4
- data/spec/scenic/adapters/postgres_spec.rb +38 -0
- data/spec/scenic/{active_record/command_recorder → command_recorder}/statement_arguments_spec.rb +1 -1
- data/spec/scenic/{active_record/command_recorder_spec.rb → command_recorder_spec.rb} +5 -12
- data/spec/scenic/definition_spec.rb +56 -0
- data/spec/scenic/{active_record/schema_dumper_spec.rb → schema_dumper_spec.rb} +1 -1
- data/spec/scenic/statements_spec.rb +72 -0
- data/spec/smoke +86 -0
- data/spec/support/view_definition_helpers.rb +3 -3
- metadata +27 -14
- data/lib/scenic/active_record/command_recorder.rb +0 -44
- data/lib/scenic/active_record/command_recorder/statement_arguments.rb +0 -44
- data/lib/scenic/active_record/schema_dumper.rb +0 -52
- data/lib/scenic/active_record/statements.rb +0 -37
- data/spec/scenic/active_record/statements_spec.rb +0 -82
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a764a03ca4024bdd13deea0c9a18251b91e664e1
|
4
|
+
data.tar.gz: be3c32867baca5e7c3887372d25f3a40d1f93dd3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9afab6aedec2d7167737a4879e8a7f79f3511dc10d0d0b980790cd827035ccec6697c0fb61c401305190f353e72d21543ad2699552ea5eafe7ae0e33877bff68
|
7
|
+
data.tar.gz: da72e6957c563eca8c01601ac99a1876d7b48f1767d55d327b5c60ae8acf53853cda13a4b6b1c8d99af72ec9864fa2d90425afc71e41cd5bdb481cebbe251cc9
|
data/.hound.yml
CHANGED
data/.travis.yml
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
before_install:
|
2
2
|
- "echo '--colour' > ~/.rspec"
|
3
3
|
- "echo 'gem: --no-document' > ~/.gemrc"
|
4
|
+
- git config --global user.name 'Travis CI'
|
5
|
+
- git config --global user.email 'travis-ci@example.com'
|
4
6
|
before_script:
|
5
7
|
- pushd spec/dummy && bundle exec rake db:create && popd
|
6
8
|
branches:
|
data/NEWS.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
* Teach view generator to update existing views [683361d](https://github.com/thoughtbot/scenic/commit/683361d59410f46aba508a3ceb850161dd0be027)
|
2
|
+
|
3
|
+
|
4
|
+
*Caleb Thompson*
|
5
|
+
|
6
|
+
* Raise an error if view definition is empty. [PR #38](https://github.com/thoughtbot/scenic/issues/38)
|
7
|
+
|
8
|
+
*Caleb Thompson*
|
data/Rakefile
CHANGED
@@ -2,8 +2,16 @@ Description:
|
|
2
2
|
Create a new database view for your application. This will create a new
|
3
3
|
view definition file and the accompanying migration.
|
4
4
|
|
5
|
+
If a view of the given name already exists, create a new version of the view
|
6
|
+
and a migration to replace the old version with the new.
|
7
|
+
|
5
8
|
Examples:
|
6
9
|
rails generate scenic:view searches
|
7
10
|
|
8
|
-
create: db/views/
|
11
|
+
create: db/views/searches_v01.sql
|
9
12
|
create: db/migrate/20140803191158_create_searches.rb
|
13
|
+
|
14
|
+
rails generate scenic:view searches
|
15
|
+
|
16
|
+
create: db/views/searches_v02.sql
|
17
|
+
create: db/migrate/20140804191158_update_searches_to_version_2.rb
|
@@ -8,19 +8,69 @@ module Scenic
|
|
8
8
|
source_root File.expand_path("../templates", __FILE__)
|
9
9
|
|
10
10
|
def create_view_definition
|
11
|
-
create_file
|
11
|
+
create_file definition.path
|
12
12
|
end
|
13
13
|
|
14
14
|
def create_migration_file
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
if creating_new_view? || destroying_initial_view?
|
16
|
+
migration_template(
|
17
|
+
"db/migrate/create_view.erb",
|
18
|
+
"db/migrate/create_#{plural_file_name}.rb"
|
19
|
+
)
|
20
|
+
else
|
21
|
+
migration_template(
|
22
|
+
"db/migrate/update_view.erb",
|
23
|
+
"db/migrate/update_#{plural_file_name}_to_version_#{version}.rb"
|
24
|
+
)
|
25
|
+
end
|
19
26
|
end
|
20
27
|
|
21
28
|
def self.next_migration_number(dir)
|
22
29
|
::ActiveRecord::Generators::Base.next_migration_number(dir)
|
23
30
|
end
|
31
|
+
|
32
|
+
no_tasks do
|
33
|
+
def previous_version
|
34
|
+
@previous_version ||=
|
35
|
+
Dir.entries(Rails.root.join(*%w(db views)))
|
36
|
+
.map { |name| version_regex.match(name).try(:[], "version").to_i }
|
37
|
+
.max
|
38
|
+
end
|
39
|
+
|
40
|
+
def version
|
41
|
+
@version ||= destroying? ? previous_version : previous_version.next
|
42
|
+
end
|
43
|
+
|
44
|
+
def migration_class_name
|
45
|
+
if creating_new_view?
|
46
|
+
super
|
47
|
+
else
|
48
|
+
"Update#{class_name.pluralize}ToVersion#{version}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def version_regex
|
56
|
+
/\A#{plural_file_name}_v(?<version>\d+)\.sql\z/
|
57
|
+
end
|
58
|
+
|
59
|
+
def creating_new_view?
|
60
|
+
previous_version == 0
|
61
|
+
end
|
62
|
+
|
63
|
+
def definition
|
64
|
+
Scenic::Definition.new(plural_file_name, version)
|
65
|
+
end
|
66
|
+
|
67
|
+
def destroying?
|
68
|
+
behavior == :revoke
|
69
|
+
end
|
70
|
+
|
71
|
+
def destroying_initial_view?
|
72
|
+
destroying? && version == 1
|
73
|
+
end
|
24
74
|
end
|
25
75
|
end
|
26
76
|
end
|
data/lib/scenic.rb
CHANGED
@@ -1,21 +1,20 @@
|
|
1
|
-
require "scenic/
|
1
|
+
require "scenic/adapters/postgres"
|
2
|
+
require "scenic/command_recorder"
|
3
|
+
require "scenic/definition"
|
2
4
|
require "scenic/railtie"
|
3
|
-
require "scenic/
|
4
|
-
require "scenic/
|
5
|
-
require "scenic/
|
5
|
+
require "scenic/schema_dumper"
|
6
|
+
require "scenic/statements"
|
7
|
+
require "scenic/version"
|
8
|
+
require "scenic/view"
|
6
9
|
|
7
10
|
module Scenic
|
8
11
|
def self.load
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
::ActiveRecord::Migration::CommandRecorder.class_eval do
|
14
|
-
include Scenic::ActiveRecord::CommandRecorder
|
15
|
-
end
|
12
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.include Scenic::Statements
|
13
|
+
ActiveRecord::Migration::CommandRecorder.include Scenic::CommandRecorder
|
14
|
+
ActiveRecord::SchemaDumper.include Scenic::SchemaDumper
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
end
|
17
|
+
def self.database
|
18
|
+
Scenic::Adapters::Postgres
|
20
19
|
end
|
21
20
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Scenic
|
2
|
+
module Adapters
|
3
|
+
module Postgres
|
4
|
+
def self.views
|
5
|
+
execute(<<-SQL).map { |result| Scenic::View.new(result) }
|
6
|
+
SELECT viewname, definition
|
7
|
+
FROM pg_views
|
8
|
+
WHERE schemaname = ANY (current_schemas(false))
|
9
|
+
SQL
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.create_view(name, sql_definition)
|
13
|
+
execute "CREATE VIEW #{name} AS #{sql_definition};"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.drop_view(name)
|
17
|
+
execute "DROP VIEW #{name};"
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def self.execute(sql, base = ActiveRecord::Base)
|
23
|
+
base.connection.execute sql
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "scenic/command_recorder/statement_arguments"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
module CommandRecorder
|
5
|
+
def create_view(*args)
|
6
|
+
record(:create_view, args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def drop_view(*args)
|
10
|
+
record(:drop_view, args)
|
11
|
+
end
|
12
|
+
|
13
|
+
def update_view(*args)
|
14
|
+
record(:update_view, args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def invert_create_view(args)
|
18
|
+
[:drop_view, args]
|
19
|
+
end
|
20
|
+
|
21
|
+
def invert_drop_view(args)
|
22
|
+
perform_scenic_inversion(:create_view, args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def invert_update_view(args)
|
26
|
+
perform_scenic_inversion(:update_view, args)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def perform_scenic_inversion(method, args)
|
32
|
+
scenic_args = StatementArguments.new(args)
|
33
|
+
|
34
|
+
if scenic_args.revert_to_version.nil?
|
35
|
+
message = "#{method} is reversible only if given a revert_to_version"
|
36
|
+
raise ActiveRecord::IrreversibleMigration, message
|
37
|
+
end
|
38
|
+
|
39
|
+
[method, scenic_args.invert_version.to_a]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Scenic
|
2
|
+
module CommandRecorder
|
3
|
+
class StatementArguments
|
4
|
+
def initialize(args)
|
5
|
+
@args = args.freeze
|
6
|
+
end
|
7
|
+
|
8
|
+
def view
|
9
|
+
@args[0]
|
10
|
+
end
|
11
|
+
|
12
|
+
def version
|
13
|
+
options[:version]
|
14
|
+
end
|
15
|
+
|
16
|
+
def revert_to_version
|
17
|
+
options[:revert_to_version]
|
18
|
+
end
|
19
|
+
|
20
|
+
def invert_version
|
21
|
+
StatementArguments.new([view, options_for_revert])
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_a
|
25
|
+
@args.to_a
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def options
|
31
|
+
@options ||= @args[1] || {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def options_for_revert
|
35
|
+
options.clone.tap do |revert_options|
|
36
|
+
revert_options[:version] = revert_to_version
|
37
|
+
revert_options.delete(:revert_to_version)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Scenic
|
2
|
+
class Definition
|
3
|
+
def initialize(name, version)
|
4
|
+
@name = name
|
5
|
+
@version = version.to_i
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_sql
|
9
|
+
File.read(full_path).tap do |content|
|
10
|
+
if content.empty?
|
11
|
+
raise "Define view query in #{@path} before migrating."
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def full_path
|
17
|
+
Rails.root.join(path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def path
|
21
|
+
File.join("db", "views", filename)
|
22
|
+
end
|
23
|
+
|
24
|
+
def version
|
25
|
+
@version.to_s.rjust(2, "0")
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def filename
|
31
|
+
"#{@name}_v#{version}.sql"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "rails"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
module SchemaDumper
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included { alias_method_chain :tables, :views }
|
8
|
+
|
9
|
+
def tables_with_views(stream)
|
10
|
+
tables_without_views(stream)
|
11
|
+
views(stream)
|
12
|
+
end
|
13
|
+
|
14
|
+
def views(stream)
|
15
|
+
views_in_database.select { |view| !ignored?(view.name) }.each do |view|
|
16
|
+
stream.puts(view.to_schema)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def views_in_database
|
21
|
+
@views_in_database ||= Scenic.database.views.sort
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
unless ActiveRecord::SchemaDumper.instance_methods(false).include?(:ignored?)
|
27
|
+
# This method will be present in Rails 4.2.0 and can be removed then.
|
28
|
+
def ignored?(table_name)
|
29
|
+
["schema_migrations", ignore_tables].flatten.any? do |ignored|
|
30
|
+
case ignored
|
31
|
+
when String; remove_prefix_and_suffix(table_name) == ignored
|
32
|
+
when Regexp; remove_prefix_and_suffix(table_name) =~ ignored
|
33
|
+
else
|
34
|
+
raise StandardError, "ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values."
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Scenic
|
2
|
+
module Statements
|
3
|
+
# Public: Create a new database view.
|
4
|
+
#
|
5
|
+
# name - A string or symbol containing the singular name of the database
|
6
|
+
# view. Cannot conflict with any other view or table names.
|
7
|
+
# version - The version number of the view. If present, will be used to find
|
8
|
+
# the definition file in db/views in the form db/views/[pluralized
|
9
|
+
# name]_v[2 digit zero padded version].sql.
|
10
|
+
# Example: db/views/searches_v02.sql.
|
11
|
+
# sql_definition - A string containing the SQL definition of the view. If
|
12
|
+
# both sql_definition and version are provided,
|
13
|
+
# sql_definition takes prescedence.
|
14
|
+
#
|
15
|
+
# Examples
|
16
|
+
#
|
17
|
+
# create_view(:searches, version: 2)
|
18
|
+
#
|
19
|
+
# create_view(:active_users, sql_definition: <<-SQL)
|
20
|
+
# SELECT * FROM users WHERE users.active = 't'
|
21
|
+
# SQL
|
22
|
+
#
|
23
|
+
# Returns the database response from executing the CREATE VIEW statement.
|
24
|
+
def create_view(name, version: 1, sql_definition: nil)
|
25
|
+
if version.blank? && sql_definition.nil?
|
26
|
+
raise(
|
27
|
+
ArgumentError,
|
28
|
+
"view_definition or version_number must be specified"
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
sql_definition ||= definition(name, version)
|
33
|
+
|
34
|
+
Scenic.database.create_view(name, sql_definition)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Drop a database view by name.
|
38
|
+
#
|
39
|
+
# name - A string or symbol containing the singular name of the database
|
40
|
+
# view. Must be an existing view.
|
41
|
+
# revert_to_version - Used to revert the drop_view command in the
|
42
|
+
# db:rollback rake task, which would pass the version
|
43
|
+
# number to create_view. Usually the most recent
|
44
|
+
# version.
|
45
|
+
#
|
46
|
+
# Example
|
47
|
+
#
|
48
|
+
# drop_view(:users_who_recently_logged_in, 3)
|
49
|
+
#
|
50
|
+
# Returns the database response from executing the DROP VIEW statement.
|
51
|
+
def drop_view(name, revert_to_version: nil)
|
52
|
+
Scenic.database.drop_view(name)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: Update a database view to a new version by first dropping the
|
56
|
+
# previous version then creating the new version.
|
57
|
+
#
|
58
|
+
# name - A string or symbol containing the singular name of the database
|
59
|
+
# view. Must be an existing view.
|
60
|
+
# version - The version number of the view. See create_view for details.
|
61
|
+
# revert_to_version - The version to revert to for db:rollback. Usually the
|
62
|
+
# previous version. See drop_view for details.
|
63
|
+
#
|
64
|
+
# Example
|
65
|
+
#
|
66
|
+
# update_view(:engagement_reports, version: 3, revert_to_version: 2)
|
67
|
+
#
|
68
|
+
# Returns the database response from executing the CREATE VIEW statement.
|
69
|
+
def update_view(name, version: nil, revert_to_version: nil)
|
70
|
+
if version.blank?
|
71
|
+
raise ArgumentError, "version is required"
|
72
|
+
end
|
73
|
+
|
74
|
+
drop_view(name, revert_to_version: revert_to_version)
|
75
|
+
create_view(name, version: version)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def definition(name, version)
|
81
|
+
Scenic::Definition.new(name, version).to_sql
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/scenic/version.rb
CHANGED
data/lib/scenic/view.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Scenic
|
2
|
+
class View
|
3
|
+
attr_reader :name, :definition
|
4
|
+
delegate :<=>, to: :name
|
5
|
+
|
6
|
+
def initialize(view_row)
|
7
|
+
@name = view_row["viewname"]
|
8
|
+
@definition = view_row["definition"].strip
|
9
|
+
end
|
10
|
+
|
11
|
+
def ==(other)
|
12
|
+
name == other.name &&
|
13
|
+
definition == other.definition
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_schema
|
17
|
+
<<-DEFINITION.strip_heredoc
|
18
|
+
create_view :#{name}, sql_definition:<<-\SQL
|
19
|
+
#{definition}
|
20
|
+
SQL
|
21
|
+
DEFINITION
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
File without changes
|
@@ -1,23 +1,25 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
require "generators/scenic/model/model_generator"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
.
|
8
|
-
|
9
|
-
|
10
|
-
|
4
|
+
module Scenic::Generators
|
5
|
+
describe ModelGenerator, :generator do
|
6
|
+
before do
|
7
|
+
allow(ViewGenerator).to receive(:new)
|
8
|
+
.and_return(
|
9
|
+
instance_double("Scenic::Generators::ViewGenerator").as_null_object
|
10
|
+
)
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
it "invokes the view generator" do
|
14
|
+
run_generator ["current_customer"]
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
expect(ViewGenerator).to have_received(:new)
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
it "creates a migration to create the view" do
|
20
|
+
run_generator ["current_customer"]
|
21
|
+
model_definition = file("app/models/current_customer.rb")
|
22
|
+
expect(model_definition).to exist
|
23
|
+
end
|
22
24
|
end
|
23
25
|
end
|
@@ -2,15 +2,25 @@ require "spec_helper"
|
|
2
2
|
require "generators/scenic/view/view_generator"
|
3
3
|
|
4
4
|
describe Scenic::Generators::ViewGenerator, :generator do
|
5
|
-
it "creates
|
5
|
+
it "creates view definition and migration files" do
|
6
|
+
migration = file("db/migrate/create_searches.rb")
|
7
|
+
view_definition = file("db/views/searches_v01.sql")
|
8
|
+
|
6
9
|
run_generator ["search"]
|
7
|
-
|
10
|
+
|
11
|
+
expect(migration).to be_a_migration
|
8
12
|
expect(view_definition).to exist
|
9
13
|
end
|
10
14
|
|
11
|
-
it "
|
15
|
+
it "updates an existing view" do
|
16
|
+
migration = file("db/migrate/update_searches_to_version_2.rb")
|
17
|
+
view_definition = file("db/views/searches_v02.sql")
|
18
|
+
allow(Dir).to receive(:entries)
|
19
|
+
.and_return(["searches_v01.sql"])
|
20
|
+
|
12
21
|
run_generator ["search"]
|
13
|
-
|
22
|
+
|
14
23
|
expect(migration).to be_a_migration
|
24
|
+
expect(view_definition).to exist
|
15
25
|
end
|
16
26
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
module Adapters
|
5
|
+
describe Postgres, :db do
|
6
|
+
describe "create_view" do
|
7
|
+
it "successfully creates a view" do
|
8
|
+
Postgres.create_view("greetings", "SELECT text 'hi' AS greeting")
|
9
|
+
|
10
|
+
expect(Postgres.views.map(&:name)).to include("greetings")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "drop_view" do
|
15
|
+
it "successfully drops a view" do
|
16
|
+
Postgres.create_view("greetings", "SELECT text 'hi' AS greeting")
|
17
|
+
|
18
|
+
Postgres.drop_view("greetings")
|
19
|
+
|
20
|
+
expect(Postgres.views.map(&:name)).not_to include("greetings")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "views" do
|
25
|
+
it "finds views and builds Scenic::View objects" do
|
26
|
+
ActiveRecord::Base.connection.execute "CREATE VIEW greetings AS SELECT text 'hi' AS greeting"
|
27
|
+
|
28
|
+
expect(Postgres.views).to eq([
|
29
|
+
View.new(
|
30
|
+
"viewname" => "greetings",
|
31
|
+
"definition" => "SELECT 'hi'::text AS greeting;",
|
32
|
+
),
|
33
|
+
])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -1,18 +1,14 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
|
-
describe Scenic::
|
3
|
+
describe Scenic::CommandRecorder do
|
4
4
|
describe "#create_view" do
|
5
5
|
it "records the created view" do
|
6
|
-
recorder = ActiveRecord::Migration::CommandRecorder.new
|
7
|
-
|
8
6
|
recorder.create_view :greetings
|
9
7
|
|
10
8
|
expect(recorder.commands).to eq [[:create_view, [:greetings], nil]]
|
11
9
|
end
|
12
10
|
|
13
11
|
it "reverts to drop_view" do
|
14
|
-
recorder = ActiveRecord::Migration::CommandRecorder.new
|
15
|
-
|
16
12
|
recorder.revert { recorder.create_view :greetings }
|
17
13
|
|
18
14
|
expect(recorder.commands).to eq [[:drop_view, [:greetings]]]
|
@@ -21,15 +17,12 @@ describe Scenic::ActiveRecord::CommandRecorder do
|
|
21
17
|
|
22
18
|
describe "#drop_view" do
|
23
19
|
it "records the dropped view" do
|
24
|
-
recorder = ActiveRecord::Migration::CommandRecorder.new
|
25
|
-
|
26
20
|
recorder.drop_view :users
|
27
21
|
|
28
22
|
expect(recorder.commands).to eq [[:drop_view, [:users], nil]]
|
29
23
|
end
|
30
24
|
|
31
25
|
it "reverts to create_view with specified revert_to_version" do
|
32
|
-
recorder = ActiveRecord::Migration::CommandRecorder.new
|
33
26
|
args = [:users, { revert_to_version: 3 }]
|
34
27
|
revert_args = [:users, { version: 3 }]
|
35
28
|
|
@@ -39,7 +32,6 @@ describe Scenic::ActiveRecord::CommandRecorder do
|
|
39
32
|
end
|
40
33
|
|
41
34
|
it "raises when reverting without revert_to_version set" do
|
42
|
-
recorder = ActiveRecord::Migration::CommandRecorder.new
|
43
35
|
args = [:users, { another_argument: 1 }]
|
44
36
|
|
45
37
|
expect { recorder.revert { recorder.drop_view(*args) } }
|
@@ -49,7 +41,6 @@ describe Scenic::ActiveRecord::CommandRecorder do
|
|
49
41
|
|
50
42
|
describe "#update_view" do
|
51
43
|
it "records the updated view" do
|
52
|
-
recorder = ActiveRecord::Migration::CommandRecorder.new
|
53
44
|
args = [:users, { version: 2 }]
|
54
45
|
|
55
46
|
recorder.update_view(*args)
|
@@ -58,7 +49,6 @@ describe Scenic::ActiveRecord::CommandRecorder do
|
|
58
49
|
end
|
59
50
|
|
60
51
|
it "reverts to update_view with the specified revert_to_version" do
|
61
|
-
recorder = ActiveRecord::Migration::CommandRecorder.new
|
62
52
|
args = [:users, { version: 2, revert_to_version: 1 }]
|
63
53
|
revert_args = [:users, { version: 1 }]
|
64
54
|
|
@@ -68,11 +58,14 @@ describe Scenic::ActiveRecord::CommandRecorder do
|
|
68
58
|
end
|
69
59
|
|
70
60
|
it "raises when reverting without revert_to_version set" do
|
71
|
-
recorder = ActiveRecord::Migration::CommandRecorder.new
|
72
61
|
args = [:users, { version: 42, another_argument: 1 }]
|
73
62
|
|
74
63
|
expect { recorder.revert { recorder.update_view(*args) } }
|
75
64
|
.to raise_error(ActiveRecord::IrreversibleMigration)
|
76
65
|
end
|
77
66
|
end
|
67
|
+
|
68
|
+
def recorder
|
69
|
+
@recorder ||= ActiveRecord::Migration::CommandRecorder.new
|
70
|
+
end
|
78
71
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
describe Definition do
|
5
|
+
describe "to_sql" do
|
6
|
+
it "returns the content of a view definition" do
|
7
|
+
sql_definition = "SELECT text 'Hi' as greeting"
|
8
|
+
allow(File).to receive(:read).and_return(sql_definition)
|
9
|
+
|
10
|
+
definition = Definition.new("searches", 1)
|
11
|
+
|
12
|
+
expect(definition.to_sql).to eq sql_definition
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raises an error if the file is empty" do
|
16
|
+
allow(File).to receive(:read).and_return("")
|
17
|
+
|
18
|
+
expect do
|
19
|
+
Definition.new("searches", 1).to_sql
|
20
|
+
end.to raise_error RuntimeError
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "path" do
|
25
|
+
it "returns a sql file in db/views with padded version and view name" do
|
26
|
+
expected = "db/views/searches_v01.sql"
|
27
|
+
|
28
|
+
definition = Definition.new("searches", 1)
|
29
|
+
|
30
|
+
expect(definition.path).to eq expected
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "full_path" do
|
35
|
+
it "joins the path with Rails.root" do
|
36
|
+
definition = Definition.new("searches", 15)
|
37
|
+
|
38
|
+
expect(definition.full_path).to eq Rails.root.join(definition.path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "version" do
|
43
|
+
it "pads the version number with 0" do
|
44
|
+
definition = Definition.new(:_, 1)
|
45
|
+
|
46
|
+
expect(definition.version).to eq "01"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "doesn't pad more than 2 characters" do
|
50
|
+
definition = Definition.new(:_, 15)
|
51
|
+
|
52
|
+
expect(definition.version).to eq "15"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -2,7 +2,7 @@ require "spec_helper"
|
|
2
2
|
|
3
3
|
class Search < ActiveRecord::Base; end
|
4
4
|
|
5
|
-
describe Scenic::
|
5
|
+
describe Scenic::SchemaDumper, :db do
|
6
6
|
it "dumps a create_view for a view in the database" do
|
7
7
|
view_definition = "SELECT 'needle'::text AS haystack"
|
8
8
|
Search.connection.create_view :searches, sql_definition: view_definition
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
describe Scenic::Statements do
|
5
|
+
before do
|
6
|
+
allow(Scenic).to receive(:database)
|
7
|
+
.and_return(class_double("Scenic::Adapters::Postgres").as_null_object)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "create_view" do
|
11
|
+
it "creates a view from a file" do
|
12
|
+
version = 15
|
13
|
+
definition_stub = instance_double("Definition", to_sql: "foo")
|
14
|
+
allow(Definition).to receive(:new)
|
15
|
+
.with(:views, version)
|
16
|
+
.and_return(definition_stub)
|
17
|
+
|
18
|
+
connection.create_view :views, version: version
|
19
|
+
|
20
|
+
expect(Scenic.database).to have_received(:create_view)
|
21
|
+
.with(:views, definition_stub.to_sql)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "creates a view from a text definition" do
|
25
|
+
sql_definition = "a defintion"
|
26
|
+
|
27
|
+
connection.create_view(:views, sql_definition: sql_definition)
|
28
|
+
|
29
|
+
expect(Scenic.database).to have_received(:create_view)
|
30
|
+
.with(:views, sql_definition)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "raises an error if neither version nor sql_defintion are provided" do
|
34
|
+
expect do
|
35
|
+
connection.create_view :foo, version: nil, sql_definition: nil
|
36
|
+
end.to raise_error ArgumentError
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "drop_view" do
|
41
|
+
it "removes a view from the database" do
|
42
|
+
connection.drop_view :name
|
43
|
+
|
44
|
+
expect(Scenic.database).to have_received(:drop_view).with(:name)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "update_view" do
|
49
|
+
it "drops the existing version and creates the new" do
|
50
|
+
definition = instance_double("Definition", to_sql: "definition")
|
51
|
+
allow(Definition).to receive(:new)
|
52
|
+
.with(:name, 3)
|
53
|
+
.and_return(definition)
|
54
|
+
|
55
|
+
connection.update_view(:name, version: 3)
|
56
|
+
|
57
|
+
expect(Scenic.database).to have_received(:drop_view).with(:name)
|
58
|
+
expect(Scenic.database).to have_received(:create_view)
|
59
|
+
.with(:name, definition.to_sql)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "raises an error if not supplied a version" do
|
63
|
+
expect { connection.update_view :views }
|
64
|
+
.to raise_error(ArgumentError, /version is required/)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def connection
|
69
|
+
Class.new { extend Statements }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/spec/smoke
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -euo pipefail
|
4
|
+
|
5
|
+
setup() {
|
6
|
+
cd spec/dummy
|
7
|
+
git init
|
8
|
+
git add -A
|
9
|
+
git commit --no-gpg-sign --message "initial"
|
10
|
+
}
|
11
|
+
|
12
|
+
teardown() {
|
13
|
+
git add -A
|
14
|
+
git reset --hard HEAD
|
15
|
+
rm -rf .git/
|
16
|
+
rake db:drop db:create
|
17
|
+
}
|
18
|
+
|
19
|
+
trap teardown EXIT
|
20
|
+
|
21
|
+
verifySearchResults() {
|
22
|
+
echo "verify search results"
|
23
|
+
local expectedResult=$1
|
24
|
+
local actualResult=$(rails runner "puts Search.first.results")
|
25
|
+
[[ "$actualResult" == "$expectedResult" ]] || exit 1
|
26
|
+
echo "[success]"
|
27
|
+
}
|
28
|
+
|
29
|
+
writeToFileAndMigrateAndVerifySearchResults() {
|
30
|
+
echo "write search definition and migrate"
|
31
|
+
local version=$1
|
32
|
+
local expectedResult=$2
|
33
|
+
echo "SELECT '$expectedResult'::text AS results" >> db/views/searches_v$version\.sql
|
34
|
+
rake db:migrate
|
35
|
+
echo "[success]"
|
36
|
+
verifySearchResults $expectedResult
|
37
|
+
}
|
38
|
+
|
39
|
+
main() {
|
40
|
+
setup
|
41
|
+
echo "rails generate scenic:model search"
|
42
|
+
rails generate scenic:model search
|
43
|
+
[[ -f db/views/searches_v01.sql ]] || exit 1
|
44
|
+
[[ -f app/models/search.rb ]] || exit 1
|
45
|
+
[[ -n "$(find db/migrate -maxdepth 1 -name "*create_searches.rb" -print -quit)" ]] || exit 1
|
46
|
+
echo "[success]"
|
47
|
+
|
48
|
+
writeToFileAndMigrateAndVerifySearchResults "01" "search-results"
|
49
|
+
|
50
|
+
echo "rails generate scenic:view search (to get updates search view)"
|
51
|
+
rails generate scenic:view search
|
52
|
+
[[ -f db/views/searches_v02.sql ]] || exit 1
|
53
|
+
[[ -n "$(find db/migrate -maxdepth 1 -name "*update_searches_to_version_2.rb" -print -quit)" ]] || exit 1
|
54
|
+
echo "[success]"
|
55
|
+
|
56
|
+
writeToFileAndMigrateAndVerifySearchResults "02" "different-results"
|
57
|
+
|
58
|
+
echo "rake db:rollback"
|
59
|
+
rake db:rollback
|
60
|
+
echo "[success]"
|
61
|
+
|
62
|
+
verifySearchResults "search-results"
|
63
|
+
|
64
|
+
echo "rails destroy scenic:view search"
|
65
|
+
rails destroy scenic:view search
|
66
|
+
[[ ! -f db/views/searches_v02.sql ]] || exit 1
|
67
|
+
[[ -z "$(find db/migrate -maxdepth 1 -name "*update_searches_to_version_2.rb" -print -quit)" ]] || exit 1
|
68
|
+
[[ -f db/views/searches_v01.sql ]] || exit 1
|
69
|
+
[[ -f app/models/search.rb ]] || exit 1
|
70
|
+
[[ -n "$(find db/migrate -maxdepth 1 -name "*create_searches.rb" -print -quit)" ]] || exit 1
|
71
|
+
echo "[success]"
|
72
|
+
|
73
|
+
echo "rake db:rollback"
|
74
|
+
rake db:rollback
|
75
|
+
echo "[success]"
|
76
|
+
|
77
|
+
echo "rails destroy scenic:view search"
|
78
|
+
rails destroy scenic:model search
|
79
|
+
[[ ! -f db/views/searches_v01.sql ]] || exit 1
|
80
|
+
[[ ! -f app/models/search.rb ]] || exit 1
|
81
|
+
[[ -z "$(find db/migrate -maxdepth 1 -name "*create_searches.rb" -print -quit)" ]] || exit 1
|
82
|
+
echo "[success]"
|
83
|
+
echo "[done]"
|
84
|
+
}
|
85
|
+
|
86
|
+
main $*
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module ViewDefinitionHelpers
|
2
2
|
def with_view_definition(name, version, schema)
|
3
|
-
|
4
|
-
File.open(
|
3
|
+
definition = Scenic::Definition.new(name, version)
|
4
|
+
File.open(definition.full_path, "w") { |f| f.write(schema) }
|
5
5
|
yield
|
6
6
|
ensure
|
7
|
-
File.delete
|
7
|
+
File.delete definition.full_path
|
8
8
|
end
|
9
9
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: scenic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Derek Prior
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-08-
|
12
|
+
date: 2014-08-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -152,6 +152,7 @@ files:
|
|
152
152
|
- ".travis.yml"
|
153
153
|
- Gemfile
|
154
154
|
- LICENSE.txt
|
155
|
+
- NEWS.md
|
155
156
|
- README.md
|
156
157
|
- Rakefile
|
157
158
|
- lib/generators/scenic/model/USAGE
|
@@ -159,14 +160,18 @@ files:
|
|
159
160
|
- lib/generators/scenic/model/templates/model.erb
|
160
161
|
- lib/generators/scenic/view/USAGE
|
161
162
|
- lib/generators/scenic/view/templates/db/migrate/create_view.erb
|
163
|
+
- lib/generators/scenic/view/templates/db/migrate/update_view.erb
|
162
164
|
- lib/generators/scenic/view/view_generator.rb
|
163
165
|
- lib/scenic.rb
|
164
|
-
- lib/scenic/
|
165
|
-
- lib/scenic/
|
166
|
-
- lib/scenic/
|
167
|
-
- lib/scenic/
|
166
|
+
- lib/scenic/adapters/postgres.rb
|
167
|
+
- lib/scenic/command_recorder.rb
|
168
|
+
- lib/scenic/command_recorder/statement_arguments.rb
|
169
|
+
- lib/scenic/definition.rb
|
168
170
|
- lib/scenic/railtie.rb
|
171
|
+
- lib/scenic/schema_dumper.rb
|
172
|
+
- lib/scenic/statements.rb
|
169
173
|
- lib/scenic/version.rb
|
174
|
+
- lib/scenic/view.rb
|
170
175
|
- scenic.gemspec
|
171
176
|
- spec/dummy/.gitignore
|
172
177
|
- spec/dummy/Rakefile
|
@@ -180,14 +185,18 @@ files:
|
|
180
185
|
- spec/dummy/config/environment.rb
|
181
186
|
- spec/dummy/config/environments/development.rb
|
182
187
|
- spec/dummy/config/environments/test.rb
|
188
|
+
- spec/dummy/db/migrate/.keep
|
183
189
|
- spec/dummy/db/views/.keep
|
184
190
|
- spec/generators/scenic/model/model_generator_spec.rb
|
185
191
|
- spec/generators/scenic/view/view_generator_spec.rb
|
186
192
|
- spec/integration/revert_spec.rb
|
187
|
-
- spec/scenic/
|
188
|
-
- spec/scenic/
|
189
|
-
- spec/scenic/
|
190
|
-
- spec/scenic/
|
193
|
+
- spec/scenic/adapters/postgres_spec.rb
|
194
|
+
- spec/scenic/command_recorder/statement_arguments_spec.rb
|
195
|
+
- spec/scenic/command_recorder_spec.rb
|
196
|
+
- spec/scenic/definition_spec.rb
|
197
|
+
- spec/scenic/schema_dumper_spec.rb
|
198
|
+
- spec/scenic/statements_spec.rb
|
199
|
+
- spec/smoke
|
191
200
|
- spec/spec_helper.rb
|
192
201
|
- spec/support/generator_spec_setup.rb
|
193
202
|
- spec/support/view_definition_helpers.rb
|
@@ -228,14 +237,18 @@ test_files:
|
|
228
237
|
- spec/dummy/config/environment.rb
|
229
238
|
- spec/dummy/config/environments/development.rb
|
230
239
|
- spec/dummy/config/environments/test.rb
|
240
|
+
- spec/dummy/db/migrate/.keep
|
231
241
|
- spec/dummy/db/views/.keep
|
232
242
|
- spec/generators/scenic/model/model_generator_spec.rb
|
233
243
|
- spec/generators/scenic/view/view_generator_spec.rb
|
234
244
|
- spec/integration/revert_spec.rb
|
235
|
-
- spec/scenic/
|
236
|
-
- spec/scenic/
|
237
|
-
- spec/scenic/
|
238
|
-
- spec/scenic/
|
245
|
+
- spec/scenic/adapters/postgres_spec.rb
|
246
|
+
- spec/scenic/command_recorder/statement_arguments_spec.rb
|
247
|
+
- spec/scenic/command_recorder_spec.rb
|
248
|
+
- spec/scenic/definition_spec.rb
|
249
|
+
- spec/scenic/schema_dumper_spec.rb
|
250
|
+
- spec/scenic/statements_spec.rb
|
251
|
+
- spec/smoke
|
239
252
|
- spec/spec_helper.rb
|
240
253
|
- spec/support/generator_spec_setup.rb
|
241
254
|
- spec/support/view_definition_helpers.rb
|
@@ -1,44 +0,0 @@
|
|
1
|
-
require "scenic/active_record/command_recorder/statement_arguments"
|
2
|
-
|
3
|
-
module Scenic
|
4
|
-
module ActiveRecord
|
5
|
-
module CommandRecorder
|
6
|
-
def create_view(*args)
|
7
|
-
record(:create_view, args)
|
8
|
-
end
|
9
|
-
|
10
|
-
def drop_view(*args)
|
11
|
-
record(:drop_view, args)
|
12
|
-
end
|
13
|
-
|
14
|
-
def update_view(*args)
|
15
|
-
record(:update_view, args)
|
16
|
-
end
|
17
|
-
|
18
|
-
def invert_create_view(args)
|
19
|
-
[:drop_view, args]
|
20
|
-
end
|
21
|
-
|
22
|
-
def invert_drop_view(args)
|
23
|
-
perform_scenic_inversion(:create_view, args)
|
24
|
-
end
|
25
|
-
|
26
|
-
def invert_update_view(args)
|
27
|
-
perform_scenic_inversion(:update_view, args)
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def perform_scenic_inversion(method, args)
|
33
|
-
scenic_args = StatementArguments.new(args)
|
34
|
-
|
35
|
-
if scenic_args.revert_to_version.nil?
|
36
|
-
message = "#{method} is reversible only if given a revert_to_version"
|
37
|
-
raise ::ActiveRecord::IrreversibleMigration, message
|
38
|
-
end
|
39
|
-
|
40
|
-
[method, scenic_args.invert_version.to_a]
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
module Scenic
|
2
|
-
module ActiveRecord
|
3
|
-
module CommandRecorder
|
4
|
-
class StatementArguments
|
5
|
-
def initialize(args)
|
6
|
-
@args = args.freeze
|
7
|
-
end
|
8
|
-
|
9
|
-
def view
|
10
|
-
@args[0]
|
11
|
-
end
|
12
|
-
|
13
|
-
def version
|
14
|
-
options[:version]
|
15
|
-
end
|
16
|
-
|
17
|
-
def revert_to_version
|
18
|
-
options[:revert_to_version]
|
19
|
-
end
|
20
|
-
|
21
|
-
def invert_version
|
22
|
-
StatementArguments.new([view, options_for_revert])
|
23
|
-
end
|
24
|
-
|
25
|
-
def to_a
|
26
|
-
@args.to_a
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def options
|
32
|
-
@options ||= @args[1] || {}
|
33
|
-
end
|
34
|
-
|
35
|
-
def options_for_revert
|
36
|
-
options.clone.tap do |revert_options|
|
37
|
-
revert_options[:version] = revert_to_version
|
38
|
-
revert_options.delete(:revert_to_version)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
require "rails"
|
2
|
-
|
3
|
-
module Scenic
|
4
|
-
module ActiveRecord
|
5
|
-
module SchemaDumper
|
6
|
-
extend ActiveSupport::Concern
|
7
|
-
|
8
|
-
included { alias_method_chain :tables, :views }
|
9
|
-
|
10
|
-
def tables_with_views(stream)
|
11
|
-
tables_without_views(stream)
|
12
|
-
views(stream)
|
13
|
-
end
|
14
|
-
|
15
|
-
def views(stream)
|
16
|
-
defined_views.sort.each do |view_name|
|
17
|
-
next if ["schema_migrations", ignore_tables].flatten.any? do |ignored|
|
18
|
-
case ignored
|
19
|
-
when String; remove_prefix_and_suffix(view_name) == ignored
|
20
|
-
when Regexp; remove_prefix_and_suffix(view_name) =~ ignored
|
21
|
-
else
|
22
|
-
raise StandardError, "ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values."
|
23
|
-
end
|
24
|
-
end
|
25
|
-
view(view_name, stream)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def view(name, stream)
|
30
|
-
stream.puts(<<-DEFINITION)
|
31
|
-
create_view :#{name}, sql_definition:<<-\SQL
|
32
|
-
#{views_with_definitions[name]}
|
33
|
-
SQL
|
34
|
-
DEFINITION
|
35
|
-
stream
|
36
|
-
end
|
37
|
-
|
38
|
-
def defined_views
|
39
|
-
views_with_definitions.keys
|
40
|
-
end
|
41
|
-
|
42
|
-
def views_with_definitions
|
43
|
-
@views_with_definitions ||=
|
44
|
-
Hash[@connection.execute(<<-SQL, 'SCHEMA').values]
|
45
|
-
SELECT viewname, definition
|
46
|
-
FROM pg_views
|
47
|
-
WHERE schemaname = ANY (current_schemas(false))
|
48
|
-
SQL
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module Scenic
|
2
|
-
module ActiveRecord
|
3
|
-
module Statements
|
4
|
-
def create_view(name, version: 1, sql_definition: nil)
|
5
|
-
if version.nil? && sql_definition.nil?
|
6
|
-
raise(
|
7
|
-
ArgumentError,
|
8
|
-
"view_definition or version_number must be specified"
|
9
|
-
)
|
10
|
-
end
|
11
|
-
|
12
|
-
sql_definition ||= definition(name, version)
|
13
|
-
|
14
|
-
execute "CREATE VIEW #{name} AS #{sql_definition};"
|
15
|
-
end
|
16
|
-
|
17
|
-
def drop_view(name, revert_to_version: nil)
|
18
|
-
execute "DROP VIEW #{name};"
|
19
|
-
end
|
20
|
-
|
21
|
-
def update_view(name, version: nil, revert_to_version: nil)
|
22
|
-
if version.nil?
|
23
|
-
raise ArgumentError, "version is required"
|
24
|
-
end
|
25
|
-
|
26
|
-
drop_view(name)
|
27
|
-
create_view(name, version: version)
|
28
|
-
end
|
29
|
-
|
30
|
-
private
|
31
|
-
|
32
|
-
def definition(name, version)
|
33
|
-
File.read(::Rails.root.join("db", "views", "#{name}_v#{version}.sql"))
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
@@ -1,82 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
class View < ActiveRecord::Base
|
4
|
-
end
|
5
|
-
|
6
|
-
describe Scenic::ActiveRecord::Statements, :db do
|
7
|
-
describe "create_view" do
|
8
|
-
it "creates a view from a file" do
|
9
|
-
with_view_definition :views, 1, "SELECT text 'Hello World' AS hello" do
|
10
|
-
View.connection.create_view :views
|
11
|
-
|
12
|
-
expect(View.all.pluck(:hello)).to eq ["Hello World"]
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
it "creates a view from a text definition" do
|
17
|
-
View.connection.create_view :views,
|
18
|
-
sql_definition: "SELECT text 'Goodbye' AS hello"
|
19
|
-
|
20
|
-
expect(View.all.pluck(:hello)).to eq ["Goodbye"]
|
21
|
-
end
|
22
|
-
|
23
|
-
it "creates a view from a specific version" do
|
24
|
-
with_view_definition :views, 15, "SELECT text 'Hello Earth East 15' AS hello" do
|
25
|
-
View.connection.create_view :views, version: 15
|
26
|
-
|
27
|
-
expect(View.all.pluck(:hello)).to eq ["Hello Earth East 15"]
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
it "raises an error if both arguments are nil" do
|
32
|
-
expect do
|
33
|
-
View.connection.create_view :whatever,
|
34
|
-
version: nil,
|
35
|
-
sql_definition: nil
|
36
|
-
end.to raise_error ArgumentError
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
describe "drop_view" do
|
41
|
-
it "removes a view from the database" do
|
42
|
-
with_view_definition :things, 1, "SELECT text 'Hi' AS greeting" do
|
43
|
-
View.connection.create_view :things
|
44
|
-
|
45
|
-
View.connection.drop_view :things
|
46
|
-
|
47
|
-
expect(views).not_to include "things"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
describe "update_view" do
|
53
|
-
it "updates an existing view in the database" do
|
54
|
-
with_view_definition :views, 1, "SELECT text 'Hi' AS greeting" do
|
55
|
-
View.connection.create_view :views
|
56
|
-
with_view_definition :views, 2, "SELECT text 'Hello' AS greeting" do
|
57
|
-
View.connection.update_view :views, version: 2
|
58
|
-
|
59
|
-
expect(View.all.pluck(:greeting)).to eq ['Hello']
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
it "raises an error if not supplied a version" do
|
65
|
-
expect { View.connection.update_view :views }
|
66
|
-
.to raise_error(ArgumentError, /version is required/)
|
67
|
-
end
|
68
|
-
|
69
|
-
it "raises an error if the view to be updated does not exist" do
|
70
|
-
with_view_definition :views, 2, "SELECT text 'Hi' as greeting" do
|
71
|
-
expect { View.connection.update_view :views, version: 2 }
|
72
|
-
.to raise_error(ActiveRecord::StatementInvalid, /does not exist/)
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def views
|
78
|
-
ActiveRecord::Base.connection
|
79
|
-
.execute("SELECT table_name FROM INFORMATION_SCHEMA.views")
|
80
|
-
.map(&:values).flatten
|
81
|
-
end
|
82
|
-
end
|