activerecord_views 0.0.1

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