schema_plus_views 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +61 -1
- data/lib/schema_plus/views/active_record/connection_adapters/abstract_adapter.rb +23 -14
- data/lib/schema_plus/views/active_record/connection_adapters/mysql2_adapter.rb +18 -15
- data/lib/schema_plus/views/active_record/connection_adapters/postgresql_adapter.rb +16 -12
- data/lib/schema_plus/views/active_record/connection_adapters/sqlite3_adapter.rb +8 -3
- data/lib/schema_plus/views/middleware.rb +22 -0
- data/lib/schema_plus/views/version.rb +1 -1
- data/spec/dumper_spec.rb +0 -1
- data/spec/introspection_spec.rb +4 -4
- data/spec/middleware_spec.rb +106 -0
- data/spec/spec_helper.rb +3 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b52a3bd48e7f5213ce4cf51909573fd2d02a0fe8
|
4
|
+
data.tar.gz: 768b79ea664fa768a68dcec297044845a1ec116d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f1571bf1b1ba2a42dd6f9829711865a436bab9531f8a8b9154e9194481bb2c2c699afec992128e97c5b890d89bb6012da5ebb330e1b41dd1cc78da8a3fd6dbe
|
7
|
+
data.tar.gz: 63ae45c9d04e7939aa3ec5d8cae671b7b9bb0acfb87895d912e85943d076a0479d3325579f35f2c8027db10a75f49bf3477184b7d39c666ef8064cf1b8f97d59
|
data/README.md
CHANGED
@@ -56,7 +56,6 @@ Additional options can be provided:
|
|
56
56
|
|
57
57
|
* `:allow_replace => true` will use the command "CREATE OR REPLACE" when creating the view, for seamlessly redefining the view even if other views depend on it. It's only supported by MySQL and PostgreSQL, and each has some limitations on when a view can be replaced; see their docs for details.
|
58
58
|
|
59
|
-
|
60
59
|
SchemaPlus::Views also arranges to include the `create_view` statements (with literal SQL) in the schema dump.
|
61
60
|
|
62
61
|
### Dropping views
|
@@ -106,9 +105,70 @@ connection.view_definition(view_name) # => returns SQL string
|
|
106
105
|
|
107
106
|
This returns just the body of the definition, i.e. the part after the `CREATE VIEW 'name' AS` command.
|
108
107
|
|
108
|
+
## Customization API: Middleware Stacks
|
109
|
+
|
110
|
+
All the methods defined by SchemaPlus::Views provide middleware stacks, in case you need to do any custom filtering, rewriting, triggering, or whatever. For info on how to use middleware stacks, see the READMEs of [schema_monkey](https://SchemaPlus/schema_monkey) and [schema_plus_core](https://SchemaPlus/schema_plus_core).
|
111
|
+
|
112
|
+
|
113
|
+
### `Schema::Views` stack
|
114
|
+
|
115
|
+
Wraps the `connection.views` method. Env contains:
|
116
|
+
|
117
|
+
Env Field | Description | Initialized
|
118
|
+
--- | --- | ---
|
119
|
+
`:views` | The result of the lookup | `[]`
|
120
|
+
`:connection` | The current ActiveRecord connection | *context*
|
121
|
+
`:query_name` | Optional label for ActiveRecord logging | *arg*
|
122
|
+
|
123
|
+
The base implementation appends its results to `env.views`
|
124
|
+
|
125
|
+
### `Schema::ViewDefinition` stack
|
126
|
+
|
127
|
+
Wraps the `connection.view_definition` method. Env contains:
|
128
|
+
|
129
|
+
Env Field | Description | Initialized
|
130
|
+
--- | --- | ---
|
131
|
+
`:connection` | The current ActiveRecord connection | *context*
|
132
|
+
`:view_name` | The view to look up | *arg*
|
133
|
+
`:query_name` | Optional label for ActiveRecord logging | *arg*
|
134
|
+
`:definition` | The view definition SQL | `nil`
|
135
|
+
|
136
|
+
The base implementation looks up the definition of the view named
|
137
|
+
`env.view_name` and assigns the result to `env.definition`
|
138
|
+
|
139
|
+
### `Migration::CreateView` stack
|
140
|
+
|
141
|
+
Wraps the `migration.create_view` method. Env contains:
|
142
|
+
|
143
|
+
Env Field | Description | Initialized
|
144
|
+
--- | --- | ---
|
145
|
+
`:connection` | The current ActiveRecord connection | *context*
|
146
|
+
`:view_name` | The view name | *arg*
|
147
|
+
`:definition` | The view definition SQL | *arg*
|
148
|
+
`:options` | Create view options | *arg*
|
149
|
+
|
150
|
+
The base implementation creates the view named `env.view_name` using the
|
151
|
+
definition in `env.definition` with options in `env.options`
|
152
|
+
|
153
|
+
### `Migration::DropView` stack
|
154
|
+
|
155
|
+
Wraps the `migration.drop_view` method. Env contains:
|
156
|
+
|
157
|
+
Env Field | Description | Initialized
|
158
|
+
--- | --- | ---
|
159
|
+
`:connection` | The current ActiveRecord connection | *context*
|
160
|
+
`:view_name` | The view name | *arg*
|
161
|
+
`:options` | Drop view options | *arg*
|
162
|
+
|
163
|
+
The base implementation drops the view named `env.view_name` using the
|
164
|
+
options in `env.options`
|
165
|
+
|
109
166
|
|
110
167
|
## History
|
111
168
|
|
169
|
+
* 0.3.0
|
170
|
+
- Added middleware stacks
|
171
|
+
- Bug fix: view_definition: strip white space from result (postgresql)
|
112
172
|
* 0.2.3 - Remove unnecessary escaping in dump; use single-quote heredoc
|
113
173
|
* 0.2.2 - Prettier dumps: use heredoc for definition string
|
114
174
|
* 0.2.1 - Fix db:rollback
|
@@ -5,27 +5,36 @@ module SchemaPlus::Views
|
|
5
5
|
# Create a view given the SQL definition. Specify :force => true
|
6
6
|
# to first drop the view if it already exists.
|
7
7
|
def create_view(view_name, definition, options={})
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
SchemaMonkey::Middleware::Migration::CreateView.start(connection: self, view_name: view_name, definition: definition, options: options) do |env|
|
9
|
+
definition = env.definition
|
10
|
+
view_name = env.view_name
|
11
|
+
options = env.options
|
12
|
+
definition = definition.to_sql if definition.respond_to? :to_sql
|
13
|
+
if options[:force]
|
14
|
+
drop_view(view_name, if_exists: true)
|
15
|
+
end
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
17
|
+
command = if options[:allow_replace]
|
18
|
+
"CREATE OR REPLACE"
|
19
|
+
else
|
20
|
+
"CREATE"
|
21
|
+
end
|
18
22
|
|
19
|
-
|
23
|
+
execute "#{command} VIEW #{quote_table_name(view_name)} AS #{definition}"
|
24
|
+
end
|
20
25
|
end
|
21
26
|
|
22
27
|
# Drop the named view. Specify :if_exists => true
|
23
28
|
# to fail silently if the view doesn't exist.
|
24
29
|
def drop_view(view_name, options = {})
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
30
|
+
SchemaMonkey::Middleware::Migration::DropView.start(connection: self, view_name: view_name, options: options) do |env|
|
31
|
+
view_name = env.view_name
|
32
|
+
options = env.options
|
33
|
+
sql = "DROP VIEW"
|
34
|
+
sql += " IF EXISTS" if options[:if_exists]
|
35
|
+
sql += " #{quote_table_name(view_name)}"
|
36
|
+
execute sql
|
37
|
+
end
|
29
38
|
end
|
30
39
|
|
31
40
|
#####################################################################
|
@@ -4,24 +4,27 @@ module SchemaPlus::Views
|
|
4
4
|
module Mysql2Adapter
|
5
5
|
|
6
6
|
def views(name = nil)
|
7
|
-
views
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
views
|
7
|
+
SchemaMonkey::Middleware::Schema::Views.start(connection: self, query_name: name, views: []) { |env|
|
8
|
+
select_all("SELECT table_name FROM information_schema.views WHERE table_schema = SCHEMA()", env.query_name).each do |row|
|
9
|
+
env.views << row["table_name"]
|
10
|
+
end
|
11
|
+
}.views
|
12
12
|
end
|
13
13
|
|
14
14
|
def view_definition(view_name, name = nil)
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
15
|
+
SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name) { |env|
|
16
|
+
results = select_all("SELECT view_definition, check_option FROM information_schema.views WHERE table_schema = SCHEMA() AND table_name = #{quote(view_name)}", name)
|
17
|
+
if results.any?
|
18
|
+
row = results.first
|
19
|
+
sql = row["view_definition"]
|
20
|
+
sql.gsub!(%r{#{quote_table_name(current_database)}[.]}, '')
|
21
|
+
case row["check_option"]
|
22
|
+
when "CASCADED" then sql += " WITH CASCADED CHECK OPTION"
|
23
|
+
when "LOCAL" then sql += " WITH LOCAL CHECK OPTION"
|
24
|
+
end
|
25
|
+
env.definition = sql
|
26
|
+
end
|
27
|
+
}.definition
|
25
28
|
end
|
26
29
|
|
27
30
|
end
|
@@ -4,25 +4,29 @@ module SchemaPlus::Views
|
|
4
4
|
module PostgresqlAdapter
|
5
5
|
|
6
6
|
def views(name = nil) #:nodoc:
|
7
|
-
|
7
|
+
SchemaMonkey::Middleware::Schema::Views.start(connection: self, query_name: name, views: []) { |env|
|
8
|
+
sql = <<-SQL
|
8
9
|
SELECT viewname
|
9
10
|
FROM pg_views
|
10
11
|
WHERE schemaname = ANY (current_schemas(false))
|
11
12
|
AND viewname NOT LIKE 'pg\_%'
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
SQL
|
14
|
+
sql += " AND schemaname != 'postgis'" if adapter_name == 'PostGIS'
|
15
|
+
env.views += env.connection.query(sql, env.query_name).map { |row| row[0] }
|
16
|
+
}.views
|
15
17
|
end
|
16
18
|
|
17
19
|
def view_definition(view_name, name = nil) #:nodoc:
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name) { |env|
|
21
|
+
result = env.connection.query(<<-SQL, name)
|
22
|
+
SELECT pg_get_viewdef(oid)
|
23
|
+
FROM pg_class
|
24
|
+
WHERE relkind = 'v'
|
25
|
+
AND relname = '#{env.view_name}'
|
26
|
+
SQL
|
27
|
+
row = result.first
|
28
|
+
env.definition = row.first.chomp(';').strip unless row.nil?
|
29
|
+
}.definition
|
26
30
|
end
|
27
31
|
|
28
32
|
end
|
@@ -4,12 +4,17 @@ module SchemaPlus::Views
|
|
4
4
|
module Sqlite3Adapter
|
5
5
|
|
6
6
|
def views(name = nil)
|
7
|
-
|
7
|
+
SchemaMonkey::Middleware::Schema::Views.start(connection: self, query_name: name, views: []) { |env|
|
8
|
+
env.views += env.connection.execute("SELECT name FROM sqlite_master WHERE type='view'", env.query_name).collect{|row| row["name"]}
|
9
|
+
}.views
|
8
10
|
end
|
9
11
|
|
10
12
|
def view_definition(view_name, name = nil)
|
11
|
-
|
12
|
-
|
13
|
+
SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name) { |env|
|
14
|
+
sql = env.connection.execute("SELECT sql FROM sqlite_master WHERE type='view' AND name=#{quote(env.view_name)}", env.query_name).collect{|row| row["sql"]}.first
|
15
|
+
sql.sub!(/^CREATE VIEW \S* AS\s+/im, '') unless sql.nil?
|
16
|
+
env.definition = sql
|
17
|
+
}.definition
|
13
18
|
end
|
14
19
|
|
15
20
|
end
|
@@ -50,6 +50,28 @@ module SchemaPlus::Views
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Define new middleware stacks patterned on SchemaPlus::Core's naming
|
56
|
+
# for tables
|
57
|
+
|
58
|
+
module Schema
|
59
|
+
module Views
|
60
|
+
ENV = [:connection, :query_name, :views]
|
61
|
+
end
|
62
|
+
module ViewDefinition
|
63
|
+
ENV = [:connection, :view_name, :query_name, :definition]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module Migration
|
68
|
+
module CreateView
|
69
|
+
ENV = [:connection, :view_name, :definition, :options]
|
70
|
+
end
|
71
|
+
module DropView
|
72
|
+
ENV = [:connection, :view_name, :options]
|
73
|
+
end
|
74
|
+
end
|
53
75
|
end
|
54
76
|
|
55
77
|
end
|
data/spec/dumper_spec.rb
CHANGED
@@ -16,7 +16,6 @@ describe "Dumper" do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
it "should include view definitions" do
|
19
|
-
puts dump
|
20
19
|
expect(dump).to match(view_re("a_ones", /SELECT .*b.*,.*s.* FROM .*items.* WHERE \(?.*a.* = 1\)?/mi))
|
21
20
|
expect(dump).to match(view_re("ab_ones", /SELECT .*s.* FROM .*a_ones.* WHERE \(?.*b.* = 1\)?/mi))
|
22
21
|
end
|
data/spec/introspection_spec.rb
CHANGED
@@ -17,8 +17,8 @@ describe "Introspection" do
|
|
17
17
|
|
18
18
|
it "should list all views" do
|
19
19
|
expect(connection.views.sort).to eq(%W[a_ones ab_ones])
|
20
|
-
expect(connection.view_definition('a_ones')).to match(%r{^
|
21
|
-
expect(connection.view_definition('ab_ones')).to match(%r{^
|
20
|
+
expect(connection.view_definition('a_ones')).to match(%r{^SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1}mi)
|
21
|
+
expect(connection.view_definition('ab_ones')).to match(%r{^SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1}mi)
|
22
22
|
end
|
23
23
|
|
24
24
|
it "should ignore views named pg_*", postgresql: :only do
|
@@ -36,8 +36,8 @@ describe "Introspection" do
|
|
36
36
|
end
|
37
37
|
|
38
38
|
it "should introspect definition" do
|
39
|
-
expect(connection.view_definition('a_ones')).to match(%r{^
|
40
|
-
expect(connection.view_definition('ab_ones')).to match(%r{^
|
39
|
+
expect(connection.view_definition('a_ones')).to match(%r{^SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1}mi)
|
40
|
+
expect(connection.view_definition('ab_ones')).to match(%r{^SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1}mi)
|
41
41
|
end
|
42
42
|
|
43
43
|
context "in mysql", :mysql => :only do
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module TestMiddleware
|
4
|
+
module Middleware
|
5
|
+
|
6
|
+
module Schema
|
7
|
+
module Views
|
8
|
+
SPY = []
|
9
|
+
def after(env)
|
10
|
+
SPY << env.to_hash.except(:connection)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
module ViewDefinition
|
14
|
+
SPY = []
|
15
|
+
def after(env)
|
16
|
+
SPY << env.to_hash.except(:connection)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Migration
|
22
|
+
module CreateView
|
23
|
+
SPY = []
|
24
|
+
def after(env)
|
25
|
+
SPY << env.to_hash.except(:connection)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
module DropView
|
29
|
+
SPY = []
|
30
|
+
def after(env)
|
31
|
+
SPY << env.to_hash.except(:connection)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
SchemaMonkey.register TestMiddleware
|
40
|
+
|
41
|
+
context SchemaPlus::Views::Middleware do
|
42
|
+
|
43
|
+
let(:schema) { ActiveRecord::Schema }
|
44
|
+
let(:migration) { ActiveRecord::Migration }
|
45
|
+
let(:connection) { ActiveRecord::Base.connection }
|
46
|
+
|
47
|
+
before(:each) do
|
48
|
+
schema.define do
|
49
|
+
create_table :items, force: true do |t|
|
50
|
+
t.integer :a
|
51
|
+
end
|
52
|
+
create_view 'a_view', "select a from items"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context TestMiddleware::Middleware::Schema::Views do
|
57
|
+
it "calls middleware" do
|
58
|
+
expect(spy_on {connection.views 'qn'}).to eq({
|
59
|
+
#connection: connection,
|
60
|
+
views: ['a_view'],
|
61
|
+
query_name: 'qn'
|
62
|
+
})
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context TestMiddleware::Middleware::Schema::ViewDefinition do
|
67
|
+
it "calls middleware" do
|
68
|
+
spied = spy_on {connection.view_definition('a_view', 'qn')}
|
69
|
+
expect(spied[:view_name]).to eq('a_view')
|
70
|
+
expect(spied[:definition]).to match(%r{SELECT .*a.* FROM .*items.*}mi)
|
71
|
+
expect(spied[:query_name]).to eq('qn')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context TestMiddleware::Middleware::Migration::CreateView do
|
76
|
+
it "calls middleware" do
|
77
|
+
expect(spy_on {migration.create_view('newview', 'select a from items', force: true)}).to eq({
|
78
|
+
#connection: connection,
|
79
|
+
view_name: 'newview',
|
80
|
+
definition: 'select a from items',
|
81
|
+
options: { force: true }
|
82
|
+
})
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context TestMiddleware::Middleware::Migration::DropView do
|
87
|
+
it "calls middleware" do
|
88
|
+
expect(spy_on {migration.drop_view('a_items', if_exists: true)}).to eq({
|
89
|
+
#connection: connection,
|
90
|
+
view_name: 'a_items',
|
91
|
+
options: { if_exists: true }
|
92
|
+
})
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def spy_on
|
100
|
+
spy = described_class.const_get :SPY
|
101
|
+
spy.clear
|
102
|
+
yield
|
103
|
+
spy.first
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -21,6 +21,9 @@ RSpec.configure do |config|
|
|
21
21
|
ActiveRecord::Base.connection.tables.each do |table|
|
22
22
|
ActiveRecord::Migration.drop_table table, force: :cascade
|
23
23
|
end
|
24
|
+
ActiveRecord::Base.connection.views.each do |view|
|
25
|
+
ActiveRecord::Migration.drop_view view, force: :cascade
|
26
|
+
end
|
24
27
|
example.run
|
25
28
|
end
|
26
29
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schema_plus_views
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ronen barzel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -153,6 +153,7 @@ files:
|
|
153
153
|
- schema_plus_views.gemspec
|
154
154
|
- spec/dumper_spec.rb
|
155
155
|
- spec/introspection_spec.rb
|
156
|
+
- spec/middleware_spec.rb
|
156
157
|
- spec/migration_spec.rb
|
157
158
|
- spec/named_schemas_spec.rb
|
158
159
|
- spec/sanity_spec.rb
|
@@ -184,6 +185,7 @@ summary: Adds support for views to ActiveRecord
|
|
184
185
|
test_files:
|
185
186
|
- spec/dumper_spec.rb
|
186
187
|
- spec/introspection_spec.rb
|
188
|
+
- spec/middleware_spec.rb
|
187
189
|
- spec/migration_spec.rb
|
188
190
|
- spec/named_schemas_spec.rb
|
189
191
|
- spec/sanity_spec.rb
|