activerecord_views 0.0.16 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -20,6 +20,28 @@ describe ActiveRecordViews do
20
20
  SQL
21
21
  end
22
22
 
23
+ def test_view_populated?
24
+ value = connection.select_value(<<~SQL)
25
+ SELECT ispopulated
26
+ FROM pg_matviews
27
+ WHERE schemaname = 'public' AND matviewname = 'test'
28
+ SQL
29
+
30
+ if Rails::VERSION::MAJOR < 5
31
+ value = ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(value)
32
+ end
33
+
34
+ value
35
+ end
36
+
37
+ def test_view_refreshed_at
38
+ connection.select_value(<<~SQL)
39
+ SELECT refreshed_at
40
+ FROM active_record_views
41
+ WHERE name = 'test'
42
+ SQL
43
+ end
44
+
23
45
  def test_materialized_view_sql
24
46
  connection.select_value(<<-SQL.squish).try(&:squish)
25
47
  SELECT definition
@@ -92,14 +114,14 @@ describe ActiveRecordViews do
92
114
  it 'updates view with compatible change' do
93
115
  create_test_view 'select 2 as id'
94
116
  expect(test_view_sql).to eq 'SELECT 2 AS id;'
95
- expect(connection.select_value('SELECT id2 FROM dependant2a')).to eq '4'
117
+ expect(Integer(connection.select_value('SELECT id2 FROM dependant2a'))).to eq 4
96
118
  end
97
119
 
98
120
  describe 'changes incompatible with CREATE OR REPLACE' do
99
121
  it 'updates view with new column added before existing' do
100
122
  create_test_view "select 'foo'::text as name, 3 as id"
101
123
  expect(test_view_sql).to eq "SELECT 'foo'::text AS name, 3 AS id;"
102
- expect(connection.select_value('SELECT id2 FROM dependant2a')).to eq '6'
124
+ expect(Integer(connection.select_value('SELECT id2 FROM dependant2a'))).to eq 6
103
125
  end
104
126
 
105
127
  it 'fails to update view if column used by dependant view is removed' do
@@ -107,7 +129,7 @@ describe ActiveRecordViews do
107
129
  create_test_view "select 'foo'::text as name"
108
130
  }.to raise_error ActiveRecord::StatementInvalid, /column test.id does not exist/
109
131
  expect(test_view_sql).to eq 'SELECT 1 AS id;'
110
- expect(connection.select_value('SELECT id2 FROM dependant2a')).to eq '2'
132
+ expect(Integer(connection.select_value('SELECT id2 FROM dependant2a'))).to eq 2
111
133
  end
112
134
  end
113
135
 
@@ -120,6 +142,21 @@ describe ActiveRecordViews do
120
142
  expect(view_names).to match_array %w[unmanaged]
121
143
  end
122
144
 
145
+ it 'support being ran inside a transaction' do
146
+ expect(ActiveRecordViews).to receive(:without_transaction).at_least(:once).and_wrap_original do |original, *args, &block|
147
+ original.call(*args) do |new_connection|
148
+ new_connection.execute 'SET statement_timeout = 1000'
149
+ block.call(new_connection)
150
+ end
151
+ end
152
+
153
+ connection.transaction requires_new: true do
154
+ expect {
155
+ ActiveRecordViews.drop_all_views connection
156
+ }.to change { view_names }
157
+ end
158
+ end
159
+
123
160
  it 'errors if an unmanaged view depends on a managed view' do
124
161
  connection.execute 'CREATE VIEW unmanaged AS SELECT * from dependant2a'
125
162
 
@@ -205,7 +242,7 @@ describe ActiveRecordViews do
205
242
  it 'supports creating unique indexes on materialized views' do
206
243
  create_test_view 'select 1 as foo, 2 as bar, 3 as baz', materialized: true, unique_columns: [:foo, 'bar']
207
244
  index_sql = connection.select_value("SELECT indexdef FROM pg_indexes WHERE schemaname = 'public' AND indexname = 'test_pkey';")
208
- expect(index_sql).to eq 'CREATE UNIQUE INDEX test_pkey ON test USING btree (foo, bar)'
245
+ expect(index_sql).to eq 'CREATE UNIQUE INDEX test_pkey ON public.test USING btree (foo, bar)'
209
246
  end
210
247
 
211
248
  it 'errors if trying to create unique index on non-materialized view' do
@@ -213,6 +250,28 @@ describe ActiveRecordViews do
213
250
  create_test_view 'select 1 as foo, 2 as bar, 3 as baz', materialized: false, unique_columns: [:foo, 'bar']
214
251
  }.to raise_error ArgumentError, 'unique_columns option requires view to be materialized'
215
252
  end
253
+
254
+ it 'supports resetting all materialised views' do
255
+ class ResetMaterializeViewTestModel < ActiveRecord::Base
256
+ self.table_name = 'test'
257
+ is_view 'select 123 as id', materialized: true
258
+ end
259
+ ResetMaterializeViewTestModel.refresh_view!
260
+
261
+ expect {
262
+ ActiveRecordViews.reset_materialized_views
263
+ }.to change { test_view_populated? }.to(false)
264
+ .and change { test_view_refreshed_at }.to(nil)
265
+ end
266
+ end
267
+
268
+ describe '.drop_all_views' do
269
+ let(:connection) { ActiveRecord::Base.connection }
270
+
271
+ it 'does nothing when no views have been defined' do
272
+ ActiveRecordViews.drop_all_views connection
273
+ expect(view_names).to match_array %w[]
274
+ end
216
275
  end
217
276
 
218
277
  describe '.without_transaction' do
@@ -1,4 +1,5 @@
1
1
  require 'bundler/setup'
2
+ require './spec/support/silence_warnings.rb'
2
3
  require 'combustion'
3
4
 
4
5
  Combustion::Database.instance_eval do
@@ -9,7 +10,11 @@ end
9
10
 
10
11
  Combustion.initialize! :active_record, :action_controller do
11
12
  config.cache_classes = false
12
- config.active_record.whitelist_attributes = true if Rails::VERSION::MAJOR < 4
13
+ config.log_level = :debug
14
+ config.active_record.schema_format = ENV.fetch('SCHEMA_FORMAT', 'sql').to_sym
15
+ if ENV['SKIP_MODEL_EAGER_LOAD']
16
+ config.eager_load_paths -= Rails.application.config.paths['app/models'].to_a
17
+ end
13
18
  end
14
19
 
15
20
  load 'active_record/railties/databases.rake'
@@ -1,3 +1,7 @@
1
1
  class ErbTestModel < ActiveRecord::Base
2
+ def self.test_erb_method
3
+ 'ERB method'
4
+ end
5
+
2
6
  is_view
3
7
  end
@@ -1 +1 @@
1
- SELECT 1 AS id, '<%= "ERB" %> file'::text AS name;
1
+ SELECT 1 AS id, '<%= test_erb_method %> file'::text AS name;
@@ -1,4 +1,9 @@
1
- test:
1
+ test: &test
2
2
  adapter: postgresql
3
3
  database: activerecord_views_test
4
4
  min_messages: warning
5
+ advisory_locks: <%= !Rails.version.start_with?('6.0.') %>
6
+ development:
7
+ <<: *test
8
+ production:
9
+ <<: *test
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ root to: -> (env) { [204, {}, []] }
3
+ end
data/spec/spec_helper.rb CHANGED
@@ -2,22 +2,60 @@ require 'bundler'
2
2
  Bundler.setup
3
3
 
4
4
  require 'rails/version'
5
- $VERBOSE = true unless Rails::VERSION::MAJOR < 4
5
+ $VERBOSE = true
6
+
7
+ require './spec/support/silence_warnings.rb'
6
8
 
7
9
  require 'combustion'
8
10
  require 'active_record_views'
11
+
12
+ FileUtils.mkdir_p 'spec/internal/db'
13
+ File.write 'spec/internal/db/schema.rb', ''
14
+
15
+ TEST_TEMP_MODEL_DIR = Rails.root + 'spec/internal/app/models_temp'
16
+ FileUtils.mkdir_p TEST_TEMP_MODEL_DIR
17
+ Rails.application.config.paths['app/models'] << 'app/models_temp'
18
+
9
19
  Combustion.initialize! :active_record, :action_controller do
10
20
  config.cache_classes = false
11
- config.active_record.whitelist_attributes = true if Rails::VERSION::MAJOR < 4
12
21
  end
13
22
  require 'rspec/rails'
23
+ require 'super_diff/rspec-rails'
24
+
25
+ RSpec.shared_context 'sql_statements' do
26
+ let(:sql_statements) { [] }
27
+
28
+ let!(:sql_statements_subscription) do
29
+ ActiveSupport::Notifications.subscribe('sql.active_record') do |_, _, _, _, details|
30
+ sql_statements << details.fetch(:sql)
31
+ end
32
+ end
33
+
34
+ after do
35
+ ActiveSupport::Notifications.unsubscribe sql_statements_subscription
36
+ end
37
+ end
14
38
 
15
39
  RSpec.configure do |config|
16
40
  config.use_transactional_fixtures = false
41
+ config.filter_run_when_matching focus: true
42
+ config.filter_run_excluding skip: true
43
+ config.example_status_persistence_file_path = 'tmp/examples.txt'
44
+
45
+ config.mock_with :rspec do |mocks|
46
+ mocks.verify_doubled_constant_names = true
47
+ mocks.verify_partial_doubles = true
48
+ end
17
49
 
18
50
  config.before do
19
- ActionDispatch::Reloader.cleanup!
20
- ActionDispatch::Reloader.prepare!
51
+ FileUtils.rm_rf Dir["spec/internal/app/models_temp/*"]
52
+
53
+ if Rails::VERSION::MAJOR >= 5
54
+ Rails.application.reloader.reload!
55
+ else
56
+ ActionDispatch::Reloader.cleanup!
57
+ ActionDispatch::Reloader.prepare!
58
+ end
21
59
 
22
60
  connection = ActiveRecord::Base.connection
23
61
 
@@ -42,24 +80,26 @@ RSpec.configure do |config|
42
80
  end
43
81
  end
44
82
 
83
+ config.include_context 'sql_statements'
45
84
  end
46
85
 
47
- def test_request
48
- begin
49
- Rails.application.call({'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/'})
50
- rescue ActionController::RoutingError
86
+ def with_reloader(&block)
87
+ if Rails.application.respond_to?(:reloader)
88
+ Rails.application.reloader.wrap(&block)
89
+ else
90
+ block.call
51
91
  end
52
92
  end
53
93
 
54
- def with_temp_sql_dir
55
- Dir.mktmpdir do |temp_dir|
56
- begin
57
- old_sql_load_path = ActiveRecordViews.sql_load_path
58
- ActiveRecordViews.sql_load_path = [temp_dir] + old_sql_load_path
59
- yield temp_dir
60
- ensure
61
- ActiveRecordViews.sql_load_path = old_sql_load_path
62
- end
94
+ def test_request
95
+ with_reloader do
96
+ status, headers, body = Rails.application.call(
97
+ 'REQUEST_METHOD' => 'GET',
98
+ 'PATH_INFO' => '/',
99
+ 'rack.input' => StringIO.new,
100
+ )
101
+ expect(status).to eq 204
102
+ body.close
63
103
  end
64
104
  end
65
105
 
@@ -92,3 +132,11 @@ def without_dependency_checks
92
132
  ensure
93
133
  allow(ActiveRecordViews).to receive(:check_dependencies).and_call_original
94
134
  end
135
+
136
+ def without_create_enabled
137
+ old_enabled = ActiveRecordViews::Extension.create_enabled
138
+ ActiveRecordViews::Extension.create_enabled = false
139
+ yield
140
+ ensure
141
+ ActiveRecordViews::Extension.create_enabled = old_enabled
142
+ end
@@ -0,0 +1,23 @@
1
+ require 'warning'
2
+ require 'rails/version'
3
+
4
+ case Rails::VERSION::STRING
5
+ when /^4\.2\./
6
+ Warning.ignore(%r{lib/(active_support/core_ext|action_dispatch/middleware)/.+: warning: (method redefined|previous definition)})
7
+ Warning.ignore(%r{lib/active_support/core_ext/.+: warning: BigDecimal.new is deprecated})
8
+ Warning.ignore(%r{lib/arel/visitors/informix.rb:\d+: warning: assigned but unused variable})
9
+ Warning.ignore(%r{lib/active_record/connection_adapters/.+: warning: deprecated Object#=~ is called on Integer})
10
+ Warning.ignore(%r{Inheriting from Rack::Session::Abstract::ID is deprecated})
11
+ when /^5\.0\./
12
+ Warning.ignore(%r{lib/(active_support/core_ext|action_view)/.+: warning: (method redefined|previous definition)})
13
+ Warning.ignore(%r{lib/arel/visitors/informix.rb:\d+: warning: assigned but unused variable})
14
+ Warning.ignore(%r{lib/action_view/.+: warning: `\*' interpreted as argument prefix})
15
+ when /^5\.1\./
16
+ Warning.ignore(%r{lib/(active_support/core_ext)/.+: warning: (method redefined|previous definition)})
17
+ Warning.ignore(%r{lib/arel/visitors/informix.rb:\d+: warning: assigned but unused variable})
18
+ Warning.ignore(%r{lib/active_record/.+/schema_statements.rb:\d+: (warning: in `drop_table': the last argument was passed as a single Hash|warning: although a splat keyword arguments here)})
19
+ end
20
+
21
+ Warning.process do |_warning|
22
+ :raise
23
+ end
data/spec/tasks_spec.rb CHANGED
@@ -1,17 +1,91 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe 'rake db:structure:dump' do
4
- it 'copies over activerecord_views data' do
5
- ActiveRecordViews.create_view ActiveRecord::Base.connection, 'test_view', 'TestView', 'SELECT 1'
6
-
7
- FileUtils.rm_f 'spec/internal/db/structure.sql'
8
- system("rake -f spec/internal/Rakefile db:structure:dump")
3
+ describe 'rake tasks' do
4
+ def rake(task_name, env: {})
5
+ system(env, *%W[
6
+ rake
7
+ -f spec/internal/Rakefile
8
+ #{task_name}
9
+ ])
9
10
  raise unless $?.success?
11
+ end
12
+
13
+ describe 'db:migrate' do
14
+ def view_names
15
+ ActiveRecord::Base.connection.select_values(<<~SQL.squish)
16
+ SELECT table_name
17
+ FROM information_schema.views
18
+ WHERE table_schema = 'public'
19
+ SQL
20
+ end
21
+
22
+ it 'does not create any database views' do
23
+ expect(view_names).to be_empty
24
+ rake 'db:migrate'
25
+ expect(view_names).to be_empty
26
+ end
27
+
28
+ it 'creates database views in production mode' do
29
+ expect(view_names).to be_empty
30
+ rake 'db:migrate', env: {'RAILS_ENV' => 'production'}
31
+ expect(view_names).to_not be_empty
32
+ end
33
+
34
+ it 'does nothing in production mode without models' do
35
+ expect(view_names).to be_empty
36
+ rake 'db:migrate', env: {'RAILS_ENV' => 'production', 'SKIP_MODEL_EAGER_LOAD' => 'true'}
37
+ expect(view_names).to be_empty
38
+ end
39
+
40
+ context 'with unregistered view' do
41
+ before do
42
+ ActiveRecordViews.create_view ActiveRecord::Base.connection, 'old_view', 'OldView', 'SELECT 42 AS id'
43
+ end
44
+
45
+ it 'does not drop unregistered views' do
46
+ expect(view_names).to include 'old_view'
47
+ rake 'db:migrate'
48
+ expect(view_names).to include 'old_view'
49
+ end
50
+
51
+ it 'drops unregistered views in production mode' do
52
+ expect(view_names).to include 'old_view'
53
+ rake 'db:migrate', env: {'RAILS_ENV' => 'production'}
54
+ expect(view_names).to_not include 'old_view'
55
+ end
56
+ end
57
+ end
58
+
59
+ schema_rake_task = Gem::Version.new(Rails.version) >= Gem::Version.new("6.1") ? 'db:schema:dump' : 'db:structure:dump'
60
+ describe schema_rake_task do
61
+ before do
62
+ FileUtils.rm_f 'spec/internal/db/schema.rb'
63
+ FileUtils.rm_f 'spec/internal/db/structure.sql'
64
+
65
+ ActiveRecordViews.create_view ActiveRecord::Base.connection, 'test_view', 'TestView', 'SELECT 1'
66
+ end
67
+
68
+ after do
69
+ FileUtils.rm_f 'spec/internal/db/schema.rb'
70
+ FileUtils.rm_f 'spec/internal/db/structure.sql'
71
+
72
+ File.write 'spec/internal/db/schema.rb', ''
73
+ end
74
+
75
+ it 'copies over activerecord_views data' do
76
+ rake schema_rake_task
77
+
78
+ expect(File.exist?('spec/internal/db/schema.rb')).to eq false
79
+ sql = File.read('spec/internal/db/structure.sql')
80
+ expect(sql).to match(/COPY public\.active_record_views.+test_view\tTestView/m)
81
+ expect(sql).to match(/UPDATE public\.active_record_views SET refreshed_at = NULL/)
82
+ end
10
83
 
11
- sql = File.read('spec/internal/db/structure.sql')
12
- FileUtils.rm_f 'spec/internal/db/structure.sql'
84
+ it 'does not write structure.sql when `schema_format = :ruby`', if: schema_rake_task != 'db:structure:dump' do
85
+ rake schema_rake_task, env: {'SCHEMA_FORMAT' => 'ruby'}
13
86
 
14
- expect(sql).to match(/COPY active_record_views.+test_view\tTestView/m)
15
- expect(sql).to match(/UPDATE active_record_views SET refreshed_at = NULL/)
87
+ expect(File.exist?('spec/internal/db/schema.rb')).to eq true
88
+ expect(File.exist?('spec/internal/db/structure.sql')).to eq false
89
+ end
16
90
  end
17
91
  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.16
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Weathered
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-21 00:00:00.000000000 Z
11
+ date: 2021-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '3.2'
19
+ version: '4.2'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '4.3'
22
+ version: '6.2'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '3.2'
29
+ version: '4.2'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '4.3'
32
+ version: '6.2'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: appraisal
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -58,6 +58,20 @@ dependencies:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
60
  version: '2.14'
61
+ - !ruby/object:Gem::Dependency
62
+ name: super_diff
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
61
75
  - !ruby/object:Gem::Dependency
62
76
  name: combustion
63
77
  requirement: !ruby/object:Gem::Requirement
@@ -86,7 +100,21 @@ dependencies:
86
100
  - - ">="
87
101
  - !ruby/object:Gem::Version
88
102
  version: '0'
89
- description:
103
+ - !ruby/object:Gem::Dependency
104
+ name: warning
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ description:
90
118
  email:
91
119
  - jason@jasoncodes.com
92
120
  executables: []
@@ -95,16 +123,19 @@ extra_rdoc_files: []
95
123
  files:
96
124
  - ".gitignore"
97
125
  - ".rspec"
126
+ - ".tool-versions"
98
127
  - Appraisals
99
128
  - Gemfile
100
129
  - LICENSE.txt
101
130
  - README.markdown
102
131
  - Rakefile
103
132
  - activerecord_views.gemspec
104
- - gemfiles/rails3_2.gemfile
105
- - gemfiles/rails4_0.gemfile
106
- - gemfiles/rails4_1.gemfile
107
133
  - gemfiles/rails4_2.gemfile
134
+ - gemfiles/rails5_0.gemfile
135
+ - gemfiles/rails5_1.gemfile
136
+ - gemfiles/rails5_2.gemfile
137
+ - gemfiles/rails6_0.gemfile
138
+ - gemfiles/rails6_1.gemfile
108
139
  - lib/active_record_views.rb
109
140
  - lib/active_record_views/checksum_cache.rb
110
141
  - lib/active_record_views/database_cleaner/truncation_extension.rb
@@ -126,20 +157,20 @@ files:
126
157
  - spec/internal/app/models/external_file_test_model.rb
127
158
  - spec/internal/app/models/external_file_test_model.sql
128
159
  - spec/internal/app/models/heredoc_test_model.rb
129
- - spec/internal/app/models/missing_file_test_model.rb
130
160
  - spec/internal/app/models/modified_a.rb
131
161
  - spec/internal/app/models/modified_b.rb
132
162
  - spec/internal/app/models/namespace/test_model.rb
133
163
  - spec/internal/app/models/namespace/test_model.sql
134
164
  - spec/internal/config/database.yml
135
- - spec/internal/db/schema.rb
165
+ - spec/internal/config/routes.rb
136
166
  - spec/spec_helper.rb
167
+ - spec/support/silence_warnings.rb
137
168
  - spec/tasks_spec.rb
138
169
  homepage: http://github.com/jasoncodes/activerecord_views
139
170
  licenses:
140
171
  - MIT
141
172
  metadata: {}
142
- post_install_message:
173
+ post_install_message:
143
174
  rdoc_options: []
144
175
  require_paths:
145
176
  - lib
@@ -154,9 +185,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
154
185
  - !ruby/object:Gem::Version
155
186
  version: '0'
156
187
  requirements: []
157
- rubyforge_project:
158
- rubygems_version: 2.6.11
159
- signing_key:
188
+ rubygems_version: 3.0.3.1
189
+ signing_key:
160
190
  specification_version: 4
161
191
  summary: Automatic database view creation for ActiveRecord
162
192
  test_files:
@@ -172,12 +202,12 @@ test_files:
172
202
  - spec/internal/app/models/external_file_test_model.rb
173
203
  - spec/internal/app/models/external_file_test_model.sql
174
204
  - spec/internal/app/models/heredoc_test_model.rb
175
- - spec/internal/app/models/missing_file_test_model.rb
176
205
  - spec/internal/app/models/modified_a.rb
177
206
  - spec/internal/app/models/modified_b.rb
178
207
  - spec/internal/app/models/namespace/test_model.rb
179
208
  - spec/internal/app/models/namespace/test_model.sql
180
209
  - spec/internal/config/database.yml
181
- - spec/internal/db/schema.rb
210
+ - spec/internal/config/routes.rb
182
211
  - spec/spec_helper.rb
212
+ - spec/support/silence_warnings.rb
183
213
  - spec/tasks_spec.rb