activerecord_views 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a3d25414ba7a2d31b49f8604f17f5c2d73824135
4
+ data.tar.gz: a68b23bae39199bfa2ae160dfc0bb0e091a2dc88
5
+ SHA512:
6
+ metadata.gz: e41efe797b61dc942ac41b595b39b0d3c651b2e2966132b313b58f0d342f06fbb170239fc7fe079b879a7a60ea60f02688ee861b6d96f620b745fd4e7bdf2efe
7
+ data.tar.gz: 090e0cef956babdaacb0fb71199aff94a5e38f04c8b68f1c8b885c99e18f09d80a31e9c145aa0cf2d0ac66e77fe95fc8d530136d7840f34a104b260721b91784
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  .bundle
2
2
  Gemfile.lock
3
+ gemfiles/*.gemfile.lock
3
4
  pkg
4
5
  spec/internal/log/*.log
data/Appraisals ADDED
@@ -0,0 +1,7 @@
1
+ appraise 'rails3_2' do
2
+ gem 'rails', '~> 3.2.0'
3
+ end
4
+
5
+ appraise 'rails4_0' do
6
+ gem 'rails', '~> 4.0.0'
7
+ end
data/README.markdown CHANGED
@@ -14,7 +14,7 @@ Advantages over creating views manually in migrations include:
14
14
 
15
15
  Add this line to your application's `Gemfile`:
16
16
 
17
- ```
17
+ ```ruby
18
18
  gem 'activerecord_views'
19
19
  ```
20
20
 
@@ -22,7 +22,7 @@ gem 'activerecord_views'
22
22
 
23
23
  app/models/account.rb:
24
24
 
25
- ``` ruby
25
+ ```ruby
26
26
  class Account < ActiveRecord::Base
27
27
  has_many :transactions
28
28
 
@@ -33,7 +33,7 @@ end
33
33
 
34
34
  app/models/transaction.rb:
35
35
 
36
- ``` ruby
36
+ ```ruby
37
37
  class Transaction < ActiveRecord::Base
38
38
  belongs_to :account
39
39
  end
@@ -41,7 +41,7 @@ end
41
41
 
42
42
  app/models/account_balance.rb:
43
43
 
44
- ``` ruby
44
+ ```ruby
45
45
  class AccountBalance < ActiveRecord::Base
46
46
  is_view
47
47
 
@@ -51,7 +51,7 @@ end
51
51
 
52
52
  app/models/account_balance.sql:
53
53
 
54
- ``` sql
54
+ ```sql
55
55
  SELECT accounts.id AS account_id, coalesce(sum(transactions.amount), 0) AS balance
56
56
  FROM accounts
57
57
  LEFT JOIN transactions ON accounts.id = transactions.account_id
@@ -60,7 +60,7 @@ GROUP BY accounts.id
60
60
 
61
61
  Example usage:
62
62
 
63
- ``` ruby
63
+ ```ruby
64
64
  p Account.first.balance
65
65
 
66
66
  Account.includes(:account_balance).find_each do |account|
@@ -70,7 +70,7 @@ end
70
70
 
71
71
  ### Usage outside of Rails
72
72
 
73
- ``` ruby
73
+ ```ruby
74
74
  require 'active_record'
75
75
  require 'active_record_views'
76
76
  ActiveRecordViews.load_path = ['.']
data/Rakefile CHANGED
@@ -1 +1,14 @@
1
+ require 'appraisal'
2
+ require 'bundler/setup'
1
3
  require 'bundler/gem_tasks'
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default do
9
+ if ENV['BUNDLE_GEMFILE'] == File.expand_path('Gemfile')
10
+ exec 'rake appraisal:all'
11
+ else
12
+ Rake::Task['spec'].invoke
13
+ end
14
+ end
@@ -19,6 +19,7 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.add_dependency 'activerecord', ['>= 3.1', '< 4.1']
21
21
 
22
+ gem.add_development_dependency 'appraisal'
22
23
  gem.add_development_dependency 'rspec-rails', '>= 2.14'
23
24
  gem.add_development_dependency 'combustion', '>= 0.5.1'
24
25
  gem.add_development_dependency 'pg'
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "~> 3.2.0"
6
+
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.0.0"
6
+
7
+ gemspec :path=>"../"
@@ -1,12 +1,5 @@
1
- require 'active_record'
2
-
3
1
  module ActiveRecordViews
4
2
  class ChecksumCache
5
- class Model < ActiveRecord::Base
6
- self.table_name = 'active_record_views'
7
- self.primary_key = 'name'
8
- end
9
-
10
3
  def initialize(connection)
11
4
  @connection = connection
12
5
  init_state_table!
@@ -19,15 +12,16 @@ module ActiveRecordViews
19
12
  end
20
13
 
21
14
  def get(name)
22
- Model.where(:name => name).first_or_initialize.checksum
15
+ @connection.select_value("SELECT checksum FROM active_record_views WHERE name = #{@connection.quote name}")
23
16
  end
24
17
 
25
18
  def set(name, checksum)
26
- row = Model.where(:name => name).first_or_initialize
27
19
  if checksum
28
- row.update_attributes! :checksum => checksum
20
+ if @connection.update("UPDATE active_record_views SET checksum = #{@connection.quote checksum} WHERE name = #{@connection.quote name}") == 0
21
+ @connection.insert "INSERT INTO active_record_views (name, checksum) VALUES (#{@connection.quote name}, #{@connection.quote checksum})"
22
+ end
29
23
  else
30
- row.destroy
24
+ @connection.delete "DELETE FROM active_record_views WHERE name = #{@connection.quote name}"
31
25
  end
32
26
  end
33
27
  end
@@ -3,7 +3,7 @@ module ActiveRecordViews
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  def self.currently_migrating?
6
- if defined? Rake
6
+ if defined?(Rake) && Rake.method_defined?(:application)
7
7
  Rake.application.top_level_tasks.include?('db:migrate')
8
8
  end
9
9
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveRecordViews
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -25,31 +25,54 @@ module ActiveRecordViews
25
25
  raise "could not find #{name}.sql"
26
26
  end
27
27
 
28
+ def self.without_transaction(connection)
29
+ in_transaction = if connection.respond_to? :transaction_open?
30
+ connection.transaction_open?
31
+ else
32
+ !connection.outside_transaction?
33
+ end
34
+
35
+ if in_transaction
36
+ begin
37
+ temp_connection = connection.pool.checkout
38
+ yield temp_connection
39
+ ensure
40
+ connection.pool.checkin temp_connection
41
+ end
42
+ else
43
+ yield connection
44
+ end
45
+ end
46
+
28
47
  def self.create_view(connection, name, sql)
29
- cache = ActiveRecordViews::ChecksumCache.new(connection)
30
- checksum = Digest::SHA1.hexdigest(sql)
31
- return if cache.get(name) == checksum
48
+ without_transaction connection do |connection|
49
+ cache = ActiveRecordViews::ChecksumCache.new(connection)
50
+ checksum = Digest::SHA1.hexdigest(sql)
51
+ return if cache.get(name) == checksum
32
52
 
33
- begin
34
- connection.execute "CREATE OR REPLACE VIEW #{connection.quote_table_name name} AS #{sql}"
35
- rescue ActiveRecord::StatementInvalid => original_exception
36
53
  begin
37
- connection.transaction :requires_new => true do
38
- connection.execute "DROP VIEW #{connection.quote_table_name name}"
39
- connection.execute "CREATE VIEW #{connection.quote_table_name name} AS #{sql}"
54
+ connection.execute "CREATE OR REPLACE VIEW #{connection.quote_table_name name} AS #{sql}"
55
+ rescue ActiveRecord::StatementInvalid => original_exception
56
+ begin
57
+ connection.transaction :requires_new => true do
58
+ connection.execute "DROP VIEW #{connection.quote_table_name name}"
59
+ connection.execute "CREATE VIEW #{connection.quote_table_name name} AS #{sql}"
60
+ end
61
+ rescue
62
+ raise original_exception
40
63
  end
41
- rescue
42
- raise original_exception
43
64
  end
44
- end
45
65
 
46
- cache.set name, checksum
66
+ cache.set name, checksum
67
+ end
47
68
  end
48
69
 
49
70
  def self.drop_view(connection, name)
50
- cache = ActiveRecordViews::ChecksumCache.new(connection)
51
- connection.execute "DROP VIEW IF EXISTS #{connection.quote_table_name name}"
52
- cache.set name, nil
71
+ without_transaction connection do |connection|
72
+ cache = ActiveRecordViews::ChecksumCache.new(connection)
73
+ connection.execute "DROP VIEW IF EXISTS #{connection.quote_table_name name}"
74
+ cache.set name, nil
75
+ end
53
76
  end
54
77
 
55
78
  def self.register_for_reload(sql_path, model_path)
@@ -12,6 +12,11 @@ describe ActiveRecordViews::Extension do
12
12
  expect(ExternalFileTestModel.first.name).to eq 'External SQL file'
13
13
  end
14
14
 
15
+ it 'creates database views from namespaced external SQL files' do
16
+ expect(ActiveRecordViews).to receive(:create_view).once.and_call_original
17
+ expect(Namespace::TestModel.first.name).to eq 'Namespaced SQL file'
18
+ end
19
+
15
20
  it 'errors if external SQL file is missing' do
16
21
  expect {
17
22
  MissingFileTestModel
@@ -61,7 +66,6 @@ describe ActiveRecordViews::Extension do
61
66
  File.unlink sql_file
62
67
 
63
68
  expect(ActiveRecord::Base.connection).to receive(:execute).with(/\ADROP/).once.and_call_original
64
- expect(ActiveRecord::Base.connection).to receive(:execute).with(/\A(?:RELEASE )?SAVEPOINT/).at_least(1).times.and_call_original
65
69
  test_request
66
70
  test_request # second request does not `drop_view` again
67
71
 
@@ -4,19 +4,13 @@ describe ActiveRecordViews do
4
4
  describe '.create_view' do
5
5
  let(:connection) { ActiveRecord::Base.connection }
6
6
 
7
- self.use_transactional_fixtures = false
8
- after do
9
- connection.execute 'DROP VIEW IF EXISTS test'
10
- connection.execute 'DROP TABLE IF EXISTS active_record_views'
11
- end
12
-
13
7
  def create_test_view(sql)
14
8
  ActiveRecordViews.create_view connection, 'test', sql
15
9
  end
16
10
 
17
11
  def test_view_sql
18
12
  connection.select_value <<-SQL
19
- SELECT view_definition
13
+ SELECT trim(view_definition)
20
14
  FROM information_schema.views
21
15
  WHERE table_name = 'test'
22
16
  SQL
@@ -28,6 +22,15 @@ describe ActiveRecordViews do
28
22
  expect(test_view_sql).to eq 'SELECT 1 AS id;'
29
23
  end
30
24
 
25
+ it 'persists views if transaction rolls back' do
26
+ expect(test_view_sql).to be_nil
27
+ connection.transaction :requires_new => true do
28
+ create_test_view 'select 1 as id'
29
+ raise ActiveRecord::Rollback
30
+ end
31
+ expect(test_view_sql).to eq 'SELECT 1 AS id;'
32
+ end
33
+
31
34
  context 'with existing view' do
32
35
  before do
33
36
  create_test_view 'select 1 as id'
@@ -0,0 +1,3 @@
1
+ class Namespace::TestModel < ActiveRecord::Base
2
+ is_view
3
+ end
@@ -0,0 +1 @@
1
+ SELECT 1 AS id, 'Namespaced SQL file'::text AS name;
@@ -1,3 +1,4 @@
1
1
  test:
2
2
  adapter: postgresql
3
3
  database: activerecord_views_test
4
+ min_messages: warning
data/spec/spec_helper.rb CHANGED
@@ -5,11 +5,28 @@ require 'combustion'
5
5
  require 'active_record_views'
6
6
  Combustion.initialize! :active_record, :action_controller do
7
7
  config.cache_classes = false
8
+ config.active_record.whitelist_attributes = true if Rails::VERSION::MAJOR < 4
8
9
  end
9
10
  require 'rspec/rails'
10
11
 
11
12
  RSpec.configure do |config|
12
- config.use_transactional_fixtures = true
13
+ config.use_transactional_fixtures = false
14
+
15
+ config.before do
16
+ connection = ActiveRecord::Base.connection
17
+
18
+ connection.execute 'DROP TABLE IF EXISTS active_record_views'
19
+
20
+ view_names = connection.select_values <<-SQL
21
+ SELECT table_name
22
+ FROM information_schema.views
23
+ WHERE table_schema = 'public';
24
+ SQL
25
+ view_names.each do |view_name|
26
+ connection.execute "DROP VIEW IF EXISTS #{connection.quote_table_name view_name}"
27
+ end
28
+ end
29
+
13
30
  end
14
31
 
15
32
  def test_request
metadata CHANGED
@@ -1,84 +1,89 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord_views
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
5
- prerelease:
4
+ version: 0.0.2
6
5
  platform: ruby
7
6
  authors:
8
7
  - Jason Weathered
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-08-11 00:00:00.000000000 Z
11
+ date: 2014-06-22 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: activerecord
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - ">="
20
18
  - !ruby/object:Gem::Version
21
19
  version: '3.1'
22
- - - <
20
+ - - "<"
23
21
  - !ruby/object:Gem::Version
24
22
  version: '4.1'
25
23
  type: :runtime
26
24
  prerelease: false
27
25
  version_requirements: !ruby/object:Gem::Requirement
28
- none: false
29
26
  requirements:
30
- - - ! '>='
27
+ - - ">="
31
28
  - !ruby/object:Gem::Version
32
29
  version: '3.1'
33
- - - <
30
+ - - "<"
34
31
  - !ruby/object:Gem::Version
35
32
  version: '4.1'
33
+ - !ruby/object:Gem::Dependency
34
+ name: appraisal
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
36
47
  - !ruby/object:Gem::Dependency
37
48
  name: rspec-rails
38
49
  requirement: !ruby/object:Gem::Requirement
39
- none: false
40
50
  requirements:
41
- - - ! '>='
51
+ - - ">="
42
52
  - !ruby/object:Gem::Version
43
53
  version: '2.14'
44
54
  type: :development
45
55
  prerelease: false
46
56
  version_requirements: !ruby/object:Gem::Requirement
47
- none: false
48
57
  requirements:
49
- - - ! '>='
58
+ - - ">="
50
59
  - !ruby/object:Gem::Version
51
60
  version: '2.14'
52
61
  - !ruby/object:Gem::Dependency
53
62
  name: combustion
54
63
  requirement: !ruby/object:Gem::Requirement
55
- none: false
56
64
  requirements:
57
- - - ! '>='
65
+ - - ">="
58
66
  - !ruby/object:Gem::Version
59
67
  version: 0.5.1
60
68
  type: :development
61
69
  prerelease: false
62
70
  version_requirements: !ruby/object:Gem::Requirement
63
- none: false
64
71
  requirements:
65
- - - ! '>='
72
+ - - ">="
66
73
  - !ruby/object:Gem::Version
67
74
  version: 0.5.1
68
75
  - !ruby/object:Gem::Dependency
69
76
  name: pg
70
77
  requirement: !ruby/object:Gem::Requirement
71
- none: false
72
78
  requirements:
73
- - - ! '>='
79
+ - - ">="
74
80
  - !ruby/object:Gem::Version
75
81
  version: '0'
76
82
  type: :development
77
83
  prerelease: false
78
84
  version_requirements: !ruby/object:Gem::Requirement
79
- none: false
80
85
  requirements:
81
- - - ! '>='
86
+ - - ">="
82
87
  - !ruby/object:Gem::Version
83
88
  version: '0'
84
89
  description:
@@ -88,12 +93,15 @@ executables: []
88
93
  extensions: []
89
94
  extra_rdoc_files: []
90
95
  files:
91
- - .gitignore
96
+ - ".gitignore"
97
+ - Appraisals
92
98
  - Gemfile
93
99
  - LICENSE.txt
94
100
  - README.markdown
95
101
  - Rakefile
96
102
  - activerecord_views.gemspec
103
+ - gemfiles/rails3_2.gemfile
104
+ - gemfiles/rails4_0.gemfile
97
105
  - lib/active_record_views.rb
98
106
  - lib/active_record_views/checksum_cache.rb
99
107
  - lib/active_record_views/extension.rb
@@ -107,39 +115,34 @@ files:
107
115
  - spec/internal/app/models/external_file_test_model.sql
108
116
  - spec/internal/app/models/heredoc_test_model.rb
109
117
  - spec/internal/app/models/missing_file_test_model.rb
118
+ - spec/internal/app/models/namespace/test_model.rb
119
+ - spec/internal/app/models/namespace/test_model.sql
110
120
  - spec/internal/config/database.yml
111
121
  - spec/internal/db/schema.rb
112
122
  - spec/spec_helper.rb
113
123
  homepage: http://github.com/jasoncodes/activerecord_views
114
124
  licenses:
115
125
  - MIT
126
+ metadata: {}
116
127
  post_install_message:
117
128
  rdoc_options: []
118
129
  require_paths:
119
130
  - lib
120
131
  required_ruby_version: !ruby/object:Gem::Requirement
121
- none: false
122
132
  requirements:
123
- - - ! '>='
133
+ - - ">="
124
134
  - !ruby/object:Gem::Version
125
135
  version: '0'
126
- segments:
127
- - 0
128
- hash: -3691843505786316541
129
136
  required_rubygems_version: !ruby/object:Gem::Requirement
130
- none: false
131
137
  requirements:
132
- - - ! '>='
138
+ - - ">="
133
139
  - !ruby/object:Gem::Version
134
140
  version: '0'
135
- segments:
136
- - 0
137
- hash: -3691843505786316541
138
141
  requirements: []
139
142
  rubyforge_project:
140
- rubygems_version: 1.8.24
143
+ rubygems_version: 2.2.2
141
144
  signing_key:
142
- specification_version: 3
145
+ specification_version: 4
143
146
  summary: Automatic database view creation for ActiveRecord
144
147
  test_files:
145
148
  - spec/active_record_views_extension_spec.rb
@@ -148,6 +151,8 @@ test_files:
148
151
  - spec/internal/app/models/external_file_test_model.sql
149
152
  - spec/internal/app/models/heredoc_test_model.rb
150
153
  - spec/internal/app/models/missing_file_test_model.rb
154
+ - spec/internal/app/models/namespace/test_model.rb
155
+ - spec/internal/app/models/namespace/test_model.sql
151
156
  - spec/internal/config/database.yml
152
157
  - spec/internal/db/schema.rb
153
158
  - spec/spec_helper.rb