rails_sql_views 0.6.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/CHANGELOG +5 -1
  2. data/LICENSE +7 -0
  3. data/Rakefile +30 -72
  4. data/TODO +2 -0
  5. data/lib/active_record/view.rb +76 -0
  6. data/lib/core_ext/module.rb +11 -9
  7. data/lib/rails_sql_views.rb +16 -21
  8. data/lib/rails_sql_views/connection_adapters/abstract/schema_definitions.rb +2 -6
  9. data/lib/rails_sql_views/connection_adapters/abstract/schema_statements.rb +26 -9
  10. data/lib/rails_sql_views/connection_adapters/abstract_adapter.rb +24 -3
  11. data/lib/rails_sql_views/connection_adapters/mysql_adapter.rb +36 -9
  12. data/lib/rails_sql_views/connection_adapters/oci_adapter.rb +5 -4
  13. data/lib/rails_sql_views/connection_adapters/oracle_adapter.rb +5 -4
  14. data/lib/rails_sql_views/connection_adapters/oracleenhanced_adapter.rb +39 -0
  15. data/lib/rails_sql_views/connection_adapters/oracleenhanced_adapter.rb.orig +72 -0
  16. data/lib/rails_sql_views/connection_adapters/postgresql_adapter.rb +28 -2
  17. data/lib/rails_sql_views/connection_adapters/postgresql_adapter.rb.orig +69 -0
  18. data/lib/rails_sql_views/connection_adapters/sqlite_adapter.rb +66 -0
  19. data/lib/rails_sql_views/connection_adapters/sqlserver_adapter.rb +11 -4
  20. data/lib/rails_sql_views/loader.rb +18 -0
  21. data/lib/rails_sql_views/schema_dumper.rb +53 -16
  22. data/lib/rails_sql_views/version.rb +2 -2
  23. data/test/adapter_test.rb +82 -0
  24. data/test/connection/native_mysql/connection.rb +32 -0
  25. data/test/connection/native_postgresql/connection.rb +31 -0
  26. data/test/connection/oracle_enhanced/connection.rb +29 -0
  27. data/test/models/item.rb +4 -0
  28. data/test/models/person.rb +5 -0
  29. data/test/models/person2.rb +3 -0
  30. data/test/models/place.rb +2 -0
  31. data/test/models/v_person.rb +4 -0
  32. data/test/models/v_profile.rb +3 -0
  33. data/test/schema.native_mysql.expected.rb +51 -0
  34. data/test/schema.native_postgresql.expected.rb +51 -0
  35. data/test/schema.oracle_enhanced.expected.rb +51 -0
  36. data/test/schema_dumper_test.rb +117 -0
  37. data/test/test_helper.rb +30 -0
  38. data/test/view_model_test.rb +63 -0
  39. data/test/view_operations_test.rb +36 -0
  40. metadata +88 -57
data/CHANGELOG CHANGED
@@ -15,4 +15,8 @@
15
15
 
16
16
  0.6.1 - Released June 6, 2007
17
17
  * Added test for union support
18
- * Updated tests to include new table to support union test
18
+ * Updated tests to include new table to support union test
19
+
20
+ 0.7.0
21
+ * Updated dependency versions to get on ActiveRecord 2.x. May not be compatible with Rails versions less than 2.x.
22
+ * Add nonview_tables support to *all* of the adapters (was missing from Postgres and SQLServer)
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2006-2008 Anthony Eden
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile CHANGED
@@ -3,7 +3,6 @@ require 'rake/testtask'
3
3
  require 'rake/rdoctask'
4
4
  require 'rake/packagetask'
5
5
  require 'rake/gempackagetask'
6
- require 'rake/contrib/rubyforgepublisher'
7
6
 
8
7
  require File.join(File.dirname(__FILE__), 'lib/rails_sql_views', 'version')
9
8
 
@@ -28,6 +27,17 @@ Rake::TestTask.new(:test) do |t|
28
27
  t.verbose = true
29
28
  end
30
29
 
30
+ namespace :rcov do
31
+ desc 'Measures test coverage'
32
+ task :test do
33
+ rm_f 'coverage.data'
34
+ mkdir 'coverage' unless File.exist?('coverage')
35
+ rcov = "rcov --aggregate coverage.data --text-summary -Ilib"
36
+ system("#{rcov} test/*_test.rb test/**/*_test.rb")
37
+ system("open coverage/index.html") if PLATFORM['darwin']
38
+ end
39
+ end
40
+
31
41
  desc 'Generate documentation library.'
32
42
  Rake::RDocTask.new(:rdoc) do |rdoc|
33
43
  rdoc.rdoc_dir = 'rdoc'
@@ -37,80 +47,28 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
37
47
  rdoc.rdoc_files.include('lib/**/*.rb')
38
48
  end
39
49
 
40
- PKG_FILES = FileList[
41
- 'CHANGELOG',
42
- 'README',
43
- 'Rakefile',
44
- 'bin/**/*',
45
- 'lib/**/*',
46
- ] - [ 'test' ]
47
-
48
- spec = Gem::Specification.new do |s|
49
- s.name = 'rails_sql_views'
50
- s.version = PKG_VERSION
51
- s.summary = "Adds SQL Views to Rails."
52
- s.description = <<-EOF
53
- Library which adds SQL Views to Rails.
54
- EOF
55
-
56
- s.add_dependency('activerecord', '>= 1.14.4')
57
- s.add_dependency('rake', '>= 0.7.1')
58
-
59
- s.rdoc_options << '--exclude' << '.'
60
- s.has_rdoc = false
61
-
62
- s.files = PKG_FILES.to_a.delete_if {|f| f.include?('.svn')}
63
- s.require_path = 'lib'
64
-
65
- s.author = "Anthony Eden"
66
- s.email = "anthonyeden@gmail.com"
67
- s.homepage = "http://activewarehouse.rubyforge.org/rails_sql_views"
68
- s.rubyforge_project = "activewarehouse"
69
- end
70
-
71
- Rake::GemPackageTask.new(spec) do |pkg|
72
- pkg.gem_spec = spec
73
- pkg.need_tar = true
74
- pkg.need_zip = true
75
- end
76
-
77
- desc "Generate code statistics"
78
- task :lines do
79
- lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
80
-
81
- for file_name in FileList["lib/**/*.rb"]
82
- next if file_name =~ /vendor/
83
- f = File.open(file_name)
84
-
85
- while line = f.gets
86
- lines += 1
87
- next if line =~ /^\s*$/
88
- next if line =~ /^\s*#/
89
- codelines += 1
90
- end
91
- puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
92
-
93
- total_lines += lines
94
- total_codelines += codelines
95
-
96
- lines, codelines = 0, 0
97
- end
98
-
99
- puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
100
- end
101
-
102
- desc "Publish the release files to RubyForge."
103
- task :release => [ :package ] do
104
- `rubyforge login`
105
-
106
- for ext in %w( gem tgz zip )
107
- release_command = "rubyforge add_release activewarehouse #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
108
- puts release_command
109
- system(release_command)
50
+ begin
51
+ require 'jeweler'
52
+ Jeweler::Tasks.new do |s|
53
+ s.name = "rails_sql_views"
54
+ s.summary = "Library which adds SQL Views to ActiveRecord."
55
+ s.email = "josh@technicalpickles.com"
56
+ s.homepage = "http://activewarehouse.rubyforge.org/rails_sql_views"
57
+ s.description = "Adds support for using SQL views within ActiveRecord"
58
+ s.authors = ["Anthony Eden"]
59
+ s.files = FileList[
60
+ "CHANGELOG",
61
+ "README",
62
+ "Rakefile",
63
+ "{bin,lib}/**/*"
64
+ ]
65
+ s.add_dependency 'activerecord'
110
66
  end
67
+ rescue LoadError
68
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
111
69
  end
112
70
 
113
71
  desc "Publish the API documentation"
114
72
  task :pdoc => [:rdoc] do
115
73
  Rake::SshDirPublisher.new("aeden@rubyforge.org", "/var/www/gforge-projects/activewarehouse/rails_sql_views/rdoc", "rdoc").upload
116
- end
74
+ end
data/TODO ADDED
@@ -0,0 +1,2 @@
1
+
2
+ - Use pre-existing Adapter#supports_views? where possible
@@ -0,0 +1,76 @@
1
+
2
+ # A base class for database views.
3
+ # It is primarily useful for views that are centered around a single table/model.
4
+ module ActiveRecord # :nodoc:
5
+ class View < Base
6
+ self.abstract_class = true
7
+
8
+ def readonly?
9
+ true
10
+ end
11
+
12
+ class << self
13
+ # Clones all applicable associations from +model+ to this view
14
+ # and provides an instance method
15
+ # <tt>to_<em>model</em></tt>
16
+ # that casts a view object to an object of the kind view is
17
+ # based on. This latter object may be missing attributes; to fill
18
+ # them in, call #reload.
19
+ def based_on(model)
20
+ define_method("to_#{model.name.demodulize.underscore}") do
21
+ becomes(model)
22
+ end
23
+
24
+ model.reflect_on_all_associations.each do |assoc|
25
+ clone_association(model, assoc)
26
+ end
27
+ end
28
+
29
+ # Clone one or more associations from +model+ to this view class.
30
+ #
31
+ # NOTE: Currently only <tt>belongs_to</tt>, <tt>has_many</tt> (withouth
32
+ # <tt>:through</tt>), and <tt>has_and_belongs_to_many</tt> associations
33
+ # are supported.
34
+ def clone_association(model, *associations)
35
+ associations.each do |association|
36
+ r = case association
37
+ when String, Symbol
38
+ model.reflect_on_association(association.to_sym)
39
+ when ActiveRecord::Reflection::AssociationReflection
40
+ association
41
+ else
42
+ raise ArgumentError, "Unrecognized association #{association.inspect}; must be a Symbol, String, or AssociationReflection."
43
+ end
44
+ case r.macro
45
+ when :belongs_to
46
+ if self.column_names.include?(r.primary_key_name.to_s)
47
+ if !r.options[:foreign_type] || self.column_names.include?(r.options[:foreign_type])
48
+ options = r.options.merge(
49
+ :class_name => r.class_name,
50
+ :foreign_key => r.primary_key_name
51
+ )
52
+ belongs_to r.name, options
53
+ end
54
+ end
55
+ when :has_many
56
+ ### TODO :through assocications
57
+ options = r.options.merge(
58
+ :class_name => r.class_name,
59
+ :foreign_key => r.primary_key_name
60
+ )
61
+ has_many r.name, options
62
+ when :has_and_belongs_to_many
63
+ options = r.options.merge(
64
+ :class_name => r.class_name,
65
+ :foreign_key => r.primary_key_name,
66
+ :association_foreign_key => r.association_foreign_key
67
+ )
68
+ has_and_belongs_to_many r.name, options
69
+ when :has_one
70
+ ### TODO
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -1,11 +1,13 @@
1
1
  # This is required for 1.1.6 support
2
- class Module
3
- def alias_method_chain(target, feature)
4
- # Strip out punctuation on predicates or bang methods since
5
- # e.g. target?_without_feature is not a valid method name.
6
- aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
7
- yield(aliased_target, punctuation) if block_given?
8
- alias_method "#{aliased_target}_without_#{feature}#{punctuation}", target
9
- alias_method target, "#{aliased_target}_with_#{feature}#{punctuation}"
2
+ unless Module.respond_to?(:alias_method_chain)
3
+ class Module
4
+ def alias_method_chain(target, feature)
5
+ # Strip out punctuation on predicates or bang methods since
6
+ # e.g. target?_without_feature is not a valid method name.
7
+ aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
8
+ yield(aliased_target, punctuation) if block_given?
9
+ alias_method "#{aliased_target}_without_#{feature}#{punctuation}", target
10
+ alias_method target, "#{aliased_target}_with_#{feature}#{punctuation}"
11
+ end
10
12
  end
11
- end
13
+ end
@@ -21,32 +21,27 @@
21
21
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
22
  #++
23
23
 
24
- $:.unshift(File.dirname(__FILE__)) unless
25
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
26
-
27
- require 'rubygems'
28
- unless Kernel.respond_to?(:gem)
29
- Kernel.send :alias_method, :gem, :require_gem
30
- end
31
-
32
- unless defined?(ActiveRecord)
33
- begin
34
- $:.unshift(File.dirname(__FILE__) + "/../../activerecord/lib")
35
- require 'active_record'
36
- rescue LoadError
37
- gem 'activerecord'
38
- end
39
- end
24
+ $:.unshift(File.dirname(__FILE__))
25
+
26
+ require 'active_record'
40
27
 
41
28
  require 'core_ext/module'
42
29
 
43
30
  require 'rails_sql_views/connection_adapters/abstract/schema_definitions'
44
31
  require 'rails_sql_views/connection_adapters/abstract/schema_statements'
45
- require 'rails_sql_views/connection_adapters/mysql_adapter'
46
- require 'rails_sql_views/connection_adapters/postgresql_adapter'
47
- require 'rails_sql_views/connection_adapters/sqlserver_adapter'
32
+ require 'rails_sql_views/connection_adapters/abstract_adapter'
48
33
  require 'rails_sql_views/schema_dumper'
34
+ require 'rails_sql_views/loader'
49
35
 
50
- class ActiveRecord::ConnectionAdapters::AbstractAdapter
36
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
51
37
  include RailsSqlViews::ConnectionAdapters::SchemaStatements
52
- end
38
+ def self.inherited(sub)
39
+ RailsSqlViews::Loader.load_extensions
40
+ end
41
+ end
42
+
43
+ ActiveRecord::SchemaDumper.class_eval do
44
+ include RailsSqlViews::SchemaDumper
45
+ end
46
+
47
+ RailsSqlViews::Loader.load_extensions
@@ -10,10 +10,6 @@ module RailsSqlViews
10
10
  @select_query = select_query
11
11
  end
12
12
 
13
- def [](name)
14
- @columns[name.to_s]
15
- end
16
-
17
13
  def column(name)
18
14
  column = name.to_s
19
15
  @columns << column unless @columns.include? column
@@ -21,7 +17,7 @@ module RailsSqlViews
21
17
  end
22
18
 
23
19
  def to_sql
24
- @columns * ', '
20
+ @columns.collect { |c| @base.quote_column_name(c) } * ', '
25
21
  end
26
22
 
27
23
  end
@@ -64,4 +60,4 @@ module RailsSqlViews
64
60
  end
65
61
  end
66
62
  end
67
- end
63
+ end
@@ -1,6 +1,10 @@
1
1
  module RailsSqlViews
2
2
  module ConnectionAdapters # :nodoc:
3
3
  module SchemaStatements
4
+ def self.included(base)
5
+ base.alias_method_chain :drop_table, :cascade
6
+ end
7
+
4
8
  # Create a view.
5
9
  # The +options+ hash can include the following keys:
6
10
  # [<tt>:check_option</tt>]
@@ -9,17 +13,23 @@ module RailsSqlViews
9
13
  def create_view(name, select_query, options={})
10
14
  if supports_views?
11
15
  view_definition = ViewDefinition.new(self, select_query)
12
-
13
- yield view_definition
16
+
17
+ if block_given?
18
+ yield view_definition
19
+ end
14
20
 
15
21
  if options[:force]
16
22
  drop_view(name) rescue nil
17
23
  end
18
24
 
19
25
  create_sql = "CREATE VIEW "
20
- create_sql << "#{name} ("
21
- create_sql << view_definition.to_sql
22
- create_sql << ") AS #{view_definition.select_query}"
26
+ create_sql << "#{quote_table_name(name)} "
27
+ if supports_view_columns_definition? && !view_definition.to_sql.blank?
28
+ create_sql << "("
29
+ create_sql << view_definition.to_sql
30
+ create_sql << ") "
31
+ end
32
+ create_sql << "AS #{view_definition.select_query}"
23
33
  create_sql << " WITH #{options[:check_option]} CHECK OPTION" if options[:check_option]
24
34
  execute create_sql
25
35
  end
@@ -40,10 +50,17 @@ module RailsSqlViews
40
50
  drop_view(new_name) rescue nil
41
51
  end
42
52
 
43
- view_sql = "CREATE VIEW #{new_name} (#{mapper.view_cols.join(', ')}) "
44
- view_sql << "AS SELECT #{mapper.select_cols.join(', ')} FROM #{old_name}"
53
+ view_sql = "CREATE VIEW #{new_name} "
54
+ if supports_view_columns_definition?
55
+ view_sql << "(#{mapper.view_cols.collect { |c| quote_column_name(c) }.join(', ')}) "
56
+ end
57
+ view_sql << "AS SELECT #{mapper.select_cols.collect { |c| quote_column_name(c) }.join(', ')} FROM #{old_name}"
45
58
  execute view_sql
46
59
  end
60
+
61
+ def drop_table_with_cascade(table_name, options = {})
62
+ execute "DROP TABLE #{quote_table_name(table_name)} CASCADE"
63
+ end
47
64
 
48
65
  # Drop a view.
49
66
  # The +options+ hash can include the following keys:
@@ -52,11 +69,11 @@ module RailsSqlViews
52
69
  # database documentation to determine what drop behaviors are available.
53
70
  def drop_view(name, options={})
54
71
  if supports_views?
55
- drop_sql = "DROP VIEW #{name}"
72
+ drop_sql = "DROP VIEW #{quote_table_name(name)}"
56
73
  drop_sql << " #{options[:drop_behavior]}" if options[:drop_behavior]
57
74
  execute drop_sql
58
75
  end
59
76
  end
60
77
  end
61
78
  end
62
- end
79
+ end
@@ -1,10 +1,31 @@
1
- module ActiveRecord
1
+ module RailsSqlViews
2
2
  module ConnectionAdapters
3
- class AbstractAdapter
3
+ module AbstractAdapter
4
+ def self.included(base)
5
+ base.alias_method_chain :disable_referential_integrity, :views_excluded
6
+ end
7
+
4
8
  # Subclasses should override and return true if they support views.
5
9
  def supports_views?
6
10
  return false
7
11
  end
12
+
13
+ # Subclasses should override and return false if they don't support CASCADE
14
+ def supports_drop_table_cascade?
15
+ return true
16
+ end
17
+
18
+ def disable_referential_integrity_with_views_excluded(&block)
19
+ self.class.send(:alias_method, :original_tables_method, :tables)
20
+ self.class.send(:alias_method, :tables, :base_tables)
21
+ disable_referential_integrity_without_views_excluded(&block)
22
+ ensure
23
+ self.class.send(:alias_method, :tables, :original_tables_method)
24
+ end
25
+
26
+ def supports_view_columns_definition?
27
+ true
28
+ end
8
29
 
9
30
  # Get a list of all views for the current database
10
31
  def views(name = nil)
@@ -17,4 +38,4 @@ module ActiveRecord
17
38
  end
18
39
  end
19
40
  end
20
- end
41
+ end
@@ -1,29 +1,56 @@
1
- module ActiveRecord
1
+ module RailsSqlViews
2
2
  module ConnectionAdapters
3
- class MysqlAdapter
3
+ module MysqlAdapter
4
+ def self.included(base)
5
+ if base.private_method_defined?(:supports_views?)
6
+ base.send(:public, :supports_views?)
7
+ end
8
+ end
9
+
4
10
  # Returns true as this adapter supports views.
5
11
  def supports_views?
6
12
  true
7
13
  end
8
14
 
9
- def tables(name = nil) #:nodoc:
15
+ def base_tables(name = nil) #:nodoc:
10
16
  tables = []
11
- execute("SHOW TABLE STATUS", name).each { |row| tables << row[0] if row[17] != 'VIEW' }
17
+ execute("SHOW FULL TABLES WHERE TABLE_TYPE='BASE TABLE'").each{|row| tables << row[0]}
12
18
  tables
13
19
  end
20
+ alias nonview_tables base_tables
14
21
 
15
22
  def views(name = nil) #:nodoc:
16
23
  views = []
17
- execute("SHOW TABLE STATUS", name).each { |row| views << row[0] if row[17] == 'VIEW' }
24
+ execute("SHOW FULL TABLES WHERE TABLE_TYPE='VIEW'").each{|row| views << row[0]}
18
25
  views
19
26
  end
27
+
28
+ def tables_with_views_included(name = nil)
29
+ nonview_tables(name) + views(name)
30
+ end
20
31
 
32
+ def structure_dump
33
+ structure = ""
34
+ base_tables.each do |table|
35
+ structure += select_one("SHOW CREATE TABLE #{quote_table_name(table)}")["Create Table"] + ";\n\n"
36
+ end
37
+
38
+ views.each do |view|
39
+ structure += select_one("SHOW CREATE VIEW #{quote_table_name(view)}")["Create View"] + ";\n\n"
40
+ end
41
+
42
+ return structure
43
+ end
44
+
21
45
  # Get the view select statement for the specified table.
22
46
  def view_select_statement(view, name=nil)
23
- row = execute("SHOW CREATE VIEW #{view}", name).each do |row|
24
- return convert_statement(row[1]) if row[0] == view
47
+ begin
48
+ row = execute("SHOW CREATE VIEW #{view}", name).each do |row|
49
+ return convert_statement(row[1]) if row[0] == view
50
+ end
51
+ rescue ActiveRecord::StatementInvalid => e
52
+ raise "No view called #{view} found"
25
53
  end
26
- raise "No view called #{view} found"
27
54
  end
28
55
 
29
56
  private
@@ -32,4 +59,4 @@ module ActiveRecord
32
59
  end
33
60
  end
34
61
  end
35
- end
62
+ end