activerecord_views 0.0.16 → 0.1.0

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.
@@ -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