activerecord_views 0.0.9 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.markdown +35 -0
- data/lib/active_record_views.rb +92 -15
- data/lib/active_record_views/version.rb +1 -1
- data/spec/active_record_views_extension_spec.rb +82 -7
- data/spec/active_record_views_spec.rb +44 -38
- data/spec/internal/app/models/dependency_b.rb +1 -1
- data/spec/internal/app/models/dependency_c.rb +1 -1
- data/spec/internal/app/models/modified_b.rb +1 -1
- data/spec/spec_helper.rb +23 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad72d8911d4f996a5b7eb841d2a7b1f930d16aac
|
4
|
+
data.tar.gz: 1891b3ee70bfa4e3737213f285b2e350c9c628ae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dda606e695a85d2e07170c2337d315d72a23cab5e0c53650035168d520e8ebfd9661a8e78a1025a46f9c556fd080eb9da07c1101343d614bae12ca97ce209988
|
7
|
+
data.tar.gz: b84c8e82443bf416f9c754ecba2a097ed002bf7b97c2c8a24c90049e6f4984e8f3ba7626ebef7e33cc41d968401a219b1d54bcb0755ca6c2272341c9ed9ed23d
|
data/README.markdown
CHANGED
@@ -68,6 +68,35 @@ Account.includes(:account_balance).find_each do |account|
|
|
68
68
|
end
|
69
69
|
```
|
70
70
|
|
71
|
+
## Dependencies
|
72
|
+
|
73
|
+
You can use an view model from another view model or within SQL blocks in your application code.
|
74
|
+
In order to ensure the model file is loaded (and thus the view is created), you should reference
|
75
|
+
the model class when you use the view rather than using the database table name directly:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
connection.select_values <<-SQL
|
79
|
+
SELECT …
|
80
|
+
FROM …
|
81
|
+
INNER JOIN #{AccountBalance.table_name} … # use instead of account_balances
|
82
|
+
…
|
83
|
+
SQL
|
84
|
+
```
|
85
|
+
|
86
|
+
Due to the importance of ensuring view models load in the correct order, ActiveRecordViews has
|
87
|
+
a safety check which will require you to specify the dependency explicitly if your view refers
|
88
|
+
to another view model:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
class AccountBalance < ActiveRecord::Base
|
92
|
+
is_view
|
93
|
+
end
|
94
|
+
|
95
|
+
class AccountSummary < ActiveRecord::Base
|
96
|
+
is_view dependencies: [AccountBalance]
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
71
100
|
## Materialized views
|
72
101
|
|
73
102
|
ActiveRecordViews has support [PostgreSQL's materialized views](http://www.postgresql.org/docs/9.4/static/rules-materializedviews.html).
|
@@ -128,6 +157,12 @@ In order to keep things tidy and to avoid accidentally referencing a stale view,
|
|
128
157
|
ActiveRecordViews.drop_view connection, 'account_balances'
|
129
158
|
```
|
130
159
|
|
160
|
+
Alternatively, all view models can be dropped with the following:
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
ActiveRecordViews.drop_all_views connection
|
164
|
+
```
|
165
|
+
|
131
166
|
## Usage outside of Rails
|
132
167
|
|
133
168
|
When included in a Ruby on Rails project, ActiveRecordViews will automatically detect `.sql` files alongside models in `app/models`.
|
data/lib/active_record_views.rb
CHANGED
@@ -54,7 +54,9 @@ module ActiveRecordViews
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def self.create_view(base_connection, name, class_name, sql, options = {})
|
57
|
-
options
|
57
|
+
options = options.dup
|
58
|
+
options.assert_valid_keys :dependencies, :materialized, :unique_columns
|
59
|
+
options[:dependencies] = parse_dependencies(options[:dependencies])
|
58
60
|
|
59
61
|
without_transaction base_connection do |connection|
|
60
62
|
cache = ActiveRecordViews::ChecksumCache.new(connection)
|
@@ -66,7 +68,10 @@ module ActiveRecordViews
|
|
66
68
|
else
|
67
69
|
raise ArgumentError, 'unique_columns option requires view to be materialized' if options[:unique_columns]
|
68
70
|
begin
|
69
|
-
connection.
|
71
|
+
connection.transaction :requires_new => true do
|
72
|
+
connection.execute "CREATE OR REPLACE VIEW #{connection.quote_table_name name} AS #{sql}"
|
73
|
+
check_dependencies connection, name, class_name, options[:dependencies]
|
74
|
+
end
|
70
75
|
false
|
71
76
|
rescue ActiveRecord::StatementInvalid
|
72
77
|
true
|
@@ -75,9 +80,10 @@ module ActiveRecordViews
|
|
75
80
|
|
76
81
|
if drop_and_create
|
77
82
|
connection.transaction :requires_new => true do
|
78
|
-
|
83
|
+
without_dependants connection, name do
|
79
84
|
execute_drop_view connection, name
|
80
85
|
execute_create_view connection, name, sql, options
|
86
|
+
check_dependencies connection, name, class_name, options[:dependencies]
|
81
87
|
end
|
82
88
|
end
|
83
89
|
end
|
@@ -86,6 +92,43 @@ module ActiveRecordViews
|
|
86
92
|
end
|
87
93
|
end
|
88
94
|
|
95
|
+
def self.parse_dependencies(dependencies)
|
96
|
+
dependencies = Array(dependencies)
|
97
|
+
unless dependencies.all? { |dependency| dependency.is_a?(Class) && dependency < ActiveRecord::Base }
|
98
|
+
raise ArgumentError, 'dependencies must be ActiveRecord classes'
|
99
|
+
end
|
100
|
+
dependencies.map(&:name).sort
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.check_dependencies(connection, name, class_name, declared_class_names)
|
104
|
+
actual_class_names = get_view_direct_dependencies(connection, name).sort
|
105
|
+
|
106
|
+
missing_class_names = actual_class_names - declared_class_names
|
107
|
+
extra_class_names = declared_class_names - actual_class_names
|
108
|
+
|
109
|
+
if missing_class_names.present?
|
110
|
+
example = "is_view dependencies: [#{actual_class_names.join(', ')}]"
|
111
|
+
raise ArgumentError, <<-TEXT.squish
|
112
|
+
#{missing_class_names.to_sentence}
|
113
|
+
must be specified as
|
114
|
+
#{missing_class_names.size > 1 ? 'dependencies' : 'a dependency'}
|
115
|
+
of #{class_name}:
|
116
|
+
`#{example}`
|
117
|
+
TEXT
|
118
|
+
end
|
119
|
+
|
120
|
+
if extra_class_names.present?
|
121
|
+
raise ArgumentError, <<-TEXT.squish
|
122
|
+
#{extra_class_names.to_sentence}
|
123
|
+
#{extra_class_names.size > 1 ? 'are' : 'is'}
|
124
|
+
not
|
125
|
+
#{extra_class_names.size > 1 ? 'dependencies' : 'a dependency'}
|
126
|
+
of
|
127
|
+
#{class_name}
|
128
|
+
TEXT
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
89
132
|
def self.drop_view(base_connection, name)
|
90
133
|
without_transaction base_connection do |connection|
|
91
134
|
cache = ActiveRecordViews::ChecksumCache.new(connection)
|
@@ -94,8 +137,23 @@ module ActiveRecordViews
|
|
94
137
|
end
|
95
138
|
end
|
96
139
|
|
140
|
+
def self.drop_all_views(connection)
|
141
|
+
names = Set.new connection.select_values('SELECT name FROM active_record_views;')
|
142
|
+
|
143
|
+
func = lambda do |name|
|
144
|
+
if view_exists?(connection, name)
|
145
|
+
get_view_dependants(connection, name).each do |dependant_name, _, _, _|
|
146
|
+
func.call(dependant_name)
|
147
|
+
end
|
148
|
+
drop_view connection, name
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
names.each { |name| func.call(name) }
|
153
|
+
end
|
154
|
+
|
97
155
|
def self.execute_create_view(connection, name, sql, options)
|
98
|
-
options.assert_valid_keys :materialized, :unique_columns
|
156
|
+
options.assert_valid_keys :dependencies, :materialized, :unique_columns
|
99
157
|
sql = sql.sub(/;\s*\z/, '')
|
100
158
|
|
101
159
|
if options[:materialized]
|
@@ -146,7 +204,26 @@ module ActiveRecordViews
|
|
146
204
|
connection.raw_connection.server_version >= 90400
|
147
205
|
end
|
148
206
|
|
149
|
-
def self.
|
207
|
+
def self.get_view_direct_dependencies(connection, name)
|
208
|
+
connection.select_values <<-SQL.squish
|
209
|
+
WITH dependencies AS (
|
210
|
+
SELECT DISTINCT refobjid::regclass::text AS name
|
211
|
+
FROM pg_depend d
|
212
|
+
INNER JOIN pg_rewrite r ON r.oid = d.objid
|
213
|
+
WHERE refclassid = 'pg_class'::regclass
|
214
|
+
AND classid = 'pg_rewrite'::regclass
|
215
|
+
AND deptype = 'n'
|
216
|
+
AND refobjid != r.ev_class
|
217
|
+
AND r.ev_class = #{connection.quote name}::regclass::oid
|
218
|
+
)
|
219
|
+
|
220
|
+
SELECT class_name
|
221
|
+
FROM dependencies
|
222
|
+
INNER JOIN active_record_views USING (name)
|
223
|
+
SQL
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.get_view_dependants(connection, name)
|
150
227
|
connection.select_rows <<-SQL.squish
|
151
228
|
WITH RECURSIVE dependants AS (
|
152
229
|
SELECT
|
@@ -178,30 +255,30 @@ module ActiveRecordViews
|
|
178
255
|
SQL
|
179
256
|
end
|
180
257
|
|
181
|
-
def self.
|
258
|
+
def self.without_dependants(connection, name)
|
182
259
|
unless view_exists?(connection, name)
|
183
260
|
yield
|
184
261
|
return
|
185
262
|
end
|
186
263
|
|
187
|
-
|
264
|
+
dependants = get_view_dependants(connection, name)
|
188
265
|
cache = ActiveRecordViews::ChecksumCache.new(connection)
|
189
|
-
|
266
|
+
dependant_metadata = {}
|
190
267
|
|
191
|
-
|
192
|
-
execute_drop_view connection,
|
193
|
-
|
194
|
-
cache.set
|
268
|
+
dependants.reverse.each do |dependant_name, _, _, _|
|
269
|
+
execute_drop_view connection, dependant_name
|
270
|
+
dependant_metadata[dependant_name] = cache.get(dependant_name)
|
271
|
+
cache.set dependant_name, nil
|
195
272
|
end
|
196
273
|
|
197
274
|
yield
|
198
275
|
|
199
|
-
|
276
|
+
dependants.each do |dependant_name, class_name, definition, options_json|
|
200
277
|
create_view_exception = begin
|
201
278
|
connection.transaction :requires_new => true do
|
202
279
|
options = JSON.load(options_json).symbolize_keys
|
203
|
-
execute_create_view connection,
|
204
|
-
cache.set
|
280
|
+
execute_create_view connection, dependant_name, definition, options
|
281
|
+
cache.set dependant_name, dependant_metadata[dependant_name]
|
205
282
|
end
|
206
283
|
nil
|
207
284
|
rescue StandardError => e
|
@@ -91,24 +91,30 @@ describe ActiveRecordViews::Extension do
|
|
91
91
|
end
|
92
92
|
|
93
93
|
it 'successfully recreates modified paired views with incompatible changes' do
|
94
|
-
|
95
|
-
|
94
|
+
without_dependency_checks do
|
95
|
+
ActiveRecordViews.create_view ActiveRecord::Base.connection, 'modified_as', 'ModifiedA', 'SELECT 11 AS old_name;'
|
96
|
+
ActiveRecordViews.create_view ActiveRecord::Base.connection, 'modified_bs', 'ModifiedB', 'SELECT old_name FROM modified_as;'
|
97
|
+
end
|
96
98
|
|
97
99
|
expect(ModifiedB.first.attributes.except(nil)).to eq('new_name' => 22)
|
98
100
|
end
|
99
101
|
|
100
102
|
it 'successfully restores dependant view when temporarily dropping dependency' do
|
101
|
-
|
102
|
-
|
103
|
+
without_dependency_checks do
|
104
|
+
ActiveRecordViews.create_view ActiveRecord::Base.connection, 'dependency_as', 'DependencyA', 'SELECT 42 AS foo, 1 AS id;'
|
105
|
+
ActiveRecordViews.create_view ActiveRecord::Base.connection, 'dependency_bs', 'DependencyB', 'SELECT id FROM dependency_as;'
|
106
|
+
end
|
103
107
|
|
104
108
|
expect(DependencyA.first.id).to eq 2
|
105
109
|
expect(DependencyB.first.id).to eq 2
|
106
110
|
end
|
107
111
|
|
108
112
|
it 'sucessfully restore dependant view and dependency when loading from middle outwards' do
|
109
|
-
|
110
|
-
|
111
|
-
|
113
|
+
without_dependency_checks do
|
114
|
+
ActiveRecordViews.create_view ActiveRecord::Base.connection, 'dependency_as', 'DependencyA', 'SELECT 42 AS foo, 1 AS id;'
|
115
|
+
ActiveRecordViews.create_view ActiveRecord::Base.connection, 'dependency_bs', 'DependencyB', 'SELECT id FROM dependency_as;'
|
116
|
+
ActiveRecordViews.create_view ActiveRecord::Base.connection, 'dependency_cs', 'DependencyC', 'SELECT id FROM dependency_bs;'
|
117
|
+
end
|
112
118
|
|
113
119
|
expect(DependencyB.first.id).to eq 2
|
114
120
|
end
|
@@ -204,5 +210,74 @@ describe ActiveRecordViews::Extension do
|
|
204
210
|
MaterializedViewAutoRefreshTestModel.refresh_view! concurrent: :blah
|
205
211
|
}.to raise_error ArgumentError, 'invalid concurrent option'
|
206
212
|
end
|
213
|
+
|
214
|
+
it 'errors if dependencies are not specified' do
|
215
|
+
class DependencyCheckBase1 < ActiveRecord::Base
|
216
|
+
self.table_name = 'dependency_check_base1'
|
217
|
+
is_view 'SELECT 1 AS ID;'
|
218
|
+
end
|
219
|
+
class DependencyCheckBase2 < ActiveRecord::Base
|
220
|
+
self.table_name = 'dependency_check_base2'
|
221
|
+
is_view 'SELECT 1 AS ID;'
|
222
|
+
end
|
223
|
+
ActiveRecord::Base.connection.execute 'CREATE VIEW dependency_check_base_unmanaged AS SELECT 1 AS ID;'
|
224
|
+
|
225
|
+
expect {
|
226
|
+
class DependencyCheckGood < ActiveRecord::Base
|
227
|
+
is_view 'SELECT * FROM dependency_check_base1;', dependencies: [DependencyCheckBase1]
|
228
|
+
end
|
229
|
+
}.to_not raise_error
|
230
|
+
|
231
|
+
expect {
|
232
|
+
class DependencyCheckGoodUnmanaged < ActiveRecord::Base
|
233
|
+
is_view 'SELECT * FROM dependency_check_base_unmanaged;'
|
234
|
+
end
|
235
|
+
}.to_not raise_error
|
236
|
+
|
237
|
+
expect {
|
238
|
+
class DependencyCheckMissing1 < ActiveRecord::Base
|
239
|
+
is_view 'SELECT * FROM dependency_check_base1 UNION ALL SELECT * FROM dependency_check_base2;', dependencies: [DependencyCheckBase1]
|
240
|
+
end
|
241
|
+
}.to raise_error ArgumentError, 'DependencyCheckBase2 must be specified as a dependency of DependencyCheckMissing1: `is_view dependencies: [DependencyCheckBase1, DependencyCheckBase2]`'
|
242
|
+
|
243
|
+
expect {
|
244
|
+
class DependencyCheckMissing2 < ActiveRecord::Base
|
245
|
+
is_view 'SELECT * FROM dependency_check_base1 UNION ALL SELECT * FROM dependency_check_base2;', dependencies: []
|
246
|
+
end
|
247
|
+
}.to raise_error ArgumentError, 'DependencyCheckBase1 and DependencyCheckBase2 must be specified as dependencies of DependencyCheckMissing2: `is_view dependencies: [DependencyCheckBase1, DependencyCheckBase2]`'
|
248
|
+
|
249
|
+
expect {
|
250
|
+
class DependencyCheckNested < ActiveRecord::Base
|
251
|
+
is_view 'SELECT 1 FROM dependency_check_goods'
|
252
|
+
end
|
253
|
+
}.to raise_error ArgumentError, 'DependencyCheckGood must be specified as a dependency of DependencyCheckNested: `is_view dependencies: [DependencyCheckGood]`'
|
254
|
+
|
255
|
+
expect {
|
256
|
+
class DependencyCheckExtra1 < ActiveRecord::Base
|
257
|
+
is_view 'SELECT * FROM dependency_check_base1;', dependencies: [DependencyCheckBase1, DependencyCheckBase2]
|
258
|
+
end
|
259
|
+
}.to raise_error ArgumentError, 'DependencyCheckBase2 is not a dependency of DependencyCheckExtra1'
|
260
|
+
|
261
|
+
expect {
|
262
|
+
class DependencyCheckExtra2 < ActiveRecord::Base
|
263
|
+
is_view 'SELECT 1 AS id;', dependencies: [DependencyCheckBase1, DependencyCheckBase2]
|
264
|
+
end
|
265
|
+
}.to raise_error ArgumentError, 'DependencyCheckBase1 and DependencyCheckBase2 are not dependencies of DependencyCheckExtra2'
|
266
|
+
|
267
|
+
expect {
|
268
|
+
class DependencyCheckWrongType < ActiveRecord::Base
|
269
|
+
is_view 'SELECT 1;', dependencies: %w[DependencyCheckBase1]
|
270
|
+
end
|
271
|
+
}.to raise_error ArgumentError, 'dependencies must be ActiveRecord classes'
|
272
|
+
|
273
|
+
expect(view_names).to match_array %w[
|
274
|
+
dependency_check_base1
|
275
|
+
dependency_check_base2
|
276
|
+
dependency_check_base_unmanaged
|
277
|
+
|
278
|
+
dependency_check_goods
|
279
|
+
dependency_check_good_unmanageds
|
280
|
+
]
|
281
|
+
end
|
207
282
|
end
|
208
283
|
end
|
@@ -20,14 +20,6 @@ describe ActiveRecordViews do
|
|
20
20
|
SQL
|
21
21
|
end
|
22
22
|
|
23
|
-
def view_names
|
24
|
-
connection.select_values(<<-SQL.squish)
|
25
|
-
SELECT table_name
|
26
|
-
FROM information_schema.views
|
27
|
-
WHERE table_schema = 'public'
|
28
|
-
SQL
|
29
|
-
end
|
30
|
-
|
31
23
|
def test_materialized_view_sql
|
32
24
|
connection.select_value(<<-SQL.squish).try(&:squish)
|
33
25
|
SELECT definition
|
@@ -36,14 +28,6 @@ describe ActiveRecordViews do
|
|
36
28
|
SQL
|
37
29
|
end
|
38
30
|
|
39
|
-
def materialized_view_names
|
40
|
-
connection.select_values(<<-SQL.squish)
|
41
|
-
SELECT matviewname
|
42
|
-
FROM pg_matviews
|
43
|
-
WHERE schemaname = 'public'
|
44
|
-
SQL
|
45
|
-
end
|
46
|
-
|
47
31
|
it 'creates database view' do
|
48
32
|
expect(test_view_sql).to be_nil
|
49
33
|
create_test_view 'select 1 as id'
|
@@ -57,7 +41,7 @@ describe ActiveRecordViews do
|
|
57
41
|
'name' => 'test',
|
58
42
|
'class_name' => 'Test',
|
59
43
|
'checksum' => Digest::SHA1.hexdigest('select 1 as id'),
|
60
|
-
'options' => '{"materialized":true}',
|
44
|
+
'options' => '{"materialized":true,"dependencies":[]}',
|
61
45
|
}
|
62
46
|
]
|
63
47
|
end
|
@@ -95,32 +79,26 @@ describe ActiveRecordViews do
|
|
95
79
|
|
96
80
|
context 'having dependant views' do
|
97
81
|
before do
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
after do
|
106
|
-
dependants = %w[test dependency1 dependency2a dependency2b dependency3 dependency4]
|
107
|
-
expect(view_names).to match_array dependants
|
108
|
-
dependants.reverse.each do |name|
|
109
|
-
ActiveRecordViews.drop_view connection, name
|
82
|
+
without_dependency_checks do
|
83
|
+
ActiveRecordViews.create_view connection, 'dependant1', 'Dependant1', 'SELECT id FROM test;'
|
84
|
+
ActiveRecordViews.create_view connection, 'dependant2a', 'Dependant2a', 'SELECT id, id * 2 AS id2 FROM dependant1;'
|
85
|
+
ActiveRecordViews.create_view connection, 'dependant2b', 'Dependant2b', 'SELECT id, id * 4 AS id4 FROM dependant1;'
|
86
|
+
ActiveRecordViews.create_view connection, 'dependant3', 'Dependant3', 'SELECT * FROM dependant2b;'
|
87
|
+
ActiveRecordViews.create_view connection, 'dependant4', 'Dependant4', 'SELECT id FROM dependant1 UNION ALL SELECT id FROM dependant3;'
|
110
88
|
end
|
111
89
|
end
|
112
90
|
|
113
91
|
it 'updates view with compatible change' do
|
114
92
|
create_test_view 'select 2 as id'
|
115
93
|
expect(test_view_sql).to eq 'SELECT 2 AS id;'
|
116
|
-
expect(connection.select_value('SELECT id2 FROM
|
94
|
+
expect(connection.select_value('SELECT id2 FROM dependant2a')).to eq '4'
|
117
95
|
end
|
118
96
|
|
119
97
|
describe 'changes incompatible with CREATE OR REPLACE' do
|
120
98
|
it 'updates view with new column added before existing' do
|
121
99
|
create_test_view "select 'foo'::text as name, 3 as id"
|
122
100
|
expect(test_view_sql).to eq "SELECT 'foo'::text AS name, 3 AS id;"
|
123
|
-
expect(connection.select_value('SELECT id2 FROM
|
101
|
+
expect(connection.select_value('SELECT id2 FROM dependant2a')).to eq '6'
|
124
102
|
end
|
125
103
|
|
126
104
|
it 'fails to update view if column used by dependant view is removed' do
|
@@ -128,18 +106,44 @@ describe ActiveRecordViews do
|
|
128
106
|
create_test_view "select 'foo'::text as name"
|
129
107
|
}.to raise_error ActiveRecord::StatementInvalid, /column test.id does not exist/
|
130
108
|
expect(test_view_sql).to eq 'SELECT 1 AS id;'
|
131
|
-
expect(connection.select_value('SELECT id2 FROM
|
109
|
+
expect(connection.select_value('SELECT id2 FROM dependant2a')).to eq '2'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe '.drop_all_views' do
|
114
|
+
it 'can drop all managed views' do
|
115
|
+
connection.execute 'CREATE VIEW unmanaged AS SELECT 2 AS id;'
|
116
|
+
|
117
|
+
expect(view_names).to match_array %w[test dependant1 dependant2a dependant2b dependant3 dependant4 unmanaged]
|
118
|
+
ActiveRecordViews.drop_all_views connection
|
119
|
+
expect(view_names).to match_array %w[unmanaged]
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'errors if an unmanaged view depends on a managed view' do
|
123
|
+
connection.execute 'CREATE VIEW unmanaged AS SELECT * from dependant2a'
|
124
|
+
|
125
|
+
expect {
|
126
|
+
ActiveRecordViews.drop_all_views connection
|
127
|
+
}.to raise_error ActiveRecord::StatementInvalid, /view unmanaged depends on view dependant2a/
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'can drop materialized views' do
|
131
|
+
without_dependency_checks do
|
132
|
+
ActiveRecordViews.create_view connection, 'materialized', 'Materialized', 'SELECT id FROM test;', materialized: true
|
133
|
+
end
|
134
|
+
ActiveRecordViews.drop_all_views connection
|
135
|
+
expect(view_names).to match_array %w[]
|
132
136
|
end
|
133
137
|
end
|
134
138
|
end
|
135
139
|
|
136
140
|
describe 'with unmanaged dependant view' do
|
137
141
|
before do
|
138
|
-
connection.execute 'CREATE VIEW
|
142
|
+
connection.execute 'CREATE VIEW dependant AS SELECT id FROM test'
|
139
143
|
end
|
140
144
|
|
141
145
|
after do
|
142
|
-
connection.execute 'DROP VIEW
|
146
|
+
connection.execute 'DROP VIEW dependant;'
|
143
147
|
end
|
144
148
|
|
145
149
|
it 'updates view with compatible change' do
|
@@ -150,7 +154,7 @@ describe ActiveRecordViews do
|
|
150
154
|
it 'fails to update view with incompatible change' do
|
151
155
|
expect {
|
152
156
|
create_test_view "SELECT 'foo'::text as name, 4 as id"
|
153
|
-
}.to raise_error ActiveRecord::StatementInvalid, /view
|
157
|
+
}.to raise_error ActiveRecord::StatementInvalid, /view dependant depends on view test/
|
154
158
|
expect(test_view_sql).to eq 'SELECT 1 AS id;'
|
155
159
|
end
|
156
160
|
end
|
@@ -187,9 +191,11 @@ describe ActiveRecordViews do
|
|
187
191
|
end
|
188
192
|
|
189
193
|
it 'preserves materialized view if dropping/recreating' do
|
190
|
-
|
191
|
-
|
192
|
-
|
194
|
+
without_dependency_checks do
|
195
|
+
ActiveRecordViews.create_view connection, 'test1', 'Test1', 'SELECT 1 AS foo'
|
196
|
+
ActiveRecordViews.create_view connection, 'test2', 'Test2', 'SELECT * FROM test1', materialized: true
|
197
|
+
ActiveRecordViews.create_view connection, 'test1', 'Test1', 'SELECT 2 AS bar, 1 AS foo'
|
198
|
+
end
|
193
199
|
|
194
200
|
expect(materialized_view_names).to eq %w[test2]
|
195
201
|
expect(view_names).to eq %w[test1]
|
data/spec/spec_helper.rb
CHANGED
@@ -69,3 +69,26 @@ def update_file(file, new_content)
|
|
69
69
|
File.write file, new_content
|
70
70
|
File.utime time, time, file
|
71
71
|
end
|
72
|
+
|
73
|
+
def view_names
|
74
|
+
ActiveRecord::Base.connection.select_values(<<-SQL.squish)
|
75
|
+
SELECT table_name
|
76
|
+
FROM information_schema.views
|
77
|
+
WHERE table_schema = 'public'
|
78
|
+
SQL
|
79
|
+
end
|
80
|
+
|
81
|
+
def materialized_view_names
|
82
|
+
ActiveRecord::Base.connection.select_values(<<-SQL.squish)
|
83
|
+
SELECT matviewname
|
84
|
+
FROM pg_matviews
|
85
|
+
WHERE schemaname = 'public'
|
86
|
+
SQL
|
87
|
+
end
|
88
|
+
|
89
|
+
def without_dependency_checks
|
90
|
+
allow(ActiveRecordViews).to receive(:check_dependencies)
|
91
|
+
yield
|
92
|
+
ensure
|
93
|
+
allow(ActiveRecordViews).to receive(:check_dependencies).and_call_original
|
94
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord_views
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Weathered
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-01-
|
11
|
+
date: 2015-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|