views_in_migrations 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
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.
@@ -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.
@@ -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
@@ -0,0 +1 @@
1
+ --color
@@ -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