rails_sql_triggers 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/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
+