triggerhappy 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ 0.0.1 - Released May 11, 2011
2
+ * Initial release
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 triggerhappy
10
+
11
+ Then add the following to your Rails config/environment.rb:
12
+
13
+ require_gem 'triggerhappy'
14
+ require 'triggerhappy'
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. :-)
@@ -0,0 +1,14 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "triggerhappy"
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,55 @@
1
+ # Triggerhappy is based the rails_sql_views gem developped by Anthony Eden
2
+ # I have included his original copyright notice since this gem is derivative of his work.
3
+ #
4
+ #--
5
+ # Copyright (c) 2006 Anthony Eden
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #++
26
+
27
+ $:.unshift(File.dirname(__FILE__)) unless
28
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
29
+
30
+ require 'rubygems'
31
+
32
+ #unless Kernel.respond_to?(:gem)
33
+ # Kernel.send :alias_method, :gem, :require_gem
34
+ #end
35
+
36
+ unless defined?(ActiveRecord)
37
+ begin
38
+ $:.unshift(File.dirname(__FILE__) + "/../../activerecord/lib")
39
+ require 'active_record'
40
+ rescue LoadError
41
+ gem 'activerecord'
42
+ end
43
+ end
44
+
45
+ #require 'core_ext/module'
46
+
47
+ require 'triggerhappy/connection_adapters/abstract/schema_definitions'
48
+ require 'triggerhappy/connection_adapters/abstract/schema_statements'
49
+ #require 'triggerhappy/connection_adapters/mysql_adapter'
50
+ require 'triggerhappy/connection_adapters/sqlserver_adapter'
51
+ require 'triggerhappy/schema_dumper'
52
+
53
+ class ActiveRecord::ConnectionAdapters::AbstractAdapter
54
+ include Triggerhappy::ConnectionAdapters::SchemaStatements
55
+ end
@@ -0,0 +1,16 @@
1
+ module Triggerhappy
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 Triggerhappy
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,31 @@
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 code for the specified trigger
15
+ def trigger_code(trigger, name=nil)
16
+ raise NotImplementedError, "trigger_code is an abstract method"
17
+ end
18
+
19
+ # Get the target for the specified trigger
20
+ def trigger_target(trigger, name=nil)
21
+ raise NotImplementedError, "trigger_target is an abstract method"
22
+ end
23
+
24
+ # Get the events for the specified trigger
25
+ def trigger_events(trigger, name=nil)
26
+ raise NotImplementedError, "trigger_events is an abstract method"
27
+ end
28
+
29
+ end
30
+ end
31
+ 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,49 @@
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
+ SELECT OBJECT_DEFINITION(OBJECT_ID('#{trigger}'));
17
+ ENDSQL
18
+
19
+ trigger_def = select_value(q, name)
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_def =~ /^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_def =~ /^CREATE.* AFTER (.*) AS /
38
+
39
+ $1.split(', ').collect {|t| t.downcase.to_sym}
40
+ end
41
+
42
+ def trigger_code(trigger, name=nil)
43
+ trigger_def = trigger_sp_helptext(trigger, name)
44
+ trigger_def.sub(/^CREATE.* AS /, '')
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,68 @@
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 " trigger_code = <<EOSQL\n"
50
+ t.print @connection.trigger_code(trigger)
51
+ t.print "EOSQL\n"
52
+ t.print " create_trigger(#{trigger.to_sym.inspect}, "
53
+ t.print "#{@connection.trigger_target(trigger).to_sym.inspect}, "
54
+ t.print "#{@connection.trigger_events(trigger).inspect}, "
55
+ t.print "trigger_code, "
56
+ t.print ":force => true)\n"
57
+ t.rewind
58
+ stream.print t.read
59
+ rescue => e
60
+ stream.puts "# Could not dump trigger #{trigger.inspect} because of following #{e.class}"
61
+ stream.puts "# #{e.message}"
62
+ stream.puts "# #{e.trace}"
63
+ end
64
+
65
+ stream
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,9 @@
1
+ module TriggerHappy
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
Binary file
Binary file
@@ -0,0 +1,14 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "triggerhappy"
3
+ s.version = "0.0.6"
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,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: triggerhappy
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 6
10
+ version: 0.0.6
11
+ platform: ruby
12
+ authors:
13
+ - Stuart Wade
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-05-13 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/triggerhappy/connection_adapters/abstract/schema_definitions.rb
32
+ - lib/triggerhappy/connection_adapters/abstract/schema_statements.rb
33
+ - lib/triggerhappy/connection_adapters/abstract_adapter.rb
34
+ - lib/triggerhappy/connection_adapters/mysql_adapter.rb
35
+ - lib/triggerhappy/connection_adapters/sqlserver_adapter.rb
36
+ - lib/triggerhappy/schema_dumper.rb
37
+ - lib/triggerhappy/version.rb
38
+ - lib/triggerhappy.rb
39
+ - CHANGELOG
40
+ - Rakefile
41
+ - README
42
+ - triggerhappy-0.0.4.gem
43
+ - triggerhappy-0.0.5.gem
44
+ - triggerhappy.gemspec
45
+ has_rdoc: true
46
+ homepage: http://blog.stewbawka.com
47
+ licenses: []
48
+
49
+ post_install_message:
50
+ rdoc_options: []
51
+
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ hash: 19
69
+ segments:
70
+ - 1
71
+ - 3
72
+ - 4
73
+ version: 1.3.4
74
+ requirements: []
75
+
76
+ rubyforge_project: triggerhappy
77
+ rubygems_version: 1.5.2
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: sql triggers in migrations
81
+ test_files: []
82
+