views_in_migrations 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +20 -0
- data/README.rdoc +24 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/views_in_migrations.rb +171 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/views_in_migrations_spec.rb +71 -0
- metadata +109 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 ASEE
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= Views In Migrations
|
2
|
+
|
3
|
+
Use MySQL views to back ActiveRecord models, and define them in your migrations.
|
4
|
+
|
5
|
+
ViewsInMigrations provides helper methods for using MySQL views behind ActiveRecord models. It helps you define, modify, test, and refresh your views. And it also handles correctly dumping the definitions to schema.rb.
|
6
|
+
|
7
|
+
|
8
|
+
= Running the Specs
|
9
|
+
|
10
|
+
1. adjust the database connection in spec_helper.rb to match your system.
|
11
|
+
|
12
|
+
== Note on Patches/Pull Requests
|
13
|
+
|
14
|
+
* Fork the project.
|
15
|
+
* Make your feature addition or bug fix.
|
16
|
+
* Add tests for it. This is important so I don't break it in a
|
17
|
+
future version unintentionally.
|
18
|
+
* Commit, do not mess with rakefile, version, or history.
|
19
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
20
|
+
* Send me a pull request. Bonus points for topic branches.
|
21
|
+
|
22
|
+
== Copyright
|
23
|
+
|
24
|
+
Copyright (c) 2010 ASEE. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "views_in_migrations"
|
8
|
+
gem.summary = %Q{Use MySQL views to back ActiveRecord models, and define them in your migrations.}
|
9
|
+
gem.description = %Q{ViewsInMigrations provides helper methods for using MySQL views behind ActiveRecord models. It helps you define, modify, test, and refresh your views. And it also handles correctly dumping the definitions to schema.rb.}
|
10
|
+
gem.email = "asolove@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/asolove/views_in_migrations"
|
12
|
+
gem.authors = ["asolove"]
|
13
|
+
gem.add_development_dependency "rspec", ">= 1.2.9"
|
14
|
+
gem.add_dependency('active_record', '>= 2.3.2')
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'spec/rake/spectask'
|
22
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
23
|
+
spec.libs << 'lib' << 'spec'
|
24
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
25
|
+
end
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
28
|
+
spec.libs << 'lib' << 'spec'
|
29
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
30
|
+
spec.rcov = true
|
31
|
+
end
|
32
|
+
|
33
|
+
task :spec => :check_dependencies
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
|
37
|
+
require 'rake/rdoctask'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "views_in_migrations #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_record'
|
3
|
+
|
4
|
+
module ViewsInMigrations
|
5
|
+
|
6
|
+
def self.views_supported?
|
7
|
+
ActiveRecord::Base.connection.send(:supports_views?)
|
8
|
+
rescue
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
module SchemaStatements
|
13
|
+
def drop_view(view_name, options = {})
|
14
|
+
execute "DROP VIEW IF EXISTS #{quote_table_name(view_name)}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_view(view_name, *args)
|
18
|
+
options = {}
|
19
|
+
|
20
|
+
if args.first.kind_of?(String)
|
21
|
+
select_statement = args.first
|
22
|
+
options = args.extract_options!
|
23
|
+
end
|
24
|
+
|
25
|
+
if options[:force] && table_exists?(view_name)
|
26
|
+
drop_view(view_name, options)
|
27
|
+
end
|
28
|
+
|
29
|
+
if select_statement
|
30
|
+
options[:options] ||= " WITH CHECK OPTION"
|
31
|
+
view_sql = "CREATE VIEW #{quote_table_name(view_name)} AS "
|
32
|
+
view_sql << select_statement
|
33
|
+
view_sql << "#{options[:options]}"
|
34
|
+
execute view_sql
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def view_definition(view_name)
|
39
|
+
select_value(%{
|
40
|
+
SELECT view_definition FROM information_schema.views
|
41
|
+
WHERE table_name = #{view_name.inspect}
|
42
|
+
AND table_schema = #{current_database.inspect}
|
43
|
+
})
|
44
|
+
end
|
45
|
+
|
46
|
+
def define_view(view_name, for_dump = false)
|
47
|
+
sanitize_view_sql(view_definition(view_name), for_dump)
|
48
|
+
end
|
49
|
+
|
50
|
+
def sanitize_view_sql(view_sql, for_dump = false)
|
51
|
+
sql = view_sql.dup
|
52
|
+
|
53
|
+
# remove newlines
|
54
|
+
sql.gsub!(/\n|^\s*|\s*$/,'')
|
55
|
+
# we can just use SELECT *...
|
56
|
+
sql.gsub!(/SELECT .* FROM/i, "SELECT * FROM" )
|
57
|
+
sql.gsub!('where', 'WHERE')
|
58
|
+
|
59
|
+
if for_dump
|
60
|
+
#insert newlines for readability
|
61
|
+
sql.gsub!(/(from|where|in|\() /i, '\n\1 ')
|
62
|
+
end
|
63
|
+
|
64
|
+
sql
|
65
|
+
end
|
66
|
+
|
67
|
+
def views
|
68
|
+
select_values( %{
|
69
|
+
select table_name
|
70
|
+
FROM information_schema.views
|
71
|
+
WHERE table_schema = #{current_database.inspect}
|
72
|
+
})
|
73
|
+
end
|
74
|
+
|
75
|
+
def table_is_view?(table_name)
|
76
|
+
views.include?(table_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def refresh_view(view_name)
|
80
|
+
raise "#{view_name.inspect} is not a view" unless table_is_view?(view_name)
|
81
|
+
sql = define_view(view_name)
|
82
|
+
drop_view(view_name)
|
83
|
+
create_view(view_name,sql)
|
84
|
+
end
|
85
|
+
|
86
|
+
def refresh_views
|
87
|
+
views.each{|view| refresh_view!(view)}
|
88
|
+
end
|
89
|
+
|
90
|
+
def assert_presence_of_column(klass_name, column)
|
91
|
+
unless current_column_names_for(klass_name).include?(column.to_s)
|
92
|
+
raise Mysql::Error.new("Invalid view on #{klass_name}, missing required column: #{column}")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def assert_absence_of_column(klass_name, column)
|
97
|
+
if current_column_names_for(klass_name).include?(column.to_s)
|
98
|
+
raise Mysql::Error.new("Invalid view on #{klass_name}, has invalid column: #{column}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def current_column_names_for(klass_name)
|
103
|
+
klass = klass_name.to_s.classify.constantize
|
104
|
+
klass.reset_column_information
|
105
|
+
klass.column_names
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
module SchemaDumper
|
110
|
+
# Only output normal tables in the body of the schema
|
111
|
+
def table_with_views_in_migrations(table, stream)
|
112
|
+
table_without_views_in_migrations(table, stream) unless @connection.table_is_view?(table)
|
113
|
+
end
|
114
|
+
|
115
|
+
# After all table definitions, print view definitions
|
116
|
+
def trailer_with_views_in_migrations(stream)
|
117
|
+
views(stream)
|
118
|
+
|
119
|
+
trailer_without_views_in_migrations(stream)
|
120
|
+
end
|
121
|
+
|
122
|
+
def views(stream)
|
123
|
+
@connection.views.each do |table|
|
124
|
+
stream.puts "if ViewsInMigrations.use_views?(current_database)"
|
125
|
+
view(table,stream)
|
126
|
+
stream.puts "else\n\n"
|
127
|
+
table_without_views_in_migrations(table, stream)
|
128
|
+
stream.puts "end\n\n"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def view(view_name, stream)
|
133
|
+
stream.puts <<-WARNING
|
134
|
+
#############################################################
|
135
|
+
# Warning: SchemaDumper cannot determine view dependencies! #
|
136
|
+
# You may have to manually reorder your views to run #
|
137
|
+
# `rake db:schema:load` successfully. #
|
138
|
+
#############################################################
|
139
|
+
|
140
|
+
WARNING
|
141
|
+
|
142
|
+
out = StringIO.new
|
143
|
+
out.puts " create_view #{view_name.inspect}, %{"
|
144
|
+
|
145
|
+
sql = @connection.define_view(view_name, true)
|
146
|
+
|
147
|
+
sql.split('\n').each do |line|
|
148
|
+
out.puts " #{line}"
|
149
|
+
end
|
150
|
+
|
151
|
+
out.puts " }, :force => true\n\n"
|
152
|
+
|
153
|
+
out.rewind
|
154
|
+
stream.print out.read
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
if ViewsInMigrations.views_supported?
|
160
|
+
|
161
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
|
162
|
+
include ViewsInMigrations::SchemaStatements
|
163
|
+
end
|
164
|
+
|
165
|
+
ActiveRecord::SchemaDumper.class_eval do
|
166
|
+
include ViewsInMigrations::SchemaDumper
|
167
|
+
alias_method_chain :table, :views_in_migrations
|
168
|
+
alias_method_chain :trailer, :views_in_migrations
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# Find source files
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
|
5
|
+
require 'spec'
|
6
|
+
require 'spec/autorun'
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'active_record'
|
10
|
+
|
11
|
+
# Establish connection
|
12
|
+
conn = { :adapter => 'mysql',
|
13
|
+
:database => '',
|
14
|
+
:username => 'root',
|
15
|
+
:password => '',
|
16
|
+
:encoding => 'utf8' }
|
17
|
+
|
18
|
+
conn[:socket] = Pathname.glob(%w[
|
19
|
+
/opt/local/var/run/mysql5/mysqld.sock
|
20
|
+
/tmp/mysqld.sock
|
21
|
+
/tmp/mysql.sock
|
22
|
+
/var/mysql/mysql.sock
|
23
|
+
/var/run/mysqld/mysqld.sock
|
24
|
+
]).find { |path| path.socket? }
|
25
|
+
|
26
|
+
ActiveRecord::Base.establish_connection(conn)
|
27
|
+
ActiveRecord::Base.connection.recreate_database("views_in_migrations_test")
|
28
|
+
|
29
|
+
conn[:database] = "views_in_migrations_test"
|
30
|
+
ActiveRecord::Base.establish_connection(conn)
|
31
|
+
|
32
|
+
# Must be required after AR connection is established
|
33
|
+
require 'views_in_migrations'
|
34
|
+
|
35
|
+
Spec::Runner.configure do |config|
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "ViewsInMigrations" do
|
4
|
+
before(:all) do
|
5
|
+
ActiveRecord::Base.connection.create_table("people") do |t|
|
6
|
+
t.id
|
7
|
+
t.string :name
|
8
|
+
t.boolean :manager
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveRecord::Base.connection.create_view("managers", "SELECT * from people where manager IS TRUE")
|
12
|
+
class Person < ActiveRecord::Base; end;
|
13
|
+
class Manager < ActiveRecord::Base; end;
|
14
|
+
|
15
|
+
@conn = ActiveRecord::Base.connection
|
16
|
+
end
|
17
|
+
|
18
|
+
it "can use views with mysql" do
|
19
|
+
ViewsInMigrations.views_supported?.should == true
|
20
|
+
end
|
21
|
+
|
22
|
+
it "knows a view from a table" do
|
23
|
+
@conn.table_is_view?("people").should == false
|
24
|
+
@conn.table_is_view?("managers").should == true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "quietly drops a non-existing view" do
|
28
|
+
@conn.drop_view("doesn't_exist")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "creates a view" do
|
32
|
+
lambda { @conn.create_view("drones", "SELECT * from people where manager IS FALSE") }.should_not raise_error()
|
33
|
+
@conn.views.should include("drones")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "drops an existing view" do
|
37
|
+
lambda { @conn.drop_view("drones")}.should_not raise_error()
|
38
|
+
@conn.views.should_not include("drones")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "finds view rows and creates AR instances" do
|
42
|
+
@bob = Person.create :name => "Bob", :manager => true
|
43
|
+
@bob.id.should == Manager.find_by_name("Bob").id
|
44
|
+
end
|
45
|
+
|
46
|
+
it "gets column names from the database" do
|
47
|
+
Manager.should_receive(:reset_column_information).and_return(true)
|
48
|
+
Manager.should_receive(:column_names).and_return(["a"])
|
49
|
+
|
50
|
+
@conn.current_column_names_for(Manager).should == ["a"]
|
51
|
+
end
|
52
|
+
|
53
|
+
it "asserts the presence and absence of columns" do
|
54
|
+
lambda { @conn.assert_presence_of_column Manager, :name
|
55
|
+
@conn.assert_absence_of_column Manager, :fake }.should_not raise_error
|
56
|
+
lambda { @conn.assert_presence_of_column Manager, :fake }.should raise_error(Mysql::Error)
|
57
|
+
lambda { @conn.assert_absence_of_column Manager, :name }.should raise_error(Mysql::Error)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "returns the definition of a view" do
|
61
|
+
# @conn.view_definition('managers').should ??
|
62
|
+
end
|
63
|
+
|
64
|
+
it "dumps views after tables" do
|
65
|
+
stream = StringIO.new
|
66
|
+
ActiveRecord::SchemaDumper.dump(@conn, stream)
|
67
|
+
stream.rewind
|
68
|
+
schema = stream.read
|
69
|
+
schema.index("managers").should > schema.index("people")
|
70
|
+
end
|
71
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: views_in_migrations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- asolove
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-06-25 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 13
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 2
|
33
|
+
- 9
|
34
|
+
version: 1.2.9
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: active_record
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 7
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 3
|
49
|
+
- 2
|
50
|
+
version: 2.3.2
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
description: ViewsInMigrations provides helper methods for using MySQL views behind ActiveRecord models. It helps you define, modify, test, and refresh your views. And it also handles correctly dumping the definitions to schema.rb.
|
54
|
+
email: asolove@gmail.com
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files:
|
60
|
+
- LICENSE
|
61
|
+
- README.rdoc
|
62
|
+
files:
|
63
|
+
- .document
|
64
|
+
- .gitignore
|
65
|
+
- LICENSE
|
66
|
+
- README.rdoc
|
67
|
+
- Rakefile
|
68
|
+
- VERSION
|
69
|
+
- lib/views_in_migrations.rb
|
70
|
+
- spec/spec.opts
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
- spec/views_in_migrations_spec.rb
|
73
|
+
has_rdoc: true
|
74
|
+
homepage: http://github.com/asolove/views_in_migrations
|
75
|
+
licenses: []
|
76
|
+
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options:
|
79
|
+
- --charset=UTF-8
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
hash: 3
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
version: "0"
|
100
|
+
requirements: []
|
101
|
+
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.3.7
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: Use MySQL views to back ActiveRecord models, and define them in your migrations.
|
107
|
+
test_files:
|
108
|
+
- spec/spec_helper.rb
|
109
|
+
- spec/views_in_migrations_spec.rb
|