hairtrigger 0.1.14 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'