activerecord_views 0.0.2 → 0.0.3
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/.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
|