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