rails_sql_triggers 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,18 @@
1
+ 0.1.0 - Released Dec 27, 2006
2
+ * Initial release
3
+
4
+ 0.5.0 - Released Dec 29, 2006
5
+ * Added support for PostgreSQL (Michael Schuerig)
6
+ * Fixed the schema dumper
7
+
8
+ 0.5.1 - Released Jan 10, 2007
9
+ * Patch by Clifford T. Matthews to use String.dump to dump out the view select statement
10
+
11
+ 0.6.0 - Released May 9, 2007
12
+ * Added support for SQL Server (Seth Ladd)-
13
+ * Added support for using views to map non-friendly database field names to AR-friendly names (Nathan Vack)
14
+ * Added support for Oracle (Alistair Davidson)
15
+
16
+ 0.6.1 - Released June 6, 2007
17
+ * Added test for union support
18
+ * Updated tests to include new table to support union test
data/README ADDED
@@ -0,0 +1,72 @@
1
+ == Rails SQL Triggers
2
+
3
+ Library which adds SQL Triggers to Rails. Adds create_trigger and drop_trigger to the ActiveRecord::ConnectionAdapters::AbstractAdapter (which makes them available to migrations) and adds support for dumping triggers in the ActiveRecord::SchemaDumper.
4
+
5
+ == Installation
6
+
7
+ To install:
8
+
9
+ gem install rails_sql_triggers
10
+
11
+ Then add the following to your Rails config/environment.rb:
12
+
13
+ require_gem 'rails_sql_triggers'
14
+ require 'rails_sql_triggers'
15
+
16
+ == Usage
17
+
18
+ You can then use create_trigger and drop_trigger in your migrations. For example:
19
+
20
+ class CreatePersonChangeTrigger < ActiveRecord::Migration
21
+ def self.up
22
+ create_table :person_changes do |t|
23
+ t.integer :person_id, :null => false
24
+ t.string :first_name, :null => false
25
+ t.string :last_name, :null => false
26
+ t.string :email, :null => false
27
+ t.datetime :effective_start
28
+ t.datetime :effective_end
29
+ t.integer :deleted_flag, :null => false, :default => 0
30
+ t.string :updated_by
31
+ end
32
+
33
+ create_trigger :person_change_trig, :people, [:insert, :update], <<EOSQL
34
+ DECLARE @person_id INT
35
+ DECLARE @first_name VARCHAR(255)
36
+ DECLARE @last_name VARCHAR(255)
37
+ DECLARE @email VARCHAR(255)
38
+ DECLARE @effective_start DATETIME
39
+ DECLARE @updated_by VARCHAR(255)
40
+ SELECT @person_id = (SELECT id FROM inserted)
41
+ SELECT @first_name = (SELECT first_name FROM inserted)
42
+ SELECT @last_name = (SELECT last_name FROM inserted)
43
+ SELECT @email = (SELECT email FROM inserted)
44
+ SELECT @effective_start = (SELECT updated_at FROM inserted)
45
+ SELECT @updated_by = (SELECT updated_by FROM inserted)
46
+ UPDATE person_changes SET effective_end = @effective_start
47
+ WHERE person_id = @person_id AND effective_end IS NULL
48
+ INSERT INTO person_changes (person_id, first_name, last_name, email, effective_start, updated_by)
49
+ VALUES (@person_id, @first_name, @last_name, @email, @effective_start, @updated_by)
50
+ EOSQL
51
+ end
52
+
53
+ def self.down
54
+ drop_trigger :person_change_trig
55
+ end
56
+ end
57
+
58
+ This extension also adds support for triggers in the ActiveRecord::SchemaDumper class.
59
+
60
+ The following drivers are supported:
61
+
62
+ SQL Server
63
+
64
+ == Known Issues
65
+
66
+ * Drivers not mentioned above are not supported.
67
+
68
+ If you find any issues please send an email to stewbawka@gmail.com .
69
+
70
+ == Contributing
71
+
72
+ If you would like to implement trigger support for other adapters then please drop me an email. Better yet, write up the adapter modifications and send them to me. :-)
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "rails_sql_triggers"
5
+ gemspec.summary = "sql triggers in migrations"
6
+ gemspec.description = "Provide create_trigger and drop_trigger in migrations. Also extends SchemaDumper to clue in about triggers"
7
+ gemspec.email = "stewbawka@gmail.com"
8
+ gemspec.homepage = "http://blog.stewbawka.com""
9
+ gemspec.authors = ["Stuart Wade"]
10
+ end
11
+ Jeweler::GemcutterTasks.new
12
+ rescue LoadError
13
+ puts "Jeweler not available. Install it with: sudo gem install jeweler -s http://gemcutter.org"
14
+ end
@@ -0,0 +1,11 @@
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}"
10
+ end
11
+ end
@@ -0,0 +1,16 @@
1
+ module RailsSqlTriggers
2
+ module ConnectionAdapters #:nodoc:
3
+ # Abstract definition of a View
4
+ class TriggerDefinition
5
+ attr_accessor :name, :target, :events, :sql
6
+
7
+ def initialize(base, name, target, events, sql)
8
+ @base = base
9
+ @name = name
10
+ @target = target
11
+ @events = events
12
+ @sql = sql
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,29 @@
1
+ module RailsSqlriggers
2
+ module ConnectionAdapters # :nodoc:
3
+ module SchemaStatements
4
+ # Create a trigger.
5
+ def create_trigger(name, target, events, sql, options={})
6
+ if supports_triggers?
7
+ trigger_definition = TriggerDefinition.new(self, name, target, events, sql)
8
+
9
+ if options[:force]
10
+ drop_trigger(name) rescue nil
11
+ end
12
+
13
+ create_sql = "CREATE TRIGGER "
14
+ create_sql << "#{name} ON #{target} AFTER #{(events*', ').upcase} AS"
15
+ create_sql << trigger_definition.sql
16
+ execute create_sql
17
+ end
18
+ end
19
+
20
+ # Drop a trigger.
21
+ def drop_trigger(name, options={})
22
+ if supports_triggers?
23
+ drop_sql = "DROP TRIGGER #{name}"
24
+ execute drop_sql
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class AbstractAdapter
4
+ # Subclasses should override and return true if they support triggers.
5
+ def supports_triggers?
6
+ return false
7
+ end
8
+
9
+ # Get a list of all triggers for the current database
10
+ def triggers(name = nil)
11
+ raise NotImplementedError, "triggers is an abstract method"
12
+ end
13
+
14
+ # Get the select statement for the specified trigger
15
+ def trigger_select_statement(trigger, name=nil)
16
+ raise NotImplementedError, "trigger_select_statement is an abstract method"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class MysqlAdapter
4
+ # Returns true as this adapter supports views.
5
+ def supports_views?
6
+ true
7
+ end
8
+
9
+ def tables(name = nil) #:nodoc:
10
+ tables = []
11
+ execute("SHOW TABLE STATUS", name).each { |row| tables << row[0] if row[17] != 'VIEW' }
12
+ tables
13
+ end
14
+
15
+ def views(name = nil) #:nodoc:
16
+ views = []
17
+ execute("SHOW TABLE STATUS", name).each { |row| views << row[0] if row[17] == 'VIEW' }
18
+ views
19
+ end
20
+
21
+ # Get the view select statement for the specified table.
22
+ 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
25
+ end
26
+ raise "No view called #{view} found"
27
+ end
28
+
29
+ private
30
+ def convert_statement(s)
31
+ s.gsub!(/.* AS (select .*)/, '\1')
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class OciAdapter
4
+ # Returns true as this adapter supports views.
5
+ def supports_views?
6
+ true
7
+ end
8
+
9
+ def tables(name = nil) #:nodoc:
10
+ tables = []
11
+ execute("SELECT TABLE_NAME FROM USER_TABLES", name).each { |row| tables << row[0] }
12
+ tables
13
+ end
14
+
15
+ def views(name = nil) #:nodoc:
16
+ views = []
17
+ execute("SELECT VIEW_NAME FROM USER_VIEWS", name).each { |row| views << row[0] }
18
+ views
19
+ end
20
+
21
+ # Get the view select statement for the specified table.
22
+ def view_select_statement(view, name=nil)
23
+ row = execute("SELECT TEXT FROM USER_VIEWS WHERE VIEW_NAME = '#{view}'", name).each do |row|
24
+ return row[0]
25
+ end
26
+ raise "No view called #{view} found"
27
+ end
28
+
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class OracleAdapter
4
+ # Returns true as this adapter supports views.
5
+ def supports_views?
6
+ true
7
+ end
8
+
9
+ def tables(name = nil) #:nodoc:
10
+ tables = []
11
+ execute("SELECT TABLE_NAME FROM USER_TABLES", name).each { |row| tables << row[0] }
12
+ tables
13
+ end
14
+
15
+ def views(name = nil) #:nodoc:
16
+ views = []
17
+ execute("SELECT VIEW_NAME FROM USER_VIEWS", name).each { |row| views << row[0] }
18
+ views
19
+ end
20
+
21
+ # Get the view select statement for the specified table.
22
+ def view_select_statement(view, name=nil)
23
+ row = execute("SELECT TEXT FROM USER_VIEWS WHERE VIEW_NAME = '#{view}'", name).each do |row|
24
+ return row[0]
25
+ end
26
+ raise "No view called #{view} found"
27
+ end
28
+
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,39 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PostgreSQLAdapter
4
+ # Returns true as this adapter supports views.
5
+ def supports_views?
6
+ true
7
+ end
8
+
9
+ def views(name = nil) #:nodoc:
10
+ q = <<-SQL
11
+ SELECT table_name, table_type
12
+ FROM information_schema.tables
13
+ WHERE table_schema IN (#{schemas})
14
+ AND table_type = 'VIEW'
15
+ SQL
16
+
17
+ query(q, name).map { |row| row[0] }
18
+ end
19
+
20
+ def view_select_statement(view, name = nil)
21
+ q = <<-SQL
22
+ SELECT view_definition
23
+ FROM information_schema.views
24
+ WHERE table_catalog = (SELECT catalog_name FROM information_schema.information_schema_catalog_name)
25
+ AND table_schema IN (#{schemas})
26
+ AND table_name = '#{view}'
27
+ SQL
28
+
29
+ select_value(q, name) or raise "No view called #{view} found"
30
+ end
31
+
32
+ private
33
+
34
+ def schemas
35
+ schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,48 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class SQLServerAdapter
4
+ # Returns true as this adapter supports views.
5
+ def supports_triggers?
6
+ true
7
+ end
8
+
9
+ # Returns all the trigger names from the currently connected schema.
10
+ def triggers(name = nil)
11
+ select_values("SELECT name FROM sys.triggers", name)
12
+ end
13
+
14
+ def trigger_sp_helptext(trigger, name=nil)
15
+ q =<<-ENDSQL
16
+ EXEC sp_helptext '#{trigger}'
17
+ ENDSQL
18
+
19
+ trigger_def = select_values(q, name).join
20
+
21
+ if !trigger_def.blank?
22
+ return trigger_def
23
+ else
24
+ raise "No trigger called #{trigger} found"
25
+ end
26
+
27
+ end
28
+
29
+ def trigger_target(trigger, name=nil)
30
+ trigger_def = trigger_sp_helptext(trigger, name)
31
+ trigger =~ /^CREATE.* ON (.*) AFTER /, '')
32
+ $1
33
+ end
34
+
35
+ def trigger_events(trigger, name=nil)
36
+ trigger_def = trigger_sp_helptext(trigger, name)
37
+ trigger =~ /^CREATE.* AFTER (.*) AS /, '')
38
+ $1.split(', ').collect {|t| t.downcase.to_sym}
39
+ end
40
+
41
+ def trigger_code(trigger, name=nil)
42
+ trigger_def = trigger_sp_helptext(trigger, name)
43
+ trigger.sub(/^CREATE.* AS /, '')
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,65 @@
1
+ module ActiveRecord
2
+ class SchemaDumper
3
+
4
+ # A list of triggers which should not be dumped to the schema.
5
+ # Acceptable values are strings as well as regexp.
6
+ # This setting is only used if ActiveRecord::Base.schema_format == :ruby
7
+ cattr_accessor :ignore_triggers
8
+ @@ignore_triggers = []
9
+
10
+ def trailer_with_triggers(stream)
11
+ # do nothing...we'll call this later
12
+ end
13
+ alias_method_chain :trailer, :triggers
14
+
15
+ # Add triggers to the end of the dump stream
16
+ def dump_with_triggers(stream)
17
+ dump_without_triggers(stream)
18
+ begin
19
+ if @connection.supports_triggers?
20
+ triggers(stream)
21
+ end
22
+ rescue => e
23
+ ActiveRecord::Base.logger.error "Unable to dump triggers: #{e}"
24
+ end
25
+ trailer_without_triggers(stream)
26
+ stream
27
+ end
28
+ alias_method_chain :dump, :triggers
29
+
30
+ # Add triggers to the stream
31
+ def triggers(stream)
32
+ @connection.triggers.sort.each do |t|
33
+ next if ["schema_info", ignore_triggers].flatten.any? do |ignored|
34
+ case ignored
35
+ when String: t == ignored
36
+ when Regexp: t =~ ignored
37
+ else
38
+ raise StandardError, 'ActiveRecord::SchemaDumper.ignore_views accepts an array of String and / or Regexp values.'
39
+ end
40
+ end
41
+ trigger(t, stream)
42
+ end
43
+ end
44
+
45
+ # Add the specified trigger to the stream
46
+ def trigger(trigger, stream)
47
+ begin
48
+ t = StringIO.new
49
+ t.print " create_trigger #{trigger.to_sym.inspect}"
50
+ t.print ", #{@connection.trigger_target(trigger)}"
51
+ t.print ", #{@connection.trigger_events(trigger).inspect}"
52
+ t.print ", #{@connection.trigger_code(trigger)}"
53
+ t.print ", :force => true"
54
+ t.rewind
55
+ stream.print t.read
56
+ rescue => e
57
+ stream.puts "# Could not dump trigger #{trigger.inspect} because of following #{e.class}"
58
+ stream.puts "# #{e.message}"
59
+ stream.puts
60
+ end
61
+
62
+ stream
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ module RailsSqlTriggers
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,52 @@
1
+ #--
2
+ # Copyright (c) 2006 Anthony Eden
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
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
40
+
41
+ require 'core_ext/module'
42
+
43
+ require 'rails_sql_triggers/connection_adapters/abstract/schema_definitions'
44
+ require 'rails_sql_triggers/connection_adapters/abstract/schema_statements'
45
+ #require 'rails_sql_triggers/connection_adapters/mysql_adapter'
46
+ #require 'rails_sql_triggers/connection_adapters/postgresql_adapter'
47
+ require 'rails_sql_triggers/connection_adapters/sqlserver_adapter'
48
+ require 'rails_sql_triggers/schema_dumper'
49
+
50
+ class ActiveRecord::ConnectionAdapters::AbstractAdapter
51
+ include RailsSqlTriggers::ConnectionAdapters::SchemaStatements
52
+ end
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "rails_sql_triggers"
3
+ s.version = "0.0.1"
4
+ s.author = "Stuart Wade"
5
+ s.email = "stewbawka@gmail.com"
6
+ s.homepage = "http://blog.stewbawka.com"
7
+ s.summary = "sql triggers in migrations"
8
+ s.description = "Provide create_trigger and drop_trigger in migrations. Also extends SchemaDumper to clue in about triggers"
9
+ s.files = Dir["{lib,test}/**/*"] + Dir["[A-Z]*"]
10
+ s.require_path = "lib"
11
+
12
+ s.rubyforge_project = s.name
13
+ s.required_rubygems_version = ">= 1.3.4"
14
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_sql_triggers
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Stuart Wade
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-05-04 00:00:00 -03:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Provide create_trigger and drop_trigger in migrations. Also extends SchemaDumper to clue in about triggers
23
+ email: stewbawka@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files: []
29
+
30
+ files:
31
+ - lib/core_ext/module.rb
32
+ - lib/rails_sql_triggers/connection_adapters/abstract/schema_definitions.rb
33
+ - lib/rails_sql_triggers/connection_adapters/abstract/schema_statements.rb
34
+ - lib/rails_sql_triggers/connection_adapters/abstract_adapter.rb
35
+ - lib/rails_sql_triggers/connection_adapters/mysql_adapter.rb
36
+ - lib/rails_sql_triggers/connection_adapters/oci_adapter.rb
37
+ - lib/rails_sql_triggers/connection_adapters/oracle_adapter.rb
38
+ - lib/rails_sql_triggers/connection_adapters/postgresql_adapter.rb
39
+ - lib/rails_sql_triggers/connection_adapters/sqlserver_adapter.rb
40
+ - lib/rails_sql_triggers/schema_dumper.rb
41
+ - lib/rails_sql_triggers/version.rb
42
+ - lib/rails_sql_triggers.rb
43
+ - CHANGELOG
44
+ - rails_sql_triggers.gemspec
45
+ - Rakefile
46
+ - README
47
+ has_rdoc: true
48
+ homepage: http://blog.stewbawka.com
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options: []
53
+
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 19
71
+ segments:
72
+ - 1
73
+ - 3
74
+ - 4
75
+ version: 1.3.4
76
+ requirements: []
77
+
78
+ rubyforge_project: rails_sql_triggers
79
+ rubygems_version: 1.5.2
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: sql triggers in migrations
83
+ test_files: []
84
+