scenic 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|