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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d730fc5ba86879e6e1a74f23f1e3320809d37e8d
4
- data.tar.gz: dc5a5b0e4453124be60b9ed57178fd81f763617d
3
+ metadata.gz: ad72d8911d4f996a5b7eb841d2a7b1f930d16aac
4
+ data.tar.gz: 1891b3ee70bfa4e3737213f285b2e350c9c628ae
5
5
  SHA512:
6
- metadata.gz: 5281382c91bde1d07a1784d781d6c618c14e059201c3d252e1d1490bd47bd2fc52890134b715430f5a29f8ae6bbbb5ac9916d9d2d6e7f3ffc7f452cf94442a53
7
- data.tar.gz: 1286c8ea11028e69b5c4e850600d1b8899672e80ee5618648e98c37fc7ec9864ddc2476e3d2dd926f0a77dfc7f65f37c2c59a486fca008e0b09c2a4fb507a7bb
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`.
@@ -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.assert_valid_keys :materialized, :unique_columns
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.execute "CREATE OR REPLACE VIEW #{connection.quote_table_name name} AS #{sql}"
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
- without_dependencies connection, name do
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.get_view_dependencies(connection, name)
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.without_dependencies(connection, name)
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
- dependencies = get_view_dependencies(connection, name)
264
+ dependants = get_view_dependants(connection, name)
188
265
  cache = ActiveRecordViews::ChecksumCache.new(connection)
189
- dependency_metadata = {}
266
+ dependant_metadata = {}
190
267
 
191
- dependencies.reverse.each do |dependency_name, _, _, _|
192
- execute_drop_view connection, dependency_name
193
- dependency_metadata[dependency_name] = cache.get(dependency_name)
194
- cache.set dependency_name, nil
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
- dependencies.each do |dependency_name, class_name, definition, options_json|
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, dependency_name, definition, options
204
- cache.set dependency_name, dependency_metadata[dependency_name]
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
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordViews
2
- VERSION = '0.0.9'
2
+ VERSION = '0.0.10'
3
3
  end
@@ -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
- ActiveRecordViews.create_view ActiveRecord::Base.connection, 'modified_as', 'ModifiedA', 'SELECT 11 AS old_name;'
95
- ActiveRecordViews.create_view ActiveRecord::Base.connection, 'modified_bs', 'ModifiedB', 'SELECT old_name FROM modified_as;'
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
- ActiveRecordViews.create_view ActiveRecord::Base.connection, 'dependency_as', 'DependencyA', 'SELECT 42 AS foo, 1 AS id;'
102
- ActiveRecordViews.create_view ActiveRecord::Base.connection, 'dependency_bs', 'DependencyB', 'SELECT id FROM dependency_as;'
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
- ActiveRecordViews.create_view ActiveRecord::Base.connection, 'dependency_as', 'DependencyA', 'SELECT 42 AS foo, 1 AS id;'
110
- ActiveRecordViews.create_view ActiveRecord::Base.connection, 'dependency_bs', 'DependencyB', 'SELECT id FROM dependency_as;'
111
- ActiveRecordViews.create_view ActiveRecord::Base.connection, 'dependency_cs', 'DependencyC', 'SELECT id FROM dependency_bs;'
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
- ActiveRecordViews.create_view connection, 'dependency1', 'Dependency1', 'SELECT id FROM test;'
99
- ActiveRecordViews.create_view connection, 'dependency2a', 'Dependency2a', 'SELECT id, id * 2 AS id2 FROM dependency1;'
100
- ActiveRecordViews.create_view connection, 'dependency2b', 'Dependency2b', 'SELECT id, id * 4 AS id4 FROM dependency1;'
101
- ActiveRecordViews.create_view connection, 'dependency3', 'Dependency3', 'SELECT * FROM dependency2b;'
102
- ActiveRecordViews.create_view connection, 'dependency4', 'Dependency4', 'SELECT id FROM dependency1 UNION ALL SELECT id FROM dependency3;'
103
- end
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 dependency2a')).to eq '4'
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 dependency2a')).to eq '6'
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 dependency2a')).to eq '2'
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 dependency AS SELECT id FROM test'
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 dependency;'
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 dependency depends on view test/
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
- ActiveRecordViews.create_view connection, 'test1', 'Test1', 'SELECT 1 AS foo'
191
- ActiveRecordViews.create_view connection, 'test2', 'Test2', 'SELECT * FROM test1', materialized: true
192
- ActiveRecordViews.create_view connection, 'test1', 'Test1', 'SELECT 2 AS bar, 1 AS foo'
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]
@@ -1,3 +1,3 @@
1
1
  class DependencyB < ActiveRecord::Base
2
- is_view "SELECT id FROM #{DependencyA.table_name};"
2
+ is_view "SELECT id FROM #{DependencyA.table_name};", dependencies: [DependencyA]
3
3
  end
@@ -1,3 +1,3 @@
1
1
  class DependencyC < ActiveRecord::Base
2
- is_view "SELECT id FROM #{DependencyB.table_name};"
2
+ is_view "SELECT id FROM #{DependencyB.table_name};", dependencies: [DependencyB]
3
3
  end
@@ -1,3 +1,3 @@
1
1
  class ModifiedB < ActiveRecord::Base
2
- is_view "SELECT new_name FROM #{ModifiedA.table_name};"
2
+ is_view "SELECT new_name FROM #{ModifiedA.table_name};", dependencies: [ModifiedA]
3
3
  end
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.9
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-19 00:00:00.000000000 Z
11
+ date: 2015-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord