activerecord_views 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/Appraisals +4 -0
- data/README.markdown +33 -1
- data/activerecord_views.gemspec +1 -1
- data/gemfiles/rails3_2.gemfile +1 -1
- data/gemfiles/rails4_0.gemfile +1 -1
- data/gemfiles/rails4_1.gemfile +7 -0
- data/lib/active_record_views.rb +46 -4
- data/lib/active_record_views/version.rb +1 -1
- data/spec/active_record_views_spec.rb +57 -9
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9f28ee79b39ed0d431534523ef129ba9e63e8aad
|
4
|
+
data.tar.gz: 39e683254984dff81f2fdf7e43a3b96588738612
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2654eb5b8107e4cac185668a89769569efae3a13d627029da037ad2a4b0faea6fbfeb9695f67453175300b7899de19aac21c911ea240f9be0c6dc72b54dcb3e9
|
7
|
+
data.tar.gz: c5c7685c4598c46c43230958fcac5336b906a43b77e15d0c34a5ab73b373d02d51d263e1b8f11ebb8ae35780f26124cd3776fbd497d9f2f9a27b74485d262c92
|
data/.rspec
ADDED
data/Appraisals
CHANGED
data/README.markdown
CHANGED
@@ -68,13 +68,45 @@ Account.includes(:account_balance).find_each do |account|
|
|
68
68
|
end
|
69
69
|
```
|
70
70
|
|
71
|
+
## Pre-populating views in Rails development mode
|
72
|
+
|
73
|
+
Rails loads classes lazily in development mode by default.
|
74
|
+
This means ActiveRecordViews models will not initialize and create/update database views until the model classes are accessed.
|
75
|
+
If you're debugging in `psql` and want to ensure all views have been created, you can force Rails to load them by running the following in a `rails console`:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
Rails.application.eager_load!
|
79
|
+
```
|
80
|
+
|
81
|
+
## Handling renames/deletions
|
82
|
+
|
83
|
+
ActiveRecordViews tracks database views by their name. When an ActiveRecordViews model is renamed or deleted, there is no longer a link between the model and the associated database table. This means an orphan view will be left in the database.
|
84
|
+
|
85
|
+
In order to keep things tidy and to avoid accidentally referencing a stale view, you should remove the view and associated ActiveRecordViews metadata when renaming or deleting a model using ActiveRecordViews. This is best done with a database migration (use `rails generate migration`) containing the following:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
ActiveRecordViews.drop_view connection, 'account_balances'
|
89
|
+
```
|
90
|
+
|
71
91
|
### Usage outside of Rails
|
72
92
|
|
93
|
+
When included in a Ruby on Rails project, ActiveRecordViews will automatically detect `.sql` files alongside models in `app/models`.
|
94
|
+
Outside of Rails, you will have to tell ActiveRecordViews where to find associated `.sql` files for models:
|
95
|
+
|
73
96
|
```ruby
|
74
97
|
require 'active_record'
|
75
98
|
require 'active_record_views'
|
76
|
-
|
99
|
+
require 'pg'
|
100
|
+
|
101
|
+
ActiveRecordViews.sql_load_path << '.' # load .sql files from current directory
|
77
102
|
ActiveRecordViews.init!
|
103
|
+
ActiveRecord::Base.establish_connection 'postgresql:///example'
|
104
|
+
|
105
|
+
class Foo < ActiveRecord::Base
|
106
|
+
is_view
|
107
|
+
end
|
108
|
+
|
109
|
+
p Foo.all
|
78
110
|
```
|
79
111
|
|
80
112
|
## License
|
data/activerecord_views.gemspec
CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ['lib']
|
19
19
|
|
20
|
-
gem.add_dependency 'activerecord', ['>= 3.1', '< 4.
|
20
|
+
gem.add_dependency 'activerecord', ['>= 3.1', '< 4.2']
|
21
21
|
|
22
22
|
gem.add_development_dependency 'appraisal'
|
23
23
|
gem.add_development_dependency 'rspec-rails', '>= 2.14'
|
data/gemfiles/rails3_2.gemfile
CHANGED
data/gemfiles/rails4_0.gemfile
CHANGED
data/lib/active_record_views.rb
CHANGED
@@ -53,13 +53,11 @@ module ActiveRecordViews
|
|
53
53
|
begin
|
54
54
|
connection.execute "CREATE OR REPLACE VIEW #{connection.quote_table_name name} AS #{sql}"
|
55
55
|
rescue ActiveRecord::StatementInvalid => original_exception
|
56
|
-
|
57
|
-
connection
|
56
|
+
connection.transaction :requires_new => true do
|
57
|
+
without_dependencies connection, name do
|
58
58
|
connection.execute "DROP VIEW #{connection.quote_table_name name}"
|
59
59
|
connection.execute "CREATE VIEW #{connection.quote_table_name name} AS #{sql}"
|
60
60
|
end
|
61
|
-
rescue
|
62
|
-
raise original_exception
|
63
61
|
end
|
64
62
|
end
|
65
63
|
|
@@ -75,6 +73,50 @@ module ActiveRecordViews
|
|
75
73
|
end
|
76
74
|
end
|
77
75
|
|
76
|
+
def self.get_view_dependencies(connection, name)
|
77
|
+
connection.select_rows <<-SQL
|
78
|
+
WITH RECURSIVE dependants AS (
|
79
|
+
SELECT
|
80
|
+
#{connection.quote name}::regclass::oid,
|
81
|
+
0 AS level
|
82
|
+
|
83
|
+
UNION ALL
|
84
|
+
|
85
|
+
SELECT
|
86
|
+
DISTINCT(pg_rewrite.ev_class) AS oid,
|
87
|
+
dependants.level + 1 AS level
|
88
|
+
FROM pg_depend dep
|
89
|
+
INNER JOIN pg_rewrite ON pg_rewrite.oid = dep.objid
|
90
|
+
INNER JOIN dependants ON dependants.oid = dep.refobjid
|
91
|
+
WHERE pg_rewrite.ev_class != dep.refobjid AND dep.deptype = 'n'
|
92
|
+
)
|
93
|
+
|
94
|
+
SELECT
|
95
|
+
oid::regclass::text AS name,
|
96
|
+
pg_catalog.pg_get_viewdef(oid) AS definition
|
97
|
+
FROM dependants
|
98
|
+
INNER JOIN active_record_views ON active_record_views.name = oid::regclass::text
|
99
|
+
WHERE level > 0
|
100
|
+
GROUP BY oid
|
101
|
+
ORDER BY MAX(level)
|
102
|
+
;
|
103
|
+
SQL
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.without_dependencies(connection, name)
|
107
|
+
dependencies = get_view_dependencies(connection, name)
|
108
|
+
|
109
|
+
dependencies.reverse.each do |name, _|
|
110
|
+
connection.execute "DROP VIEW #{name};"
|
111
|
+
end
|
112
|
+
|
113
|
+
yield
|
114
|
+
|
115
|
+
dependencies.each do |name, definition|
|
116
|
+
connection.execute "CREATE VIEW #{name} AS #{definition};"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
78
120
|
def self.register_for_reload(sql_path, model_path)
|
79
121
|
self.registered_views << RegisteredView.new(sql_path, model_path)
|
80
122
|
end
|
@@ -9,10 +9,18 @@ describe ActiveRecordViews do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def test_view_sql
|
12
|
-
connection.select_value
|
13
|
-
SELECT
|
12
|
+
connection.select_value(<<-SQL).try(&:squish)
|
13
|
+
SELECT view_definition
|
14
14
|
FROM information_schema.views
|
15
|
-
WHERE table_name = 'test'
|
15
|
+
WHERE table_schema = 'public' AND table_name = 'test'
|
16
|
+
SQL
|
17
|
+
end
|
18
|
+
|
19
|
+
def view_names
|
20
|
+
connection.select_values <<-SQL
|
21
|
+
SELECT table_name
|
22
|
+
FROM information_schema.views
|
23
|
+
WHERE table_schema = 'public'
|
16
24
|
SQL
|
17
25
|
end
|
18
26
|
|
@@ -47,13 +55,53 @@ describe ActiveRecordViews do
|
|
47
55
|
expect(test_view_sql).to eq "SELECT 'foo'::text AS name;"
|
48
56
|
end
|
49
57
|
|
50
|
-
context 'having
|
58
|
+
context 'having dependant views' do
|
59
|
+
before do
|
60
|
+
ActiveRecordViews.create_view connection, 'dependency1', 'SELECT id FROM test;'
|
61
|
+
ActiveRecordViews.create_view connection, 'dependency2a', 'SELECT id, id * 2 AS id2 FROM dependency1;'
|
62
|
+
ActiveRecordViews.create_view connection, 'dependency2b', 'SELECT id, id * 4 AS id4 FROM dependency1;'
|
63
|
+
ActiveRecordViews.create_view connection, 'dependency3', 'SELECT * FROM dependency2b;'
|
64
|
+
ActiveRecordViews.create_view connection, 'dependency4', 'SELECT id FROM dependency1 UNION ALL SELECT id FROM dependency3;'
|
65
|
+
end
|
66
|
+
|
67
|
+
after do
|
68
|
+
dependants = %w[test dependency1 dependency2a dependency2b dependency3 dependency4]
|
69
|
+
expect(view_names).to match_array dependants
|
70
|
+
dependants.reverse.each do |name|
|
71
|
+
ActiveRecordViews.drop_view connection, name
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'updates view with compatible change' do
|
76
|
+
create_test_view 'select 2 as id'
|
77
|
+
expect(test_view_sql).to eq 'SELECT 2 AS id;'
|
78
|
+
expect(connection.select_value('SELECT id2 FROM dependency2a')).to eq '4'
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'changes incompatible with CREATE OR REPLACE' do
|
82
|
+
it 'updates view with new column added before existing' do
|
83
|
+
create_test_view "select 'foo'::text as name, 3 as id"
|
84
|
+
expect(test_view_sql).to eq "SELECT 'foo'::text AS name, 3 AS id;"
|
85
|
+
expect(connection.select_value('SELECT id2 FROM dependency2a')).to eq '6'
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'fails to update view if column used by dependant view is removed' do
|
89
|
+
expect {
|
90
|
+
create_test_view "select 'foo'::text as name"
|
91
|
+
}.to raise_error ActiveRecord::StatementInvalid, /column test.id does not exist/
|
92
|
+
expect(test_view_sql).to eq 'SELECT 1 AS id;'
|
93
|
+
expect(connection.select_value('SELECT id2 FROM dependency2a')).to eq '2'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe 'with unmanaged dependant view' do
|
51
99
|
before do
|
52
|
-
connection.execute 'CREATE VIEW dependency AS SELECT
|
100
|
+
connection.execute 'CREATE VIEW dependency AS SELECT id FROM test'
|
53
101
|
end
|
54
102
|
|
55
103
|
after do
|
56
|
-
connection.execute 'DROP VIEW dependency'
|
104
|
+
connection.execute 'DROP VIEW dependency;'
|
57
105
|
end
|
58
106
|
|
59
107
|
it 'updates view with compatible change' do
|
@@ -61,10 +109,10 @@ describe ActiveRecordViews do
|
|
61
109
|
expect(test_view_sql).to eq 'SELECT 2 AS id;'
|
62
110
|
end
|
63
111
|
|
64
|
-
it 'fails to update view with incompatible
|
112
|
+
it 'fails to update view with incompatible change' do
|
65
113
|
expect {
|
66
|
-
create_test_view "
|
67
|
-
}.to raise_error ActiveRecord::StatementInvalid, /
|
114
|
+
create_test_view "SELECT 'foo'::text as name, 4 as id"
|
115
|
+
}.to raise_error ActiveRecord::StatementInvalid, /view dependency depends on view test/
|
68
116
|
expect(test_view_sql).to eq 'SELECT 1 AS id;'
|
69
117
|
end
|
70
118
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Weathered
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-12-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
version: '3.1'
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: '4.
|
22
|
+
version: '4.2'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -29,7 +29,7 @@ dependencies:
|
|
29
29
|
version: '3.1'
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '4.
|
32
|
+
version: '4.2'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: appraisal
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +94,7 @@ extensions: []
|
|
94
94
|
extra_rdoc_files: []
|
95
95
|
files:
|
96
96
|
- ".gitignore"
|
97
|
+
- ".rspec"
|
97
98
|
- Appraisals
|
98
99
|
- Gemfile
|
99
100
|
- LICENSE.txt
|
@@ -102,6 +103,7 @@ files:
|
|
102
103
|
- activerecord_views.gemspec
|
103
104
|
- gemfiles/rails3_2.gemfile
|
104
105
|
- gemfiles/rails4_0.gemfile
|
106
|
+
- gemfiles/rails4_1.gemfile
|
105
107
|
- lib/active_record_views.rb
|
106
108
|
- lib/active_record_views/checksum_cache.rb
|
107
109
|
- lib/active_record_views/extension.rb
|