activerecord_views 0.0.9 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|