hairtrigger 0.1.14 → 0.2.0

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.
@@ -1,4 +1,5 @@
1
1
  = HairTrigger
2
+ {<img src="https://secure.travis-ci.org/jenseng/hair_trigger.png?branch=master" />}[http://travis-ci.org/jenseng/hair_trigger]
2
3
 
3
4
  HairTrigger lets you create and manage database triggers in a concise,
4
5
  db-agnostic, Rails-y way. You declare triggers right in your models in Ruby,
@@ -6,14 +7,18 @@ and a simple rake task does all the dirty work for you.
6
7
 
7
8
  == Installation
8
9
 
9
- === Step 1.
10
+ === Rails 3
10
11
 
11
- If you are using bundler, just put hairtrigger in your Gemfile.
12
+ If you are using Rails 3, just put hairtrigger in your Gemfile.
12
13
 
13
- If you're not using bundler, you can "gem install hairtrigger" and then put
14
- hairtrigger in environment.rb
14
+ === Rails 2
15
15
 
16
- === Step 2.
16
+ ==== Step 1.
17
+
18
+ Put hairtrigger in your Gemfile, or if you're not using bundler, you can
19
+ "gem install hairtrigger" and then put hairtrigger in environment.rb
20
+
21
+ ==== Step 2.
17
22
 
18
23
  Create lib/tasks/hair_trigger.rake with the following:
19
24
 
@@ -60,38 +65,6 @@ removed or updated. It does this by diffing the current model trigger
60
65
  declarations and any auto-generated triggers in schema.rb (and subsequent
61
66
  migrations).
62
67
 
63
- === Manual Migrations
64
-
65
- You can also manage triggers manually in your migrations via create_trigger and
66
- drop_trigger. They are a little more verbose than model triggers, and they can
67
- be more work since you need to figure out the up/down create/drop logic when
68
- you change things. A sample trigger:
69
-
70
- create_trigger(:compatibility => 1).on(:users).after(:insert) do
71
- "UPDATE accounts SET user_count = user_count + 1 WHERE id = NEW.account_id;"
72
- end
73
-
74
- ==== Manual triggers and :compatibility
75
-
76
- As bugs are fixed and features are implemented in hairtrigger, it's possible
77
- that the generated trigger SQL will change (this has only happened once so
78
- far). If you upgrade to a newer version of hairtrigger, it needs a way of
79
- knowing which previous version generated the original trigger. You only need
80
- to worry about this for manual trigger migrations, as the model ones
81
- automatically take care of this. For your manual triggers you can either:
82
-
83
- * pass ":compatibility => x" to your create_trigger statement, where x is
84
- whatever HairTrigger::Builder.compatiblity is (1 for this version).
85
- * set "HairTrigger::Builder.base_compatibility = x" in an initializer, where
86
- x is whatever HairTrigger::Builder.compatiblity is. This is like doing the
87
- first option on every create_trigger. Note that once the compatibility
88
- changes, you'll need to set :compatibility on new triggers (unless you
89
- just redo all your triggers and bump the base_compatibility).
90
-
91
- If you upgrade to a newer version of hairtrigger and see that the SQL
92
- compatibility has changed, you'll need to set the appropriate compatibility
93
- on any new triggers that you create.
94
-
95
68
  === Chainable Methods
96
69
 
97
70
  Triggers are built by chaining several methods together, ending in a block
@@ -139,10 +112,10 @@ and timing. For example:
139
112
  t.all do # every row
140
113
  # some sql
141
114
  end
142
- t.where("OLD.foo <> NEW.foo") do
115
+ t.where("OLD.foo != NEW.foo") do
143
116
  # some more sql
144
117
  end
145
- t.where("OLD.bar <> NEW.bar") do
118
+ t.where("OLD.bar != NEW.bar") do
146
119
  # some other sql
147
120
  end
148
121
  end
@@ -175,6 +148,38 @@ you might do something like the following:
175
148
  MYSQL
176
149
  end
177
150
 
151
+ === Manual Migrations
152
+
153
+ You can also manage triggers manually in your migrations via create_trigger and
154
+ drop_trigger. They are a little more verbose than model triggers, and they can
155
+ be more work since you need to figure out the up/down create/drop logic when
156
+ you change things. A sample trigger:
157
+
158
+ create_trigger(:compatibility => 1).on(:users).after(:insert) do
159
+ "UPDATE accounts SET user_count = user_count + 1 WHERE id = NEW.account_id;"
160
+ end
161
+
162
+ ==== Manual triggers and :compatibility
163
+
164
+ As bugs are fixed and features are implemented in hairtrigger, it's possible
165
+ that the generated trigger SQL will change (this has only happened once so
166
+ far). If you upgrade to a newer version of hairtrigger, it needs a way of
167
+ knowing which previous version generated the original trigger. You only need
168
+ to worry about this for manual trigger migrations, as the model ones
169
+ automatically take care of this. For your manual triggers you can either:
170
+
171
+ * pass ":compatibility => x" to your create_trigger statement, where x is
172
+ whatever HairTrigger::Builder.compatiblity is (1 for this version).
173
+ * set "HairTrigger::Builder.base_compatibility = x" in an initializer, where
174
+ x is whatever HairTrigger::Builder.compatiblity is. This is like doing the
175
+ first option on every create_trigger. Note that once the compatibility
176
+ changes, you'll need to set :compatibility on new triggers (unless you
177
+ just redo all your triggers and bump the base_compatibility).
178
+
179
+ If you upgrade to a newer version of hairtrigger and see that the SQL
180
+ compatibility has changed, you'll need to set the appropriate compatibility
181
+ on any new triggers that you create.
182
+
178
183
  == rake db:schema:dump
179
184
 
180
185
  HairTrigger hooks into rake db:schema:dump (and rake tasks that call it) to
@@ -251,13 +256,14 @@ you want to support.
251
256
 
252
257
  == Compatibility
253
258
 
254
- * Rails 2.3 - Rails 3.0.x
259
+ * Rails 2.3+
255
260
  * Postgres 8.0+
256
261
  * MySQL 5.0.10+
257
262
  * SQLite 3.3.8+
258
263
 
259
264
  == Version History
260
265
 
266
+ * 0.2.0 Rails 3.1+ support, easier installation, ruby 1.9 fix, travis-ci
261
267
  * 0.1.14 sqlite + ruby1.9 bugfix
262
268
  * 0.1.13 drop_trigger fix
263
269
  * 0.1.12 DB-specific trigger body support, bugfixes
@@ -275,4 +281,4 @@ you want to support.
275
281
 
276
282
  == Copyright
277
283
 
278
- Copyright (c) 2011 Jon Jensen. See LICENSE.txt for further details.
284
+ Copyright (c) 2012 Jon Jensen. See LICENSE.txt for further details.
data/Rakefile CHANGED
@@ -1,29 +1,5 @@
1
- require 'rubygems'
2
- require 'bundler'
3
- begin
4
- Bundler.setup(:default, :development)
5
- rescue Bundler::BundlerError => e
6
- $stderr.puts e.message
7
- $stderr.puts "Run `bundle install` to install missing gems"
8
- exit e.status_code
9
- end
10
1
  require 'rake'
11
2
 
12
- require 'jeweler'
13
- Jeweler::Tasks.new do |gem|
14
- # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
- gem.name = "hairtrigger"
16
- gem.homepage = "http://github.com/jenseng/hair_trigger"
17
- gem.license = "MIT"
18
- gem.summary = %Q{easy database triggers for active record}
19
- gem.description = %Q{allows you to declare database triggers in ruby in your models, and then generate appropriate migrations as they change}
20
- gem.email = "jenseng@gmail.com"
21
- gem.authors = ["Jon Jensen"]
22
- gem.add_dependency "activerecord", ">=2.3.0"
23
- gem.add_development_dependency "rspec", "~> 2.3.0"
24
- end
25
- Jeweler::RubygemsDotOrgTasks.new
26
-
27
3
  require 'rspec/core'
28
4
  require 'rspec/core/rake_task'
29
5
  RSpec::Core::RakeTask.new(:spec) do |spec|
@@ -36,13 +12,3 @@ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
12
  end
37
13
 
38
14
  task :default => :spec
39
-
40
- require 'rake/rdoctask'
41
- Rake::RDocTask.new do |rdoc|
42
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
43
-
44
- rdoc.rdoc_dir = 'rdoc'
45
- rdoc.title = "hairtrigger #{version}"
46
- rdoc.rdoc_files.include('README*')
47
- rdoc.rdoc_files.include('lib/**/*.rb')
48
- end
@@ -5,154 +5,175 @@ require 'hair_trigger/migration_reader'
5
5
  require 'hair_trigger/migrator'
6
6
  require 'hair_trigger/adapter'
7
7
  require 'hair_trigger/schema_dumper'
8
+ require 'hair_trigger/railtie' if defined?(Rails::Railtie)
8
9
 
9
10
  module HairTrigger
10
- def self.current_triggers
11
- # see what the models say there should be
12
- canonical_triggers = []
13
- Dir[model_path + '/*rb'].each do |model|
14
- class_name = model.sub(/\A.*\/(.*?)\.rb\z/, '\1').camelize
15
- next unless File.read(model) =~ /^\s*trigger[\.\(]/
16
- begin
17
- require model unless klass = Kernel.const_get(class_name) rescue nil
18
- klass = Kernel.const_get(class_name)
19
- rescue StandardError, LoadError
20
- raise "unable to load #{class_name} and its trigger(s)" if File.read(model) =~ /^\s*trigger[\.\(]/
21
- end
22
- canonical_triggers += klass.triggers if klass < ActiveRecord::Base && klass.triggers
23
- end
24
- canonical_triggers.each(&:prepare!) # interpolates any vars so we match the migrations
25
- end
11
+ class << self
12
+ attr_writer :model_path, :schema_rb_path, :migration_path
26
13
 
27
- def self.current_migrations(options = {})
28
- if options[:in_rake_task]
29
- options[:include_manual_triggers] = true
30
- options[:schema_rb_first] = true
31
- options[:skip_pending_migrations] = true
14
+ def current_triggers
15
+ # see what the models say there should be
16
+ canonical_triggers = models.map(&:triggers).flatten.compact
17
+ canonical_triggers.each(&:prepare!) # interpolates any vars so we match the migrations
32
18
  end
33
19
 
34
- # if we're in a db:schema:dump task (explict or kicked off by db:migrate),
35
- # we evaluate the previous schema.rb (if it exists), and then all applied
36
- # migrations in order (even ones older than schema.rb). this ensures we
37
- # handle db:migrate:down scenarios correctly
38
- #
39
- # if we're not in such a rake task (i.e. we just want to know what
40
- # triggers are defined, whether or not they are applied in the db), we
41
- # evaluate all migrations along with schema.rb, ordered by version
42
- migrator = ActiveRecord::Migrator.new(:up, migration_path)
43
- migrated = migrator.migrated rescue []
44
- migrations = []
45
- migrator.migrations.each do |migration|
46
- next if options[:skip_pending_migrations] && !migrated.include?(migration.version)
47
- triggers = MigrationReader.get_triggers(migration, options)
48
- migrations << [migration, triggers] unless triggers.empty?
20
+ def models
21
+ if defined?(Rails) && Rails::VERSION::MAJOR > 2
22
+ Rails.application.eager_load!
23
+ else
24
+ Dir[model_path + '/*rb'].each do |model|
25
+ class_name = model.sub(/\A.*\/(.*?)\.rb\z/, '\1').camelize
26
+ next unless File.read(model) =~ /^\s*trigger[\.\(]/
27
+ begin
28
+ require "./#{model}" unless Object.const_defined?(class_name)
29
+ rescue StandardError, LoadError
30
+ raise "unable to load #{class_name} and its trigger(s)"
31
+ end
32
+ end
33
+ end
34
+ ActiveRecord::VERSION::STRING < "3.0." ?
35
+ ActiveRecord::Base.send(:subclasses) :
36
+ ActiveRecord::Base.descendants
49
37
  end
50
38
 
51
- if previous_schema = (options.has_key?(:previous_schema) ? options[:previous_schema] : File.exist?(schema_rb_path) && File.read(schema_rb_path))
52
- base_triggers = MigrationReader.get_triggers(previous_schema, options)
53
- unless base_triggers.empty?
54
- version = (previous_schema =~ /ActiveRecord::Schema\.define\(:version => (\d+)\)/) && $1.to_i
55
- migrations.unshift [OpenStruct.new({:version => version}), base_triggers]
39
+ def current_migrations(options = {})
40
+ if options[:in_rake_task]
41
+ options[:include_manual_triggers] = true
42
+ options[:schema_rb_first] = true
43
+ options[:skip_pending_migrations] = true
56
44
  end
57
- end
58
-
59
- migrations = migrations.sort_by{|(migration, triggers)| migration.version} unless options[:schema_rb_first]
60
-
61
- all_builders = []
62
- migrations.each do |(migration, triggers)|
63
- triggers.each do |new_trigger|
64
- # if there is already a trigger with this name, delete it since we are
65
- # either dropping it or replacing it
66
- new_trigger.prepare!
67
- all_builders.delete_if{ |(n, t)| t.prepared_name == new_trigger.prepared_name }
68
- all_builders << [migration.name, new_trigger] unless new_trigger.options[:drop]
45
+
46
+ # if we're in a db:schema:dump task (explict or kicked off by db:migrate),
47
+ # we evaluate the previous schema.rb (if it exists), and then all applied
48
+ # migrations in order (even ones older than schema.rb). this ensures we
49
+ # handle db:migrate:down scenarios correctly
50
+ #
51
+ # if we're not in such a rake task (i.e. we just want to know what
52
+ # triggers are defined, whether or not they are applied in the db), we
53
+ # evaluate all migrations along with schema.rb, ordered by version
54
+ migrator = ActiveRecord::Migrator.new(:up, migration_path)
55
+ migrated = migrator.migrated rescue []
56
+ migrations = []
57
+ migrator.migrations.each do |migration|
58
+ next if options[:skip_pending_migrations] && !migrated.include?(migration.version)
59
+ triggers = MigrationReader.get_triggers(migration, options)
60
+ migrations << [migration, triggers] unless triggers.empty?
61
+ end
62
+
63
+ if previous_schema = (options.has_key?(:previous_schema) ? options[:previous_schema] : File.exist?(schema_rb_path) && File.read(schema_rb_path))
64
+ base_triggers = MigrationReader.get_triggers(previous_schema, options)
65
+ unless base_triggers.empty?
66
+ version = (previous_schema =~ /ActiveRecord::Schema\.define\(:version => (\d+)\)/) && $1.to_i
67
+ migrations.unshift [OpenStruct.new({:version => version}), base_triggers]
68
+ end
69
69
  end
70
+
71
+ migrations = migrations.sort_by{|(migration, triggers)| migration.version} unless options[:schema_rb_first]
72
+
73
+ all_builders = []
74
+ migrations.each do |(migration, triggers)|
75
+ triggers.each do |new_trigger|
76
+ # if there is already a trigger with this name, delete it since we are
77
+ # either dropping it or replacing it
78
+ new_trigger.prepare!
79
+ all_builders.delete_if{ |(n, t)| t.prepared_name == new_trigger.prepared_name }
80
+ all_builders << [migration.name, new_trigger] unless new_trigger.options[:drop]
81
+ end
82
+ end
83
+
84
+ all_builders
70
85
  end
71
86
 
72
- all_builders
73
- end
74
-
75
- def self.migrations_current?
76
- current_migrations.map(&:last).sort.eql? current_triggers.sort
77
- end
78
-
79
- def self.generate_migration(silent = false)
80
- begin
81
- canonical_triggers = current_triggers
82
- rescue
83
- $stderr.puts $!
84
- exit 1
87
+ def migrations_current?
88
+ current_migrations.map(&:last).sort.eql? current_triggers.sort
85
89
  end
86
90
 
87
- migrations = current_migrations
88
- migration_names = migrations.map(&:first)
89
- existing_triggers = migrations.map(&:last)
90
-
91
- up_drop_triggers = []
92
- up_create_triggers = []
93
- down_drop_triggers = []
94
- down_create_triggers = []
95
-
96
- existing_triggers.each do |existing|
97
- unless canonical_triggers.any?{ |t| t.prepared_name == existing.prepared_name }
98
- up_drop_triggers += existing.drop_triggers
99
- down_create_triggers << existing
91
+ def generate_migration(silent = false)
92
+ begin
93
+ canonical_triggers = current_triggers
94
+ rescue
95
+ $stderr.puts $!
96
+ exit 1
100
97
  end
101
- end
102
-
103
- (canonical_triggers - existing_triggers).each do |new_trigger|
104
- up_create_triggers << new_trigger
105
- down_drop_triggers += new_trigger.drop_triggers
106
- if existing = existing_triggers.detect{ |t| t.prepared_name == new_trigger.prepared_name }
107
- # it's not sufficient to rely on the new trigger to replace the old
108
- # one, since we could be dealing with trigger groups and the name
109
- # alone isn't sufficient to know which component triggers to remove
110
- up_drop_triggers += existing.drop_triggers
98
+
99
+ migrations = current_migrations
100
+ migration_names = migrations.map(&:first)
101
+ existing_triggers = migrations.map(&:last)
102
+
103
+ up_drop_triggers = []
104
+ up_create_triggers = []
105
+ down_drop_triggers = []
106
+ down_create_triggers = []
107
+
108
+ # see which triggers need to be dropped
109
+ existing_triggers.each do |existing|
110
+ next if canonical_triggers.any?{ |t| t.prepared_name == existing.prepared_name }
111
+ up_drop_triggers.concat existing.drop_triggers
111
112
  down_create_triggers << existing
112
113
  end
113
- end
114
-
115
- unless up_drop_triggers.empty? && up_create_triggers.empty?
116
- migration_base_name = if up_create_triggers.size > 0
117
- ("create trigger#{up_create_triggers.size > 1 ? 's' : ''} " +
118
- up_create_triggers.map{ |t| [t.options[:table], t.options[:events].join(" ")].join(" ") }.join(" and ")
119
- ).downcase.gsub(/[^a-z0-9_]/, '_').gsub(/_+/, '_').camelize
120
- else
121
- ("drop trigger#{up_drop_triggers.size > 1 ? 's' : ''} " +
122
- up_drop_triggers.map{ |t| t.options[:table] }.join(" and ")
123
- ).downcase.gsub(/[^a-z0-9_]/, '_').gsub(/_+/, '_').camelize
114
+
115
+ # see which triggers need to be added/replaced
116
+ (canonical_triggers - existing_triggers).each do |new_trigger|
117
+ up_create_triggers << new_trigger
118
+ down_drop_triggers.concat new_trigger.drop_triggers
119
+ if existing = existing_triggers.detect{ |t| t.prepared_name == new_trigger.prepared_name }
120
+ # it's not sufficient to rely on the new trigger to replace the old
121
+ # one, since we could be dealing with trigger groups and the name
122
+ # alone isn't sufficient to know which component triggers to remove
123
+ up_drop_triggers.concat existing.drop_triggers
124
+ down_create_triggers << existing
125
+ end
124
126
  end
127
+
128
+ return if up_drop_triggers.empty? && up_create_triggers.empty?
125
129
 
126
- name_version = nil
127
- while migration_names.include?("#{migration_base_name}#{name_version}")
128
- name_version = name_version.to_i + 1
129
- end
130
- migration_name = "#{migration_base_name}#{name_version}"
131
- migration_version = ActiveRecord::Base.timestamped_migrations ?
132
- Time.now.getutc.strftime("%Y%m%d%H%M%S") :
133
- Dir.glob(migration_path + '/*rb').map{ |f| f.gsub(/.*\/(\d+)_.*/, '\1').to_i}.inject(0){ |curr, i| i > curr ? i : curr } + 1
130
+ migration_name = infer_migration_name(migration_names, up_create_triggers, up_drop_triggers)
131
+ migration_version = infer_migration_version(migration_name)
134
132
  file_name = migration_path + '/' + migration_version + "_" + migration_name.underscore + ".rb"
133
+ prefix = ActiveRecord::VERSION::STRING < "3.1." ? "self." : ""
135
134
  File.open(file_name, "w"){ |f| f.write <<-MIGRATION }
136
135
  # This migration was auto-generated via `rake db:generate_trigger_migration'.
137
136
  # While you can edit this file, any changes you make to the definitions here
138
137
  # will be undone by the next auto-generated trigger migration.
139
138
 
140
139
  class #{migration_name} < ActiveRecord::Migration
141
- def self.up
140
+ def #{prefix}up
142
141
  #{(up_drop_triggers + up_create_triggers).map{ |t| t.to_ruby(' ') }.join("\n\n").lstrip}
143
142
  end
144
143
 
145
- def self.down
144
+ def #{prefix}down
146
145
  #{(down_drop_triggers + down_create_triggers).map{ |t| t.to_ruby(' ') }.join("\n\n").lstrip}
147
146
  end
148
147
  end
149
148
  MIGRATION
150
149
  file_name
151
150
  end
152
- end
153
151
 
154
- class << self
155
- attr_writer :model_path, :schema_rb_path, :migration_path
152
+ def infer_migration_name(migration_names, create_triggers, drop_triggers)
153
+ migration_base_name = if create_triggers.size > 0
154
+ ("create trigger#{create_triggers.size > 1 ? 's' : ''} " +
155
+ create_triggers.map{ |t| [t.options[:table], t.options[:events].join(" ")].join(" ") }.join(" and ")
156
+ ).downcase.gsub(/[^a-z0-9_]/, '_').gsub(/_+/, '_').camelize
157
+ else
158
+ ("drop trigger#{drop_triggers.size > 1 ? 's' : ''} " +
159
+ drop_triggers.map{ |t| t.options[:table] }.join(" and ")
160
+ ).downcase.gsub(/[^a-z0-9_]/, '_').gsub(/_+/, '_').camelize
161
+ end
162
+
163
+ name_version = nil
164
+ while migration_names.include?("#{migration_base_name}#{name_version}")
165
+ name_version = name_version.to_i + 1
166
+ end
167
+ migration_name = "#{migration_base_name}#{name_version}"
168
+ end
169
+
170
+ def infer_migration_version(migration_name)
171
+ ActiveRecord::Base.timestamped_migrations ?
172
+ Time.now.getutc.strftime("%Y%m%d%H%M%S") :
173
+ Dir.glob(migration_path + '/*rb').
174
+ map{ |f| f.gsub(/.*\/(\d+)_.*/, '\1').to_i}.
175
+ inject(0){ |curr, i| i > curr ? i : curr } + 1
176
+ end
156
177
 
157
178
  def model_path
158
179
  @model_path ||= 'app/models'
@@ -1,3 +1,5 @@
1
+ require 'hair_trigger/version'
2
+
1
3
  module HairTrigger
2
4
  class Builder
3
5
  class DeclarationError < StandardError; end
@@ -425,18 +427,7 @@ BEGIN
425
427
 
426
428
  def compatibility
427
429
  @compatibility ||= begin
428
- gem_version = (File.read(File.dirname(__FILE__) + '/../../VERSION').chomp rescue '0.1.3').split(/\./).map(&:to_i)
429
- gem_version.instance_eval(<<-METHODS)
430
- def <=>(other)
431
- [size, other.size].max.times do |i|
432
- c = self[i].to_i <=> other[i].to_i
433
- return c unless c == 0
434
- end
435
- 0
436
- end
437
- extend Comparable
438
- METHODS
439
- if gem_version <= [0, 1, 3]
430
+ if HairTrigger::VERSION <= "0.1.3"
440
431
  0 # initial releases
441
432
  else
442
433
  1 # postgres RETURN bugfix
@@ -22,7 +22,7 @@ module HairTrigger
22
22
  sexps = sexps.detect{ |s| s.is_a?(Sexp) && s[0] == :class && s[1] == source.name.to_sym }.last
23
23
  # find the block of the up method
24
24
  sexps = sexps.last if sexps.last.is_a?(Sexp) && sexps.last[0] == :block
25
- sexps = sexps.detect{ |s| s.is_a?(Sexp) && s[0] == :defs && s[1] && s[1][0] == :self && s[2] == :up }.last.last
25
+ sexps = sexps.detect{ |s| s.is_a?(Sexp) && (s[0] == :defs && s[1] && s[1][0] == :self && s[2] == :up || s[0] == :defn && s[1] == :up) }.last.last
26
26
  sexps.each do |sexp|
27
27
  next unless (method = extract_method_call(sexp)) && [:create_trigger, :drop_trigger].include?(method)
28
28
  trigger = instance_eval("generate_" + generator.process(sexp))
@@ -0,0 +1,11 @@
1
+ require 'hair_trigger'
2
+ require 'rails'
3
+ module HairTrigger
4
+ class Railtie < Rails::Railtie
5
+ railtie_name :hair_trigger
6
+
7
+ rake_tasks do
8
+ load "tasks/hair_trigger.rake"
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module HairTrigger
2
+ VERSION = "0.2.0"
3
+
4
+ def VERSION.<=>(other)
5
+ split(/\./).map(&:to_i) <=> other.split(/\./).map(&:to_i)
6
+ end
7
+ end
File without changes
@@ -12,7 +12,7 @@ namespace :db do
12
12
  desc "Create a db/schema.rb file that can be portably used against any DB supported by AR"
13
13
  task :dump => :environment do
14
14
  require 'active_record/schema_dumper'
15
- filename = ENV['SCHEMA'] || "#{RAILS_ROOT}/db/schema.rb"
15
+ filename = ENV['SCHEMA'] || "#{Rails.root}/db/schema.rb"
16
16
  ActiveRecord::SchemaDumper.previous_schema = File.exist?(filename) ? File.read(filename) : nil
17
17
  File.open(filename, "w") do |file|
18
18
  ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
@@ -0,0 +1,18 @@
1
+ class InitialTables < ActiveRecord::Migration
2
+ def self.up
3
+ create_table "users" do |t|
4
+ t.integer "group_id"
5
+ t.string "name"
6
+ end
7
+
8
+ create_table "groups" do |t|
9
+ t.integer "bob_count", :default => 0
10
+ t.integer "updated_joe_count", :default => 0
11
+ end
12
+ end
13
+
14
+ def self.down
15
+ drop_table "users"
16
+ drop_table "groups"
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # This migration was auto-generated via `rake db:generate_trigger_migration'.
2
+ # While you can edit this file, any changes you make to the definitions here
3
+ # will be undone by the next auto-generated trigger migration.
4
+
5
+ class UserTrigger < ActiveRecord::Migration
6
+ def self.up
7
+ create_trigger("users_after_insert_row_when_new_name_bob__tr", :generated => true, :compatibility => 1).
8
+ on("users").
9
+ after(:insert).
10
+ where("NEW.name = 'bob'") do
11
+ "UPDATE groups SET bob_count = bob_count + 1"
12
+ end
13
+ end
14
+
15
+ def self.down
16
+ drop_trigger("users_after_insert_row_when_new_name_bob__tr", "users")
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ class ManualUserTrigger < ActiveRecord::Migration
2
+ def self.up
3
+ create_trigger(:compatibility => 1).
4
+ on("users").
5
+ after(:update).
6
+ where("NEW.name = 'joe'") do
7
+ "UPDATE groups SET updated_joe_count = updated_joe_count + 1"
8
+ end
9
+ end
10
+ end
@@ -1,5 +1,5 @@
1
1
  class InitialTables < ActiveRecord::Migration
2
- def self.up
2
+ def up
3
3
  create_table "users" do |t|
4
4
  t.integer "group_id"
5
5
  t.string "name"
@@ -11,7 +11,7 @@ class InitialTables < ActiveRecord::Migration
11
11
  end
12
12
  end
13
13
 
14
- def self.down
14
+ def down
15
15
  drop_table "users"
16
16
  drop_table "groups"
17
17
  end
@@ -3,7 +3,7 @@
3
3
  # will be undone by the next auto-generated trigger migration.
4
4
 
5
5
  class UserTrigger < ActiveRecord::Migration
6
- def self.up
6
+ def up
7
7
  create_trigger("users_after_insert_row_when_new_name_bob__tr", :generated => true, :compatibility => 1).
8
8
  on("users").
9
9
  after(:insert).
@@ -12,7 +12,7 @@ class UserTrigger < ActiveRecord::Migration
12
12
  end
13
13
  end
14
14
 
15
- def self.down
15
+ def down
16
16
  drop_trigger("users_after_insert_row_when_new_name_bob__tr", "users")
17
17
  end
18
18
  end
@@ -1,5 +1,5 @@
1
1
  class ManualUserTrigger < ActiveRecord::Migration
2
- def self.up
2
+ def up
3
3
  create_trigger(:compatibility => 1).
4
4
  on("users").
5
5
  after(:update).
@@ -6,47 +6,48 @@ require 'active_record/connection_adapters/sqlite3_adapter'
6
6
  require 'mysql2'
7
7
  require 'rspec'
8
8
  require 'hair_trigger'
9
+ require 'yaml'
9
10
 
10
11
  # for this spec to work, you need to have postgres and mysql installed (in
11
12
  # addition to the gems), and you should make sure that you have set up
12
- # hairtrigger_schema_test dbs in mysql and postgres, along with a hairtrigger
13
- # user (no password) having appropriate privileges (user needs to be able to
14
- # drop/recreate this db)
13
+ # appropriate users and permissions. see database.yml for more info
15
14
 
16
- def reset_tmp
17
- HairTrigger.model_path = 'tmp/models'
18
- HairTrigger.migration_path = 'tmp/migrations'
19
- FileUtils.rm_rf('tmp') if File.directory?('tmp')
20
- FileUtils.mkdir_p(HairTrigger.model_path)
21
- FileUtils.mkdir_p(HairTrigger.migration_path)
22
- FileUtils.cp_r('spec/models', 'tmp')
23
- FileUtils.cp_r('spec/migrations', 'tmp')
24
- end
15
+ describe "schema" do
16
+ def reset_tmp
17
+ HairTrigger.model_path = 'tmp/models'
18
+ HairTrigger.migration_path = 'tmp/migrations'
19
+ FileUtils.rm_rf('tmp') if File.directory?('tmp')
20
+ FileUtils.mkdir_p(HairTrigger.model_path)
21
+ FileUtils.mkdir_p(HairTrigger.migration_path)
22
+ FileUtils.cp_r('spec/models', 'tmp')
23
+ FileUtils.cp_r(Dir.glob("spec/migrations#{ActiveRecord::VERSION::STRING < "3.1." ? "-pre-3.1" : ""}/*"), HairTrigger.migration_path)
24
+ end
25
+
26
+ def initialize_db(adapter)
27
+ reset_tmp
28
+ config = @configs[adapter.to_s].merge({:adapter => adapter.to_s})
29
+ case adapter
30
+ when :mysql, :mysql2
31
+ ret = `echo "drop database if exists #{config['database']}; create database #{config['database']};" | mysql -u #{config['username']}`
32
+ raise "error creating database: #{ret}" unless $?.exitstatus == 0
33
+ when :postgresql
34
+ `dropdb -U #{config['username']} #{config['database']} &>/dev/null`
35
+ ret = `createdb -U #{config['username']} #{config['database']} 2>&1`
36
+ raise "error creating database: #{ret}" unless $?.exitstatus == 0
37
+ end
38
+ # Arel has an issue in that it keeps using original connection for quoting,
39
+ # etc. (which breaks stuff) unless you do this:
40
+ Arel::Visitors::ENGINE_VISITORS.delete(ActiveRecord::Base) if defined?(Arel)
41
+ ActiveRecord::Base.establish_connection(config)
42
+ ActiveRecord::Base.logger = Logger.new('/dev/null')
43
+ ActiveRecord::Migrator.migrate(HairTrigger.migration_path)
44
+ end
25
45
 
26
- def initialize_db(adapter)
27
- reset_tmp
28
- config = {:database => 'hairtrigger_schema_test', :username => 'hairtrigger', :adapter => adapter.to_s, :host => 'localhost'}
29
- case adapter
30
- when :mysql, :mysql2
31
- ret = `echo "drop database if exists hairtrigger_schema_test; create database hairtrigger_schema_test;" | mysql hairtrigger_schema_test -u hairtrigger`
32
- raise "error creating database: #{ret}" unless $?.exitstatus == 0
33
- when :sqlite3
34
- config[:database] = 'tmp/hairtrigger_schema_test.sqlite3'
35
- when :postgresql
36
- `dropdb -U hairtrigger hairtrigger_schema_test &>/dev/null`
37
- ret = `createdb -U hairtrigger hairtrigger_schema_test 2>&1`
38
- raise "error creating database: #{ret}" unless $?.exitstatus == 0
39
- config[:min_messages] = :error
46
+ before :all do
47
+ @configs = YAML.load_file(File.expand_path(File.dirname(__FILE__) + '/../database.yml'))
48
+ @configs = @configs[ENV["DB_CONFIG"] || "test"]
40
49
  end
41
- # Arel has an issue in that it keeps using original connection for quoting,
42
- # etc. (which breaks stuff) unless you do this:
43
- Arel::Visitors::ENGINE_VISITORS.delete(ActiveRecord::Base) if defined?(Arel)
44
- ActiveRecord::Base.establish_connection(config)
45
- ActiveRecord::Base.logger = Logger.new('/dev/null')
46
- ActiveRecord::Migrator.migrate(HairTrigger.migration_path)
47
- end
48
50
 
49
- describe "schema" do
50
51
  [:mysql, :mysql2, :postgresql, :sqlite3].each do |adapter|
51
52
  it "should correctly dump #{adapter}" do
52
53
  ActiveRecord::Migration.verbose = false
metadata CHANGED
@@ -1,319 +1,216 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: hairtrigger
3
- version: !ruby/object:Gem::Version
4
- hash: 7
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 1
9
- - 14
10
- version: 0.1.14
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Jon Jensen
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-10-23 00:00:00 -06:00
19
- default_executable:
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
22
- version_requirements: &id001 !ruby/object:Gem::Requirement
23
- none: false
24
- requirements:
25
- - - ">="
26
- - !ruby/object:Gem::Version
27
- hash: 3
28
- segments:
29
- - 2
30
- - 3
31
- - 0
32
- version: 2.3.0
33
- prerelease: false
34
- type: :runtime
35
- requirement: *id001
12
+ date: 2012-12-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
36
15
  name: activerecord
37
- - !ruby/object:Gem::Dependency
38
- version_requirements: &id002 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
39
17
  none: false
40
- requirements:
41
- - - "="
42
- - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 2
46
- - 0
47
- - 6
48
- version: 2.0.6
49
- prerelease: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '2.3'
50
22
  type: :runtime
51
- requirement: *id002
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '2.3'
30
+ - !ruby/object:Gem::Dependency
52
31
  name: ruby_parser
53
- - !ruby/object:Gem::Dependency
54
- version_requirements: &id003 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
55
33
  none: false
56
- requirements:
57
- - - "="
58
- - !ruby/object:Gem::Version
59
- hash: 21
60
- segments:
61
- - 1
62
- - 2
63
- - 5
64
- version: 1.2.5
65
- prerelease: false
34
+ requirements:
35
+ - - '='
36
+ - !ruby/object:Gem::Version
37
+ version: 2.0.6
66
38
  type: :runtime
67
- requirement: *id003
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - '='
44
+ - !ruby/object:Gem::Version
45
+ version: 2.0.6
46
+ - !ruby/object:Gem::Dependency
68
47
  name: ruby2ruby
69
- - !ruby/object:Gem::Dependency
70
- version_requirements: &id004 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
71
49
  none: false
72
- requirements:
73
- - - ~>
74
- - !ruby/object:Gem::Version
75
- hash: 3
76
- segments:
77
- - 2
78
- - 3
79
- - 0
80
- version: 2.3.0
50
+ requirements:
51
+ - - '='
52
+ - !ruby/object:Gem::Version
53
+ version: 1.2.5
54
+ type: :runtime
81
55
  prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - '='
60
+ - !ruby/object:Gem::Version
61
+ version: 1.2.5
62
+ - !ruby/object:Gem::Dependency
63
+ name: rake
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
82
70
  type: :development
83
- requirement: *id004
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
84
79
  name: rspec
85
- - !ruby/object:Gem::Dependency
86
- version_requirements: &id005 !ruby/object:Gem::Requirement
80
+ requirement: !ruby/object:Gem::Requirement
87
81
  none: false
88
- requirements:
82
+ requirements:
89
83
  - - ~>
90
- - !ruby/object:Gem::Version
91
- hash: 23
92
- segments:
93
- - 1
94
- - 0
95
- - 0
96
- version: 1.0.0
97
- prerelease: false
84
+ - !ruby/object:Gem::Version
85
+ version: 2.12.0
98
86
  type: :development
99
- requirement: *id005
100
- name: bundler
101
- - !ruby/object:Gem::Dependency
102
- version_requirements: &id006 !ruby/object:Gem::Requirement
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
103
89
  none: false
104
- requirements:
90
+ requirements:
105
91
  - - ~>
106
- - !ruby/object:Gem::Version
107
- hash: 13
108
- segments:
109
- - 1
110
- - 6
111
- - 1
112
- version: 1.6.1
113
- prerelease: false
114
- type: :development
115
- requirement: *id006
116
- name: jeweler
117
- - !ruby/object:Gem::Dependency
118
- version_requirements: &id007 !ruby/object:Gem::Requirement
92
+ - !ruby/object:Gem::Version
93
+ version: 2.12.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: mysql
96
+ requirement: !ruby/object:Gem::Requirement
119
97
  none: false
120
- requirements:
121
- - - ">="
122
- - !ruby/object:Gem::Version
123
- hash: 3
124
- segments:
125
- - 0
126
- version: "0"
127
- prerelease: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 2.8.1
128
102
  type: :development
129
- requirement: *id007
130
- name: rcov
131
- - !ruby/object:Gem::Dependency
132
- version_requirements: &id008 !ruby/object:Gem::Requirement
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
133
105
  none: false
134
- requirements:
135
- - - ">="
136
- - !ruby/object:Gem::Version
137
- hash: 45
138
- segments:
139
- - 2
140
- - 8
141
- - 1
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
142
109
  version: 2.8.1
143
- prerelease: false
144
- type: :development
145
- requirement: *id008
146
- name: mysql
147
- - !ruby/object:Gem::Dependency
148
- version_requirements: &id009 !ruby/object:Gem::Requirement
110
+ - !ruby/object:Gem::Dependency
111
+ name: mysql2
112
+ requirement: !ruby/object:Gem::Requirement
149
113
  none: false
150
- requirements:
151
- - - <
152
- - !ruby/object:Gem::Version
153
- hash: 13
154
- segments:
155
- - 0
156
- - 3
157
- version: "0.3"
158
- - - ">="
159
- - !ruby/object:Gem::Version
160
- hash: 25
161
- segments:
162
- - 0
163
- - 2
164
- - 7
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
165
117
  version: 0.2.7
166
- prerelease: false
167
118
  type: :development
168
- requirement: *id009
169
- name: mysql2
170
- - !ruby/object:Gem::Dependency
171
- version_requirements: &id010 !ruby/object:Gem::Requirement
172
- none: false
173
- requirements:
174
- - - ">="
175
- - !ruby/object:Gem::Version
176
- hash: 53
177
- segments:
178
- - 0
179
- - 10
180
- - 1
181
- version: 0.10.1
182
119
  prerelease: false
183
- type: :development
184
- requirement: *id010
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: 0.2.7
126
+ - !ruby/object:Gem::Dependency
185
127
  name: pg
186
- - !ruby/object:Gem::Dependency
187
- version_requirements: &id011 !ruby/object:Gem::Requirement
128
+ requirement: !ruby/object:Gem::Requirement
188
129
  none: false
189
- requirements:
190
- - - ">="
191
- - !ruby/object:Gem::Version
192
- hash: 31
193
- segments:
194
- - 1
195
- - 3
196
- - 2
197
- version: 1.3.2
198
- prerelease: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: 0.10.1
199
134
  type: :development
200
- requirement: *id011
201
- name: sqlite3-ruby
202
- - !ruby/object:Gem::Dependency
203
- version_requirements: &id012 !ruby/object:Gem::Requirement
204
- none: false
205
- requirements:
206
- - - "="
207
- - !ruby/object:Gem::Version
208
- hash: 63
209
- segments:
210
- - 0
211
- - 10
212
- - 4
213
- version: 0.10.4
214
135
  prerelease: false
215
- type: :development
216
- requirement: *id012
217
- name: ruby-debug
218
- - !ruby/object:Gem::Dependency
219
- version_requirements: &id013 !ruby/object:Gem::Requirement
136
+ version_requirements: !ruby/object:Gem::Requirement
220
137
  none: false
221
- requirements:
222
- - - ">="
223
- - !ruby/object:Gem::Version
224
- hash: 3
225
- segments:
226
- - 2
227
- - 3
228
- - 0
229
- version: 2.3.0
230
- prerelease: false
231
- type: :runtime
232
- requirement: *id013
233
- name: activerecord
234
- - !ruby/object:Gem::Dependency
235
- version_requirements: &id014 !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: 0.10.1
142
+ - !ruby/object:Gem::Dependency
143
+ name: sqlite3
144
+ requirement: !ruby/object:Gem::Requirement
236
145
  none: false
237
- requirements:
238
- - - ~>
239
- - !ruby/object:Gem::Version
240
- hash: 3
241
- segments:
242
- - 2
243
- - 3
244
- - 0
245
- version: 2.3.0
246
- prerelease: false
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: 1.3.6
247
150
  type: :development
248
- requirement: *id014
249
- name: rspec
250
- description: allows you to declare database triggers in ruby in your models, and then generate appropriate migrations as they change
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ! '>='
156
+ - !ruby/object:Gem::Version
157
+ version: 1.3.6
158
+ description: allows you to declare database triggers in ruby in your models, and then
159
+ generate appropriate migrations as they change
251
160
  email: jenseng@gmail.com
252
161
  executables: []
253
-
254
162
  extensions: []
255
-
256
- extra_rdoc_files:
257
- - LICENSE.txt
163
+ extra_rdoc_files:
258
164
  - README.rdoc
259
- files:
260
- - .document
261
- - .rspec
262
- - Gemfile
165
+ files:
263
166
  - LICENSE.txt
264
- - README.rdoc
265
167
  - Rakefile
266
- - VERSION
267
- - init.rb
268
- - lib/hair_trigger.rb
168
+ - README.rdoc
269
169
  - lib/hair_trigger/adapter.rb
270
170
  - lib/hair_trigger/base.rb
271
171
  - lib/hair_trigger/builder.rb
272
172
  - lib/hair_trigger/migration_reader.rb
273
173
  - lib/hair_trigger/migrator.rb
174
+ - lib/hair_trigger/railtie.rb
274
175
  - lib/hair_trigger/schema_dumper.rb
176
+ - lib/hair_trigger/version.rb
177
+ - lib/hair_trigger.rb
178
+ - lib/hairtrigger.rb
275
179
  - lib/tasks/hair_trigger.rake
276
- - rails/init.rb
277
180
  - spec/builder_spec.rb
278
181
  - spec/migrations/20110331212003_initial_tables.rb
279
182
  - spec/migrations/20110331212631_user_trigger.rb
280
183
  - spec/migrations/20110417185102_manual_user_trigger.rb
184
+ - spec/migrations-pre-3.1/20110331212003_initial_tables.rb
185
+ - spec/migrations-pre-3.1/20110331212631_user_trigger.rb
186
+ - spec/migrations-pre-3.1/20110417185102_manual_user_trigger.rb
281
187
  - spec/models/group.rb
282
188
  - spec/models/user.rb
283
189
  - spec/schema_dumper_spec.rb
284
- has_rdoc: true
285
190
  homepage: http://github.com/jenseng/hair_trigger
286
- licenses:
191
+ licenses:
287
192
  - MIT
288
193
  post_install_message:
289
194
  rdoc_options: []
290
-
291
- require_paths:
195
+ require_paths:
292
196
  - lib
293
- required_ruby_version: !ruby/object:Gem::Requirement
197
+ required_ruby_version: !ruby/object:Gem::Requirement
294
198
  none: false
295
- requirements:
296
- - - ">="
297
- - !ruby/object:Gem::Version
298
- hash: 3
299
- segments:
300
- - 0
301
- version: "0"
302
- required_rubygems_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ! '>='
201
+ - !ruby/object:Gem::Version
202
+ version: 1.8.7
203
+ required_rubygems_version: !ruby/object:Gem::Requirement
303
204
  none: false
304
- requirements:
305
- - - ">="
306
- - !ruby/object:Gem::Version
307
- hash: 3
308
- segments:
309
- - 0
310
- version: "0"
205
+ requirements:
206
+ - - ! '>='
207
+ - !ruby/object:Gem::Version
208
+ version: 1.3.5
311
209
  requirements: []
312
-
313
210
  rubyforge_project:
314
- rubygems_version: 1.6.2
211
+ rubygems_version: 1.8.24
315
212
  signing_key:
316
213
  specification_version: 3
317
214
  summary: easy database triggers for active record
318
215
  test_files: []
319
-
216
+ has_rdoc:
data/.document DELETED
@@ -1,5 +0,0 @@
1
- lib/**/*.rb
2
- bin/*
3
- -
4
- features/**/*.feature
5
- LICENSE.txt
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --color
data/Gemfile DELETED
@@ -1,16 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- gem "activerecord", ">=2.3.0"
4
- gem 'ruby_parser', '2.0.6'
5
- gem 'ruby2ruby', '1.2.5'
6
- group :development do
7
- gem "rspec", "~> 2.3.0"
8
- gem "bundler", "~> 1.0.0"
9
- gem "jeweler", "~> 1.6.1"
10
- gem "rcov", ">= 0"
11
- gem 'mysql', '>= 2.8.1'
12
- gem 'mysql2', '>= 0.2.7', '< 0.3'
13
- gem 'pg', '>= 0.10.1'
14
- gem 'sqlite3-ruby', '>= 1.3.2'
15
- gem 'ruby-debug', '0.10.4'
16
- end
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.14
@@ -1 +0,0 @@
1
- require 'hair_trigger'