triggerhappy 0.0.6

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.
@@ -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
+