schema_plus_views 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/.travis.yml +1 -1
- data/README.md +21 -22
- data/lib/schema_plus/views/active_record/connection_adapters/abstract_adapter.rb +8 -1
- data/lib/schema_plus/views/version.rb +1 -1
- data/lib/schema_plus_views.rb +1 -1
- data/schema_plus_views.gemspec +1 -1
- data/spec/views_spec.rb +90 -91
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f120d0fd9e453a3e35a2ba8b71630edb81a93d96
|
4
|
+
data.tar.gz: 5b84b184193e12d27e883953d787d8f4b45f4743
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db00fa4def6a06f90503c86bbbca407e9e61f911c9f7e3f2d99b6ab17d0ae57759f75aa880ce8f032093adf1ff569c2207600e8ff513bc255d5adb7890121b82
|
7
|
+
data.tar.gz: 5a993567b4ba203f891b8dd205640f41bc37aea6f74f90d27bca7caab7b8fa54f2051ce055b253e2f06b84559898ec6f8fedf8154d832fcd23e981ec111a8d8c
|
data/.travis.yml
CHANGED
@@ -12,7 +12,7 @@ gemfile:
|
|
12
12
|
- gemfiles/activerecord-4.2/Gemfile.sqlite3
|
13
13
|
env: POSTGRESQL_DB_USER=postgres MYSQL_DB_USER=travis
|
14
14
|
addons:
|
15
|
-
postgresql: '9.
|
15
|
+
postgresql: '9.4'
|
16
16
|
before_script: bundle exec rake create_databases
|
17
17
|
after_script: bundle exec rake drop_databases
|
18
18
|
script: bundle exec rake travis
|
data/README.md
CHANGED
@@ -20,14 +20,6 @@ gem "schema_plus_views" # in a Gemfile
|
|
20
20
|
gem.add_dependency "schema_plus_views" # in a .gemspec
|
21
21
|
```
|
22
22
|
|
23
|
-
To use with a rails app, also include
|
24
|
-
|
25
|
-
```ruby
|
26
|
-
gem "schema_monkey_rails"
|
27
|
-
```
|
28
|
-
|
29
|
-
which creates a Railtie to that will insert SchemaPlus::Views appropriately into the rails stack. To use with Padrino, see [schema_monkey_padrino](https://github.com/SchemaPlus/schema_monkey_padrino).
|
30
|
-
|
31
23
|
<!-- SCHEMA_DEV: TEMPLATE INSTALLATION - end -->
|
32
24
|
|
33
25
|
## Compatibility
|
@@ -44,10 +36,10 @@ SchemaPlus::Views is tested on:
|
|
44
36
|
|
45
37
|
### Creating views
|
46
38
|
|
47
|
-
|
39
|
+
In a migration, a view can be created using literal SQL:
|
48
40
|
|
49
41
|
```ruby
|
50
|
-
create_view :uncommented_posts,
|
42
|
+
create_view :uncommented_posts, "SELECT * FROM posts LEFT OUTER JOIN comments ON comments.post_id = posts.id WHERE comments.id IS NULL"
|
51
43
|
```
|
52
44
|
|
53
45
|
or using an object that responds to `:to_sql`, such as a relation:
|
@@ -56,7 +48,16 @@ or using an object that responds to `:to_sql`, such as a relation:
|
|
56
48
|
create_view :posts_commented_by_staff, Post.joins(comment: user).where(users: {role: 'staff'}).uniq
|
57
49
|
```
|
58
50
|
|
59
|
-
(It's of course a questionable idea for your
|
51
|
+
(It's of course a questionable idea for your migration files to depend on your model definitions. But you *can* if you want.)
|
52
|
+
|
53
|
+
Additional options can be provided:
|
54
|
+
|
55
|
+
* `:force => true` if there's an existing view with the given name, deletes it first. Note that this could fail if another view depends on it.
|
56
|
+
|
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
|
+
|
59
|
+
|
60
|
+
SchemaPlus::Views also arranges to include the `create_view` statements (with literal SQL) in the schema dump.
|
60
61
|
|
61
62
|
### Dropping views
|
62
63
|
|
@@ -69,12 +70,12 @@ drop_view :uncommented_posts, :if_exists => true
|
|
69
70
|
|
70
71
|
### Using views
|
71
72
|
|
72
|
-
ActiveRecord models can be
|
73
|
+
ActiveRecord models can be based on views the same as ordinary tables. That is, for the above views you can define
|
73
74
|
|
74
75
|
```ruby
|
75
76
|
class UncommentedPost < ActiveRecord::Base
|
76
77
|
end
|
77
|
-
|
78
|
+
|
78
79
|
class PostCommentedByStaff < ActiveRecord::Base
|
79
80
|
table_name = "posts_commented_by_staff"
|
80
81
|
end
|
@@ -85,15 +86,15 @@ end
|
|
85
86
|
You can look up the defined views analogously to looking up tables:
|
86
87
|
|
87
88
|
```ruby
|
88
|
-
connection.tables # => array of table names
|
89
|
-
connection.views # => array of names
|
89
|
+
connection.tables # => array of table names [method provided by ActiveRecord]
|
90
|
+
connection.views # => array of view names [method provided by SchemaPlus::Views]
|
90
91
|
```
|
91
92
|
|
92
93
|
Notes:
|
93
94
|
|
94
95
|
1. For Mysql and SQLite3, ActiveRecord's `connection.tables` method would return views as well as tables; SchemaPlus::Views normalizes them to return only tables.
|
95
96
|
|
96
|
-
2. For PostgreSQL, `connection.views`
|
97
|
+
2. For PostgreSQL, `connection.views` suppresses views prefixed with `pg_` as those are presumed to be internal.
|
97
98
|
|
98
99
|
### Querying view definitions
|
99
100
|
|
@@ -108,13 +109,12 @@ This returns just the body of the definition, i.e. the part after the `CREATE VI
|
|
108
109
|
|
109
110
|
## History
|
110
111
|
|
112
|
+
* 0.2.0 - Added :allow_replace option (thanks to [@hcarver](https://github.com/hcarver))
|
111
113
|
* 0.1.0 - Initial release, extracted from schema_plus 1.x
|
112
114
|
|
113
115
|
## Development & Testing
|
114
116
|
|
115
|
-
Are you interested in contributing to SchemaPlus::Views? Thanks! Please follow
|
116
|
-
the standard protocol: fork, feature branch, develop, push, and issue pull
|
117
|
-
request.
|
117
|
+
Are you interested in contributing to SchemaPlus::Views? Thanks! Please follow the standard protocol: fork, feature branch, develop, push, and issue pull request.
|
118
118
|
|
119
119
|
Some things to know about to help you develop and test:
|
120
120
|
|
@@ -144,8 +144,8 @@ Some things to know about to help you develop and test:
|
|
144
144
|
provides middleware callback stacks to make it easy to extend
|
145
145
|
ActiveRecord's behavior. If that API is missing something you need for
|
146
146
|
your contribution, please head over to
|
147
|
-
[schema_plus_core](https://github/SchemaPlus/schema_plus_core) and open
|
148
|
-
an issue or pull request.
|
147
|
+
[schema_plus_core](https://github.com/SchemaPlus/schema_plus_core) and open
|
148
|
+
an issue or pull request.
|
149
149
|
|
150
150
|
<!-- SCHEMA_DEV: TEMPLATE USES SCHEMA_PLUS_CORE - end -->
|
151
151
|
|
@@ -155,6 +155,5 @@ Some things to know about to help you develop and test:
|
|
155
155
|
[schema_monkey](https://github.com/SchemaPlus/schema_monkey) client,
|
156
156
|
using [schema_monkey](https://github.com/SchemaPlus/schema_monkey)'s
|
157
157
|
convention-based protocols for extending ActiveRecord and using middleware stacks.
|
158
|
-
For more information see [schema_monkey](https://github.com/SchemaPlus/schema_monkey)'s README.
|
159
158
|
|
160
159
|
<!-- SCHEMA_DEV: TEMPLATE USES SCHEMA_MONKEY - end -->
|
@@ -9,7 +9,14 @@ module SchemaPlus::Views
|
|
9
9
|
if options[:force]
|
10
10
|
drop_view(view_name, if_exists: true)
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
|
+
command = if options[:allow_replace]
|
14
|
+
"CREATE OR REPLACE"
|
15
|
+
else
|
16
|
+
"CREATE"
|
17
|
+
end
|
18
|
+
|
19
|
+
execute "#{command} VIEW #{quote_table_name(view_name)} AS #{definition}"
|
13
20
|
end
|
14
21
|
|
15
22
|
# Drop the named view. Specify :if_exists => true
|
data/lib/schema_plus_views.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require_relative 'schema_plus/views
|
1
|
+
require_relative 'schema_plus/views'
|
data/schema_plus_views.gemspec
CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |gem|
|
|
23
23
|
gem.add_development_dependency "bundler", "~> 1.7"
|
24
24
|
gem.add_development_dependency "rake", "~> 10.0"
|
25
25
|
gem.add_development_dependency "rspec", "~> 3.0"
|
26
|
-
gem.add_development_dependency "schema_dev", "~> 3.
|
26
|
+
gem.add_development_dependency "schema_dev", "~> 3.3"
|
27
27
|
gem.add_development_dependency "simplecov"
|
28
28
|
gem.add_development_dependency "simplecov-gem-profile"
|
29
29
|
end
|
data/spec/views_spec.rb
CHANGED
@@ -9,7 +9,7 @@ end
|
|
9
9
|
class ABOnes < ActiveRecord::Base
|
10
10
|
end
|
11
11
|
|
12
|
-
describe
|
12
|
+
describe "Views" do
|
13
13
|
|
14
14
|
let(:schema) { ActiveRecord::Schema }
|
15
15
|
|
@@ -17,50 +17,67 @@ describe ActiveRecord do
|
|
17
17
|
|
18
18
|
let(:connection) { ActiveRecord::Base.connection }
|
19
19
|
|
20
|
-
|
20
|
+
before(:each) do
|
21
|
+
define_schema_and_data
|
22
|
+
end
|
21
23
|
|
22
|
-
|
23
|
-
define_schema_and_data
|
24
|
-
example.run
|
25
|
-
drop_definitions
|
26
|
-
end
|
24
|
+
context "querys" do
|
27
25
|
|
28
26
|
it "should query correctly" do
|
29
27
|
expect(AOnes.all.collect(&:s)).to eq(%W[one_one one_two])
|
30
28
|
expect(ABOnes.all.collect(&:s)).to eq(%W[one_one])
|
31
29
|
end
|
32
30
|
|
33
|
-
|
34
|
-
|
31
|
+
end
|
32
|
+
|
33
|
+
context "introspection" do
|
34
|
+
|
35
|
+
it "should list all views" do
|
35
36
|
expect(connection.views.sort).to eq(%W[a_ones ab_ones])
|
36
37
|
expect(connection.view_definition('a_ones')).to match(%r{^ ?SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1}mi)
|
37
38
|
expect(connection.view_definition('ab_ones')).to match(%r{^ ?SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1}mi)
|
38
39
|
end
|
39
40
|
|
41
|
+
it "should ignore views named pg_*", postgresql: :only do
|
42
|
+
begin
|
43
|
+
migration.create_view :pg_dummy_internal, "select 1"
|
44
|
+
expect(connection.views.sort).to eq(%W[a_ones ab_ones])
|
45
|
+
ensure
|
46
|
+
migration.drop_view :pg_dummy_internal
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
40
50
|
it "should not be listed as a table" do
|
41
51
|
expect(connection.tables).not_to include('a_ones')
|
42
52
|
expect(connection.tables).not_to include('ab_ones')
|
43
53
|
end
|
44
54
|
|
55
|
+
it "should introspect definition" do
|
56
|
+
expect(connection.view_definition('a_ones')).to match(%r{^ ?SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1}mi)
|
57
|
+
expect(connection.view_definition('ab_ones')).to match(%r{^ ?SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1}mi)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
45
61
|
|
46
|
-
|
62
|
+
context "dumper" do
|
63
|
+
|
64
|
+
it "should include view definitions" do
|
47
65
|
expect(dump).to match(%r{create_view "a_ones", " ?SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1.*, :force => true}mi)
|
48
66
|
expect(dump).to match(%r{create_view "ab_ones", " ?SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1.*, :force => true}mi)
|
49
67
|
end
|
50
68
|
|
51
|
-
it "should
|
69
|
+
it "should include views in dependency order" do
|
52
70
|
expect(dump).to match(%r{create_table "items".*create_view "a_ones".*create_view "ab_ones"}m)
|
53
71
|
end
|
54
72
|
|
55
|
-
it "should not
|
73
|
+
it "should not include views listed in ignore_tables" do
|
56
74
|
dump(ignore_tables: /b_/) do |dump|
|
57
75
|
expect(dump).to match(%r{create_view "a_ones", " ?SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1.*, :force => true}mi)
|
58
76
|
expect(dump).not_to match(%r{"ab_ones"})
|
59
77
|
end
|
60
78
|
end
|
61
79
|
|
62
|
-
|
63
|
-
it "dump should not reference current database" do
|
80
|
+
it "should not reference current database" do
|
64
81
|
# why check this? mysql default to providing the view definition
|
65
82
|
# with tables explicitly scoped to the current database, which
|
66
83
|
# resulted in the dump being bound to the current database. this
|
@@ -71,115 +88,97 @@ describe ActiveRecord do
|
|
71
88
|
db = connection.respond_to?(:current_database)? connection.current_database : SchemaDev::Rspec.db_configuration[:database]
|
72
89
|
expect(dump).not_to match(%r{#{connection.quote_table_name(db)}[.]})
|
73
90
|
end
|
91
|
+
end
|
74
92
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
migration.create_view('dupe_me', 'SELECT * FROM items WHERE (a=1)')
|
80
|
-
example.run
|
81
|
-
ensure
|
82
|
-
migration.drop_view('dupe_me')
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
93
|
+
context "duplicate creation" do
|
94
|
+
before(:each) do
|
95
|
+
migration.create_view('dupe_me', 'SELECT * FROM items WHERE (a=1)')
|
96
|
+
end
|
87
97
|
|
88
|
-
|
89
|
-
|
90
|
-
|
98
|
+
it "should raise an error by default" do
|
99
|
+
expect {migration.create_view('dupe_me', 'SELECT * FROM items WHERE (a=2)')}.to raise_error ActiveRecord::StatementInvalid
|
100
|
+
end
|
91
101
|
|
92
|
-
|
93
|
-
|
102
|
+
it "should override existing definition if :force true" do
|
103
|
+
migration.create_view('dupe_me', 'SELECT * FROM items WHERE (a=2)', :force => true)
|
104
|
+
expect(connection.view_definition('dupe_me')).to match(%r{WHERE .*a.*=.*2}i)
|
105
|
+
end
|
106
|
+
|
107
|
+
context "Postgres and MySQL only", :sqlite3 => :skip do
|
108
|
+
it "should override existing definition if :allow_replace is true" do
|
109
|
+
migration.create_view('dupe_me', 'SELECT * FROM items WHERE (a=2)', :allow_replace => true)
|
94
110
|
expect(connection.view_definition('dupe_me')).to match(%r{WHERE .*a.*=.*2}i)
|
95
111
|
end
|
96
112
|
end
|
113
|
+
end
|
97
114
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
115
|
+
context "dropping" do
|
116
|
+
it "should raise an error if the view doesn't exist" do
|
117
|
+
expect { migration.drop_view('doesnt_exist') }.to raise_error ActiveRecord::StatementInvalid
|
118
|
+
end
|
102
119
|
|
103
|
-
|
104
|
-
|
105
|
-
|
120
|
+
it "should fail silently when using if_exists option" do
|
121
|
+
expect { migration.drop_view('doesnt_exist', :if_exists => true) }.not_to raise_error
|
122
|
+
end
|
106
123
|
|
107
|
-
|
108
|
-
|
124
|
+
context "with a view that exists" do
|
125
|
+
before { migration.create_view('view_that_exists', 'SELECT * FROM items WHERE (a=1)') }
|
109
126
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
127
|
+
it "should succeed" do
|
128
|
+
migration.drop_view('view_that_exists')
|
129
|
+
expect(connection.views).not_to include('view_that_exists')
|
114
130
|
end
|
115
131
|
end
|
132
|
+
end
|
116
133
|
|
117
|
-
|
134
|
+
context "in mysql", :mysql => :only do
|
118
135
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
migration.drop_view :check if connection.views.include? 'check'
|
126
|
-
end
|
127
|
-
end
|
136
|
+
around(:each) do |example|
|
137
|
+
begin
|
138
|
+
migration.drop_view :check if connection.views.include? 'check'
|
139
|
+
example.run
|
140
|
+
ensure
|
141
|
+
migration.drop_view :check if connection.views.include? 'check'
|
128
142
|
end
|
143
|
+
end
|
129
144
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
145
|
+
it "should introspect WITH CHECK OPTION" do
|
146
|
+
migration.create_view :check, 'SELECT * FROM items WHERE (a=2) WITH CHECK OPTION'
|
147
|
+
expect(connection.view_definition('check')).to match(%r{WITH CASCADED CHECK OPTION$})
|
148
|
+
end
|
134
149
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
150
|
+
it "should introspect WITH CASCADED CHECK OPTION" do
|
151
|
+
migration.create_view :check, 'SELECT * FROM items WHERE (a=2) WITH CASCADED CHECK OPTION'
|
152
|
+
expect(connection.view_definition('check')).to match(%r{WITH CASCADED CHECK OPTION$})
|
153
|
+
end
|
139
154
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
end
|
155
|
+
it "should introspect WITH LOCAL CHECK OPTION" do
|
156
|
+
migration.create_view :check, 'SELECT * FROM items WHERE (a=2) WITH LOCAL CHECK OPTION'
|
157
|
+
expect(connection.view_definition('check')).to match(%r{WITH LOCAL CHECK OPTION$})
|
144
158
|
end
|
145
159
|
end
|
146
160
|
|
147
161
|
protected
|
148
162
|
|
149
163
|
def define_schema_and_data
|
150
|
-
|
151
|
-
|
152
|
-
connection.tables.each do |table| connection.drop_table table, cascade: true end
|
164
|
+
connection.views.each do |view| connection.drop_view view end
|
165
|
+
connection.tables.each do |table| connection.drop_table table, cascade: true end
|
153
166
|
|
154
|
-
|
167
|
+
schema.define do
|
155
168
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
end
|
161
|
-
|
162
|
-
create_view :a_ones, Item.select('b, s').where(:a => 1)
|
163
|
-
create_view :ab_ones, "select s from a_ones where b = 1"
|
164
|
-
create_view :pg_dummy_internal, "select 1" if SchemaDev::Rspec::Helpers.postgresql?
|
169
|
+
create_table :items, :force => true do |t|
|
170
|
+
t.integer :a
|
171
|
+
t.integer :b
|
172
|
+
t.string :s
|
165
173
|
end
|
174
|
+
|
175
|
+
create_view :a_ones, Item.select('b, s').where(:a => 1)
|
176
|
+
create_view :ab_ones, "select s from a_ones where b = 1"
|
166
177
|
end
|
167
178
|
connection.execute "insert into items (a, b, s) values (1, 1, 'one_one')"
|
168
179
|
connection.execute "insert into items (a, b, s) values (1, 2, 'one_two')"
|
169
180
|
connection.execute "insert into items (a, b, s) values (2, 1, 'two_one')"
|
170
181
|
connection.execute "insert into items (a, b, s) values (2, 2, 'two_two')"
|
171
|
-
|
172
|
-
end
|
173
|
-
|
174
|
-
def drop_definitions
|
175
|
-
migration.suppress_messages do
|
176
|
-
schema.define do
|
177
|
-
drop_view "ab_ones"
|
178
|
-
drop_view "a_ones"
|
179
|
-
drop_table "items"
|
180
|
-
drop_view :pg_dummy_internal if SchemaDev::Rspec::Helpers.postgresql?
|
181
|
-
end
|
182
|
-
end
|
183
182
|
end
|
184
183
|
|
185
184
|
def dump(opts={})
|
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.2.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-02
|
11
|
+
date: 2015-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -86,14 +86,14 @@ dependencies:
|
|
86
86
|
requirements:
|
87
87
|
- - "~>"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '3.
|
89
|
+
version: '3.3'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '3.
|
96
|
+
version: '3.3'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
98
|
name: simplecov
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|