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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a3d25414ba7a2d31b49f8604f17f5c2d73824135
4
- data.tar.gz: a68b23bae39199bfa2ae160dfc0bb0e091a2dc88
3
+ metadata.gz: 9f28ee79b39ed0d431534523ef129ba9e63e8aad
4
+ data.tar.gz: 39e683254984dff81f2fdf7e43a3b96588738612
5
5
  SHA512:
6
- metadata.gz: e41efe797b61dc942ac41b595b39b0d3c651b2e2966132b313b58f0d342f06fbb170239fc7fe079b879a7a60ea60f02688ee861b6d96f620b745fd4e7bdf2efe
7
- data.tar.gz: 090e0cef956babdaacb0fb71199aff94a5e38f04c8b68f1c8b885c99e18f09d80a31e9c145aa0cf2d0ac66e77fe95fc8d530136d7840f34a104b260721b91784
6
+ metadata.gz: 2654eb5b8107e4cac185668a89769569efae3a13d627029da037ad2a4b0faea6fbfeb9695f67453175300b7899de19aac21c911ea240f9be0c6dc72b54dcb3e9
7
+ data.tar.gz: c5c7685c4598c46c43230958fcac5336b906a43b77e15d0c34a5ab73b373d02d51d263e1b8f11ebb8ae35780f26124cd3776fbd497d9f2f9a27b74485d262c92
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format progress
data/Appraisals CHANGED
@@ -5,3 +5,7 @@ end
5
5
  appraise 'rails4_0' do
6
6
  gem 'rails', '~> 4.0.0'
7
7
  end
8
+
9
+ appraise 'rails4_1' do
10
+ gem 'rails', '~> 4.1.0'
11
+ end
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
- ActiveRecordViews.load_path = ['.']
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
@@ -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.1']
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'
@@ -4,4 +4,4 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 3.2.0"
6
6
 
7
- gemspec :path=>"../"
7
+ gemspec :path => "../"
@@ -4,4 +4,4 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "rails", "~> 4.0.0"
6
6
 
7
- gemspec :path=>"../"
7
+ gemspec :path => "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 4.1.0"
6
+
7
+ gemspec :path => "../"
@@ -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
- begin
57
- connection.transaction :requires_new => true do
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
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordViews
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
@@ -9,10 +9,18 @@ describe ActiveRecordViews do
9
9
  end
10
10
 
11
11
  def test_view_sql
12
- connection.select_value <<-SQL
13
- SELECT trim(view_definition)
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 a dependant view' do
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 * FROM test'
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 signature change' do
112
+ it 'fails to update view with incompatible change' do
65
113
  expect {
66
- create_test_view "select 'foo'::text as name"
67
- }.to raise_error ActiveRecord::StatementInvalid, /cannot change name of view column/
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.2
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-06-22 00:00:00.000000000 Z
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.1'
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.1'
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