activerecord_views 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .bundle
2
+ Gemfile.lock
3
+ pkg
4
+ spec/internal/log/*.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Jason Weathered
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,82 @@
1
+ # ActiveRecordViews
2
+
3
+ ActiveRecordViews makes it easy to create and update PostgreSQL database views for ActiveRecord models.
4
+
5
+ Advantages over creating views manually in migrations include:
6
+
7
+ * Automatic reloading in development mode.
8
+ This avoids the need to to run `rake db:migrate:redo` after every change.
9
+
10
+ * Keeps view changes in a single SQL file instead of spread across multiple migration files.
11
+ This allows changes to views to be easily reviewed with `git diff`.
12
+
13
+ ## Installation
14
+
15
+ Add this line to your application's `Gemfile`:
16
+
17
+ ```
18
+ gem 'activerecord_views'
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ app/models/account.rb:
24
+
25
+ ``` ruby
26
+ class Account < ActiveRecord::Base
27
+ has_many :transactions
28
+
29
+ has_one :account_balance
30
+ delegate :balance, :to => :account_balance
31
+ end
32
+ ```
33
+
34
+ app/models/transaction.rb:
35
+
36
+ ``` ruby
37
+ class Transaction < ActiveRecord::Base
38
+ belongs_to :account
39
+ end
40
+ ```
41
+
42
+ app/models/account_balance.rb:
43
+
44
+ ``` ruby
45
+ class AccountBalance < ActiveRecord::Base
46
+ is_view
47
+
48
+ belongs_to :account
49
+ end
50
+ ```
51
+
52
+ app/models/account_balance.sql:
53
+
54
+ ``` sql
55
+ SELECT accounts.id AS account_id, coalesce(sum(transactions.amount), 0) AS balance
56
+ FROM accounts
57
+ LEFT JOIN transactions ON accounts.id = transactions.account_id
58
+ GROUP BY accounts.id
59
+ ```
60
+
61
+ Example usage:
62
+
63
+ ``` ruby
64
+ p Account.first.balance
65
+
66
+ Account.includes(:account_balance).find_each do |account|
67
+ p account.balance
68
+ end
69
+ ```
70
+
71
+ ### Usage outside of Rails
72
+
73
+ ``` ruby
74
+ require 'active_record'
75
+ require 'active_record_views'
76
+ ActiveRecordViews.load_path = ['.']
77
+ ActiveRecordViews.init!
78
+ ```
79
+
80
+ ## License
81
+
82
+ MIT
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_record_views/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'activerecord_views'
8
+ gem.version = ActiveRecordViews::VERSION
9
+ gem.authors = ['Jason Weathered']
10
+ gem.email = ['jason@jasoncodes.com']
11
+ gem.summary = %q{Automatic database view creation for ActiveRecord}
12
+ gem.homepage = 'http://github.com/jasoncodes/activerecord_views'
13
+ gem.license = 'MIT'
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_dependency 'activerecord', ['>= 3.1', '< 4.1']
21
+
22
+ gem.add_development_dependency 'rspec-rails', '>= 2.14'
23
+ gem.add_development_dependency 'combustion', '>= 0.5.1'
24
+ gem.add_development_dependency 'pg'
25
+ end
@@ -0,0 +1,34 @@
1
+ require 'active_record'
2
+
3
+ module ActiveRecordViews
4
+ class ChecksumCache
5
+ class Model < ActiveRecord::Base
6
+ self.table_name = 'active_record_views'
7
+ self.primary_key = 'name'
8
+ end
9
+
10
+ def initialize(connection)
11
+ @connection = connection
12
+ init_state_table!
13
+ end
14
+
15
+ def init_state_table!
16
+ unless @connection.table_exists?('active_record_views')
17
+ @connection.execute 'CREATE TABLE active_record_views(name text PRIMARY KEY, checksum text NOT NULL);'
18
+ end
19
+ end
20
+
21
+ def get(name)
22
+ Model.where(:name => name).first_or_initialize.checksum
23
+ end
24
+
25
+ def set(name, checksum)
26
+ row = Model.where(:name => name).first_or_initialize
27
+ if checksum
28
+ row.update_attributes! :checksum => checksum
29
+ else
30
+ row.destroy
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,24 @@
1
+ module ActiveRecordViews
2
+ module Extension
3
+ extend ActiveSupport::Concern
4
+
5
+ def self.currently_migrating?
6
+ if defined? Rake
7
+ Rake.application.top_level_tasks.include?('db:migrate')
8
+ end
9
+ end
10
+
11
+ module ClassMethods
12
+ def is_view(sql = nil)
13
+ sql ||= begin
14
+ sql_path = ActiveRecordViews.find_sql_file(self.name.underscore)
15
+ ActiveRecordViews.register_for_reload self, sql_path
16
+ File.read sql_path
17
+ end
18
+ unless ActiveRecordViews::Extension.currently_migrating?
19
+ ActiveRecordViews.create_view self.connection, self.table_name, sql
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module ActiveRecordViews
2
+ class Railtie < ::Rails::Railtie
3
+ initializer 'active_record_views' do |app|
4
+ ActiveSupport.on_load :active_record do
5
+ ActiveRecordViews.sql_load_path << Rails.root + 'app/models'
6
+ ActiveRecordViews.init!
7
+ end
8
+
9
+ ActiveSupport.on_load :action_controller do
10
+ unless app.config.cache_classes
11
+ ActionDispatch::Callbacks.before do
12
+ ActiveRecordViews.reload_stale_views!
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,38 @@
1
+ module ActiveRecordViews
2
+ class RegisteredView
3
+ attr_reader :model_class, :sql_path
4
+
5
+ def initialize(model_class, sql_path)
6
+ @model_class_name = model_class.name
7
+ @sql_path = sql_path
8
+ update_timestamp!
9
+ end
10
+
11
+ def model_class
12
+ @model_class_name.constantize
13
+ end
14
+
15
+ def stale?
16
+ sql_timestamp != @cached_sql_timestamp
17
+ end
18
+
19
+ def reload!
20
+ if File.exists? sql_path
21
+ ActiveRecordViews.create_view model_class.connection, model_class.table_name, File.read(sql_path)
22
+ else
23
+ ActiveRecordViews.drop_view model_class.connection, model_class.table_name
24
+ end
25
+ update_timestamp!
26
+ end
27
+
28
+ private
29
+
30
+ def sql_timestamp
31
+ File.exists?(sql_path) ? File.mtime(sql_path) : nil
32
+ end
33
+
34
+ def update_timestamp!
35
+ @cached_sql_timestamp = sql_timestamp
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveRecordViews
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,66 @@
1
+ require 'active_record_views/version'
2
+ require 'active_record_views/railtie' if defined? Rails
3
+ require 'active_record_views/registered_view'
4
+ require 'active_record_views/checksum_cache'
5
+ require 'active_support/core_ext/module/attribute_accessors'
6
+ require 'digest/sha1'
7
+
8
+ module ActiveRecordViews
9
+ mattr_accessor :sql_load_path
10
+ self.sql_load_path = []
11
+
12
+ mattr_accessor :registered_views
13
+ self.registered_views = []
14
+
15
+ def self.init!
16
+ require 'active_record_views/extension'
17
+ ::ActiveRecord::Base.send :include, ActiveRecordViews::Extension
18
+ end
19
+
20
+ def self.find_sql_file(name)
21
+ self.sql_load_path.each do |dir|
22
+ path = "#{dir}/#{name}.sql"
23
+ return path if File.exists?(path)
24
+ end
25
+ raise "could not find #{name}.sql"
26
+ end
27
+
28
+ 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
32
+
33
+ begin
34
+ connection.execute "CREATE OR REPLACE VIEW #{connection.quote_table_name name} AS #{sql}"
35
+ rescue ActiveRecord::StatementInvalid => original_exception
36
+ 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}"
40
+ end
41
+ rescue
42
+ raise original_exception
43
+ end
44
+ end
45
+
46
+ cache.set name, checksum
47
+ end
48
+
49
+ 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
53
+ end
54
+
55
+ def self.register_for_reload(sql_path, model_path)
56
+ self.registered_views << RegisteredView.new(sql_path, model_path)
57
+ end
58
+
59
+ def self.reload_stale_views!
60
+ self.registered_views.each do |registered_view|
61
+ if registered_view.stale?
62
+ registered_view.reload!
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1 @@
1
+ require 'active_record_views'
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecordViews::Extension do
4
+ describe '.as_view' do
5
+ it 'creates database views from heredocs' do
6
+ expect(ActiveRecordViews).to receive(:create_view).once.and_call_original
7
+ expect(HeredocTestModel.first.name).to eq 'Here document'
8
+ end
9
+
10
+ it 'creates database views from external SQL files' do
11
+ expect(ActiveRecordViews).to receive(:create_view).once.and_call_original
12
+ expect(ExternalFileTestModel.first.name).to eq 'External SQL file'
13
+ end
14
+
15
+ it 'errors if external SQL file is missing' do
16
+ expect {
17
+ MissingFileTestModel
18
+ }.to raise_error RuntimeError, /could not find missing_file_test_model.sql/
19
+ end
20
+
21
+ it 'reloads the database view when external SQL file is modified' do
22
+ %w[foo bar baz].each do |sql|
23
+ expect(ActiveRecordViews).to receive(:create_view).with(
24
+ anything,
25
+ 'modified_file_test_models',
26
+ sql
27
+ ).once.ordered
28
+ end
29
+
30
+ with_temp_sql_dir do |temp_dir|
31
+ sql_file = File.join(temp_dir, 'modified_file_test_model.sql')
32
+ update_file sql_file, 'foo'
33
+
34
+ class ModifiedFileTestModel < ActiveRecord::Base
35
+ is_view
36
+ end
37
+
38
+ update_file sql_file, 'bar'
39
+
40
+ test_request
41
+ test_request # second request does not `create_view` again
42
+
43
+ update_file sql_file, 'baz'
44
+
45
+ test_request
46
+ end
47
+ test_request # trigger cleanup
48
+ end
49
+
50
+ it 'drops the view if the external SQL file is deleted' do
51
+ with_temp_sql_dir do |temp_dir|
52
+ sql_file = File.join(temp_dir, 'deleted_file_test_model.sql')
53
+ File.write sql_file, "SELECT 1 AS id, 'delete test'::text AS name"
54
+
55
+ class DeletedFileTestModel < ActiveRecord::Base
56
+ is_view
57
+ end
58
+
59
+ expect(DeletedFileTestModel.first.name).to eq 'delete test'
60
+
61
+ File.unlink sql_file
62
+
63
+ 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
+ test_request
66
+ test_request # second request does not `drop_view` again
67
+
68
+ expect {
69
+ DeletedFileTestModel.first.name
70
+ }.to raise_error ActiveRecord::StatementInvalid, /relation "deleted_file_test_models" does not exist/
71
+ end
72
+ end
73
+
74
+ it 'does not create if database view is initially up to date' do
75
+ ActiveRecordViews.create_view ActiveRecord::Base.connection, 'initial_create_test_models', 'SELECT 42 as id'
76
+ expect(ActiveRecord::Base.connection).to receive(:execute).with(/\ACREATE (?:OR REPLACE )?VIEW/).never
77
+ class InitialCreateTestModel < ActiveRecord::Base
78
+ is_view 'SELECT 42 as id'
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveRecordViews do
4
+ describe '.create_view' do
5
+ let(:connection) { ActiveRecord::Base.connection }
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
+ def create_test_view(sql)
14
+ ActiveRecordViews.create_view connection, 'test', sql
15
+ end
16
+
17
+ def test_view_sql
18
+ connection.select_value <<-SQL
19
+ SELECT view_definition
20
+ FROM information_schema.views
21
+ WHERE table_name = 'test'
22
+ SQL
23
+ end
24
+
25
+ it 'creates database view' do
26
+ expect(test_view_sql).to be_nil
27
+ create_test_view 'select 1 as id'
28
+ expect(test_view_sql).to eq 'SELECT 1 AS id;'
29
+ end
30
+
31
+ context 'with existing view' do
32
+ before do
33
+ create_test_view 'select 1 as id'
34
+ expect(test_view_sql).to eq 'SELECT 1 AS id;'
35
+ end
36
+
37
+ it 'updates view with compatible change' do
38
+ create_test_view 'select 2 as id'
39
+ expect(test_view_sql).to eq 'SELECT 2 AS id;'
40
+ end
41
+
42
+ it 'recreates view with incompatible change' do
43
+ create_test_view "select 'foo'::text as name"
44
+ expect(test_view_sql).to eq "SELECT 'foo'::text AS name;"
45
+ end
46
+
47
+ context 'having a dependant view' do
48
+ before do
49
+ connection.execute 'CREATE VIEW dependency AS SELECT * FROM test'
50
+ end
51
+
52
+ after do
53
+ connection.execute 'DROP VIEW dependency'
54
+ end
55
+
56
+ it 'updates view with compatible change' do
57
+ create_test_view 'select 2 as id'
58
+ expect(test_view_sql).to eq 'SELECT 2 AS id;'
59
+ end
60
+
61
+ it 'fails to update view with incompatible signature change' do
62
+ expect {
63
+ create_test_view "select 'foo'::text as name"
64
+ }.to raise_error ActiveRecord::StatementInvalid, /cannot change name of view column/
65
+ expect(test_view_sql).to eq 'SELECT 1 AS id;'
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,3 @@
1
+ class ExternalFileTestModel < ActiveRecord::Base
2
+ is_view
3
+ end
@@ -0,0 +1 @@
1
+ SELECT 1 AS id, 'External SQL file'::text AS name;
@@ -0,0 +1,5 @@
1
+ class HeredocTestModel < ActiveRecord::Base
2
+ is_view <<-SQL
3
+ SELECT 1 AS id, 'Here document'::text AS name;
4
+ SQL
5
+ end
@@ -0,0 +1,3 @@
1
+ class MissingFileTestModel < ActiveRecord::Base
2
+ is_view
3
+ end
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: postgresql
3
+ database: activerecord_views_test
@@ -0,0 +1,2 @@
1
+ ActiveRecord::Schema.define do
2
+ end
@@ -0,0 +1,39 @@
1
+ require 'bundler'
2
+ Bundler.setup
3
+
4
+ require 'combustion'
5
+ require 'active_record_views'
6
+ Combustion.initialize! :active_record, :action_controller do
7
+ config.cache_classes = false
8
+ end
9
+ require 'rspec/rails'
10
+
11
+ RSpec.configure do |config|
12
+ config.use_transactional_fixtures = true
13
+ end
14
+
15
+ def test_request
16
+ begin
17
+ Rails.application.call({'REQUEST_METHOD' => 'GET', 'PATH_INFO' => '/'})
18
+ rescue ActionController::RoutingError
19
+ end
20
+ end
21
+
22
+ def with_temp_sql_dir
23
+ Dir.mktmpdir do |temp_dir|
24
+ begin
25
+ old_sql_load_path = ActiveRecordViews.sql_load_path
26
+ ActiveRecordViews.sql_load_path = [temp_dir] + old_sql_load_path
27
+ yield temp_dir
28
+ ensure
29
+ ActiveRecordViews.sql_load_path = old_sql_load_path
30
+ end
31
+ end
32
+ end
33
+
34
+ def update_file(file, new_content)
35
+ time = File.exists?(file) ? File.mtime(file) : Time.parse('2012-01-01')
36
+ time = time + 1
37
+ File.write file, new_content
38
+ File.utime time, time, file
39
+ end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord_views
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jason Weathered
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '3.1'
22
+ - - <
23
+ - !ruby/object:Gem::Version
24
+ version: '4.1'
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '3.1'
33
+ - - <
34
+ - !ruby/object:Gem::Version
35
+ version: '4.1'
36
+ - !ruby/object:Gem::Dependency
37
+ name: rspec-rails
38
+ requirement: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '2.14'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '2.14'
52
+ - !ruby/object:Gem::Dependency
53
+ name: combustion
54
+ requirement: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: 0.5.1
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ! '>='
66
+ - !ruby/object:Gem::Version
67
+ version: 0.5.1
68
+ - !ruby/object:Gem::Dependency
69
+ name: pg
70
+ requirement: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ description:
85
+ email:
86
+ - jason@jasoncodes.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.markdown
95
+ - Rakefile
96
+ - activerecord_views.gemspec
97
+ - lib/active_record_views.rb
98
+ - lib/active_record_views/checksum_cache.rb
99
+ - lib/active_record_views/extension.rb
100
+ - lib/active_record_views/railtie.rb
101
+ - lib/active_record_views/registered_view.rb
102
+ - lib/active_record_views/version.rb
103
+ - lib/activerecord_views.rb
104
+ - spec/active_record_views_extension_spec.rb
105
+ - spec/active_record_views_spec.rb
106
+ - spec/internal/app/models/external_file_test_model.rb
107
+ - spec/internal/app/models/external_file_test_model.sql
108
+ - spec/internal/app/models/heredoc_test_model.rb
109
+ - spec/internal/app/models/missing_file_test_model.rb
110
+ - spec/internal/config/database.yml
111
+ - spec/internal/db/schema.rb
112
+ - spec/spec_helper.rb
113
+ homepage: http://github.com/jasoncodes/activerecord_views
114
+ licenses:
115
+ - MIT
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ segments:
127
+ - 0
128
+ hash: -3691843505786316541
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ segments:
136
+ - 0
137
+ hash: -3691843505786316541
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 1.8.24
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: Automatic database view creation for ActiveRecord
144
+ test_files:
145
+ - spec/active_record_views_extension_spec.rb
146
+ - spec/active_record_views_spec.rb
147
+ - spec/internal/app/models/external_file_test_model.rb
148
+ - spec/internal/app/models/external_file_test_model.sql
149
+ - spec/internal/app/models/heredoc_test_model.rb
150
+ - spec/internal/app/models/missing_file_test_model.rb
151
+ - spec/internal/config/database.yml
152
+ - spec/internal/db/schema.rb
153
+ - spec/spec_helper.rb