hairtrigger 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Gemfile CHANGED
@@ -1,13 +1,16 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gem "activerecord", ">=2.3.0"
4
+ gem 'ruby_parser', '2.0.6'
5
+ gem 'ruby2ruby', '1.2.5'
4
6
  group :development do
5
7
  gem "rspec", "~> 2.3.0"
6
8
  gem "bundler", "~> 1.0.0"
7
- gem "jeweler", "~> 1.5.2"
9
+ gem "jeweler", "~> 1.6.1"
8
10
  gem "rcov", ">= 0"
9
11
  gem 'mysql', '>= 2.8.1'
10
- gem 'mysql2', '>= 0.2.7'
12
+ gem 'mysql2', '>= 0.2.7', '< 0.3'
11
13
  gem 'pg', '>= 0.10.1'
12
14
  gem 'sqlite3-ruby', '>= 1.3.2'
15
+ gem 'ruby-debug', '0.10.4'
13
16
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.10
1
+ 0.1.11
data/lib/hair_trigger.rb CHANGED
@@ -1,11 +1,10 @@
1
1
  require 'ostruct'
2
2
  require 'hair_trigger/base'
3
3
  require 'hair_trigger/builder'
4
+ require 'hair_trigger/migration_reader'
4
5
  require 'hair_trigger/migrator'
5
- require 'hair_trigger/migration'
6
6
  require 'hair_trigger/adapter'
7
7
  require 'hair_trigger/schema_dumper'
8
- require 'hair_trigger/schema'
9
8
 
10
9
  module HairTrigger
11
10
  def self.current_triggers
@@ -13,12 +12,12 @@ module HairTrigger
13
12
  canonical_triggers = []
14
13
  Dir[model_path + '/*rb'].each do |model|
15
14
  class_name = model.sub(/\A.*\/(.*?)\.rb\z/, '\1').camelize
15
+ next unless File.read(model) =~ /^\s*trigger[\.\(]/
16
16
  begin
17
17
  require model unless klass = Kernel.const_get(class_name) rescue nil
18
18
  klass = Kernel.const_get(class_name)
19
19
  rescue StandardError, LoadError
20
20
  raise "unable to load #{class_name} and its trigger(s)" if File.read(model) =~ /^\s*trigger[\.\(]/
21
- next
22
21
  end
23
22
  canonical_triggers += klass.triggers if klass < ActiveRecord::Base && klass.triggers
24
23
  end
@@ -32,11 +31,6 @@ module HairTrigger
32
31
  options[:skip_pending_migrations] = true
33
32
  end
34
33
 
35
- prev_verbose = ActiveRecord::Migration.verbose
36
- ActiveRecord::Migration.verbose = false
37
- ActiveRecord::Migration.extract_trigger_builders = true
38
- ActiveRecord::Migration.extract_all_triggers = options[:include_manual_triggers] || false
39
-
40
34
  # if we're in a db:schema:dump task (explict or kicked off by db:migrate),
41
35
  # we evaluate the previous schema.rb (if it exists), and then all applied
42
36
  # migrations in order (even ones older than schema.rb). this ensures we
@@ -47,27 +41,26 @@ module HairTrigger
47
41
  # evaluate all migrations along with schema.rb, ordered by version
48
42
  migrator = ActiveRecord::Migrator.new(:up, migration_path)
49
43
  migrated = migrator.migrated rescue []
50
- migrations = migrator.migrations.select{ |migration|
51
- File.read(migration.filename) =~ /(create|drop)_trigger/ &&
52
- (options[:skip_pending_migrations] ? migrated.include?(migration.version) : true)
53
- }.each{ |migration|
54
- migration.migrate(:up)
55
- }
56
-
57
- if options.has_key?(:previous_schema)
58
- eval(options[:previous_schema]) if options[:previous_schema]
59
- elsif File.exist?(schema_rb_path)
60
- load(schema_rb_path)
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?
61
49
  end
62
- if ActiveRecord::Schema.info && ActiveRecord::Schema.trigger_builders
63
- migrations.unshift OpenStruct.new({:version => ActiveRecord::Schema.info[:version], :trigger_builders => ActiveRecord::Schema.trigger_builders})
50
+
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]
56
+ end
64
57
  end
65
- migrations = migrations.sort_by(&:version) unless options[:schema_rb_first]
58
+
59
+ migrations = migrations.sort_by{|(migration, triggers)| migration.version} unless options[:schema_rb_first]
66
60
 
67
61
  all_builders = []
68
- migrations.each do |migration|
69
- next unless migration.trigger_builders
70
- migration.trigger_builders.each do |new_trigger|
62
+ migrations.each do |(migration, triggers)|
63
+ triggers.each do |new_trigger|
71
64
  # if there is already a trigger with this name, delete it since we are
72
65
  # either dropping it or replacing it
73
66
  new_trigger.prepare!
@@ -77,11 +70,6 @@ module HairTrigger
77
70
  end
78
71
 
79
72
  all_builders
80
-
81
- ensure
82
- ActiveRecord::Migration.verbose = prev_verbose
83
- ActiveRecord::Migration.extract_trigger_builders = false
84
- ActiveRecord::Migration.extract_all_triggers = false
85
73
  end
86
74
 
87
75
  def self.migrations_current?
@@ -185,9 +173,6 @@ end
185
173
  end
186
174
 
187
175
  ActiveRecord::Base.send :extend, HairTrigger::Base
188
- ActiveRecord::Migration.send :extend, HairTrigger::Migration
189
- ActiveRecord::MigrationProxy.send :delegate, :trigger_builders, :to=>:migration
190
176
  ActiveRecord::Migrator.send :extend, HairTrigger::Migrator
191
177
  ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval { include HairTrigger::Adapter }
192
- ActiveRecord::SchemaDumper.class_eval { include HairTrigger::SchemaDumper }
193
- ActiveRecord::Schema.send :extend, HairTrigger::Schema
178
+ ActiveRecord::SchemaDumper.class_eval { include HairTrigger::SchemaDumper }
@@ -8,6 +8,7 @@ module HairTrigger
8
8
  name = nil
9
9
  end
10
10
  options[:compatibility] ||= ::HairTrigger::Builder::compatibility
11
+ options[:generated] = true
11
12
  @triggers ||= []
12
13
  trigger = ::HairTrigger::Builder.new(name, options)
13
14
  @triggers << trigger
@@ -212,17 +212,25 @@ module HairTrigger
212
212
 
213
213
  def <=>(other)
214
214
  ret = prepared_name <=> other.prepared_name
215
- ret == 0 ? hash <=> other.hash : ret
215
+ return ret unless ret == 0
216
+ hash <=> other.hash
217
+ end
218
+
219
+ def ==(other)
220
+ components == other.components
216
221
  end
217
222
 
218
223
  def eql?(other)
219
- return false unless other.is_a?(HairTrigger::Builder)
220
- hash == other.hash
224
+ other.is_a?(HairTrigger::Builder) && self == other
221
225
  end
222
226
 
223
227
  def hash
224
228
  prepare!
225
- [self.options.hash, self.prepared_actions.hash, self.prepared_where.hash, self.triggers.hash, @compatibility].hash
229
+ components.hash
230
+ end
231
+
232
+ def components
233
+ [self.options, self.prepared_actions, self.prepared_where, self.triggers, @compatibility]
226
234
  end
227
235
 
228
236
  def errors
@@ -0,0 +1,64 @@
1
+ require 'ruby_parser'
2
+ require 'ruby2ruby'
3
+
4
+ module HairTrigger
5
+ module MigrationReader
6
+ class << self
7
+ def get_triggers(source, options)
8
+ triggers = []
9
+ if source.is_a?(String)
10
+ # schema.rb contents... because it's auto-generated and we know
11
+ # exactly what it will look like, we can safely use a regex
12
+ source.scan(/^ create_trigger\(.*?\n end\n\n/m).each do |match|
13
+ trigger = instance_eval("generate_" + match.strip)
14
+ triggers << trigger if options[:include_manual_triggers] || trigger.options[:generated]
15
+ end
16
+ else
17
+ contents = File.read(source.filename)
18
+ return [] unless contents =~ /(create|drop)_trigger/
19
+ sexps = RubyParser.new.parse(contents)
20
+ # find the migration class
21
+ sexps = [sexps] unless sexps[0] == :block
22
+ sexps = sexps.detect{ |s| s.is_a?(Sexp) && s[0] == :class && s[1] == source.name.to_sym }.last
23
+ # find the block of the up method
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
26
+ sexps.each do |sexp|
27
+ next unless (method = extract_method_call(sexp)) && [:create_trigger, :drop_trigger].include?(method)
28
+ trigger = instance_eval("generate_" + generator.process(sexp))
29
+ triggers << trigger if options[:include_manual_triggers] || trigger.options[:generated]
30
+ end
31
+ end
32
+ triggers
33
+ rescue
34
+ $stderr.puts "Error reading triggers in #{source.filename rescue "schema.rb"}: #{$!}"
35
+ end
36
+
37
+ private
38
+ def extract_method_call(exp)
39
+ return nil unless exp.is_a?(Array)
40
+ if exp[0] == :iter
41
+ exp = exp[1] while exp[1].is_a?(Array) && exp[1][0] == :call
42
+ end
43
+ if exp[0] == :call
44
+ exp[2]
45
+ end
46
+ end
47
+
48
+ def generate_create_trigger(*arguments)
49
+ arguments.unshift({}) if arguments.empty?
50
+ arguments.unshift(nil) if arguments.first.is_a?(Hash)
51
+ arguments[1][:compatibility] ||= HairTrigger::Builder.base_compatibility
52
+ ::HairTrigger::Builder.new(*arguments)
53
+ end
54
+
55
+ def generate_drop_trigger(*arguments)
56
+ ::HairTrigger::Builder.new(arguments[0], {:table => arguments[1], :drop => true})
57
+ end
58
+
59
+ def generator
60
+ @generator ||= Ruby2Ruby.new
61
+ end
62
+ end
63
+ end
64
+ end
@@ -77,6 +77,7 @@ describe "schema" do
77
77
  }
78
78
  migration = HairTrigger.generate_migration
79
79
  ActiveRecord::Migrator.migrate(HairTrigger.migration_path)
80
+ HairTrigger.should be_migrations_current
80
81
  ActiveRecord::Base.connection.triggers.values.grep(/bob_count \+ 1/).size.should eql(0)
81
82
  ActiveRecord::Base.connection.triggers.values.grep(/bob_count \+ 2/).size.should eql(1)
82
83
 
@@ -100,6 +101,24 @@ describe "schema" do
100
101
  schema_rb4.should_not eql(schema_rb3)
101
102
  schema_rb4.should eql(schema_rb2)
102
103
  ActiveRecord::Base.connection.triggers.values.grep(/bob_count \+ 1/).size.should eql(1)
104
+
105
+ # delete our migrations, it should still dump correctly
106
+ FileUtils.rm_rf(Dir.glob('tmp/migrations/*rb'))
107
+ ActiveRecord::SchemaDumper.previous_schema = schema_rb4
108
+ io = StringIO.new
109
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, io)
110
+ io.rewind
111
+ schema_rb5 = io.read
112
+ schema_rb5.should eql(schema_rb4)
113
+
114
+ # "delete" schema.rb too, now it should have adapter-specific triggers
115
+ ActiveRecord::SchemaDumper.previous_schema = nil
116
+ io = StringIO.new
117
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, io)
118
+ io.rewind
119
+ schema_rb6 = io.read
120
+ schema_rb6.should_not match(/create_trigger\(/)
121
+ schema_rb6.should match(/no candidate create_trigger statement could be found, creating an adapter-specific one/)
103
122
  end
104
123
  end
105
124
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hairtrigger
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 13
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 10
10
- version: 0.1.10
9
+ - 11
10
+ version: 0.1.11
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jon Jensen
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-05-24 00:00:00 -06:00
18
+ date: 2011-05-31 00:00:00 -06:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -36,8 +36,40 @@ dependencies:
36
36
  version_requirements: *id001
37
37
  - !ruby/object:Gem::Dependency
38
38
  prerelease: false
39
- type: :development
39
+ type: :runtime
40
40
  requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - "="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 2
48
+ - 0
49
+ - 6
50
+ version: 2.0.6
51
+ name: ruby_parser
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ prerelease: false
55
+ type: :runtime
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - "="
60
+ - !ruby/object:Gem::Version
61
+ hash: 21
62
+ segments:
63
+ - 1
64
+ - 2
65
+ - 5
66
+ version: 1.2.5
67
+ name: ruby2ruby
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ prerelease: false
71
+ type: :development
72
+ requirement: &id004 !ruby/object:Gem::Requirement
41
73
  none: false
42
74
  requirements:
43
75
  - - ~>
@@ -49,11 +81,11 @@ dependencies:
49
81
  - 0
50
82
  version: 2.3.0
51
83
  name: rspec
52
- version_requirements: *id002
84
+ version_requirements: *id004
53
85
  - !ruby/object:Gem::Dependency
54
86
  prerelease: false
55
87
  type: :development
56
- requirement: &id003 !ruby/object:Gem::Requirement
88
+ requirement: &id005 !ruby/object:Gem::Requirement
57
89
  none: false
58
90
  requirements:
59
91
  - - ~>
@@ -65,27 +97,27 @@ dependencies:
65
97
  - 0
66
98
  version: 1.0.0
67
99
  name: bundler
68
- version_requirements: *id003
100
+ version_requirements: *id005
69
101
  - !ruby/object:Gem::Dependency
70
102
  prerelease: false
71
103
  type: :development
72
- requirement: &id004 !ruby/object:Gem::Requirement
104
+ requirement: &id006 !ruby/object:Gem::Requirement
73
105
  none: false
74
106
  requirements:
75
107
  - - ~>
76
108
  - !ruby/object:Gem::Version
77
- hash: 7
109
+ hash: 13
78
110
  segments:
79
111
  - 1
80
- - 5
81
- - 2
82
- version: 1.5.2
112
+ - 6
113
+ - 1
114
+ version: 1.6.1
83
115
  name: jeweler
84
- version_requirements: *id004
116
+ version_requirements: *id006
85
117
  - !ruby/object:Gem::Dependency
86
118
  prerelease: false
87
119
  type: :development
88
- requirement: &id005 !ruby/object:Gem::Requirement
120
+ requirement: &id007 !ruby/object:Gem::Requirement
89
121
  none: false
90
122
  requirements:
91
123
  - - ">="
@@ -95,11 +127,11 @@ dependencies:
95
127
  - 0
96
128
  version: "0"
97
129
  name: rcov
98
- version_requirements: *id005
130
+ version_requirements: *id007
99
131
  - !ruby/object:Gem::Dependency
100
132
  prerelease: false
101
133
  type: :development
102
- requirement: &id006 !ruby/object:Gem::Requirement
134
+ requirement: &id008 !ruby/object:Gem::Requirement
103
135
  none: false
104
136
  requirements:
105
137
  - - ">="
@@ -111,13 +143,20 @@ dependencies:
111
143
  - 1
112
144
  version: 2.8.1
113
145
  name: mysql
114
- version_requirements: *id006
146
+ version_requirements: *id008
115
147
  - !ruby/object:Gem::Dependency
116
148
  prerelease: false
117
149
  type: :development
118
- requirement: &id007 !ruby/object:Gem::Requirement
150
+ requirement: &id009 !ruby/object:Gem::Requirement
119
151
  none: false
120
152
  requirements:
153
+ - - <
154
+ - !ruby/object:Gem::Version
155
+ hash: 13
156
+ segments:
157
+ - 0
158
+ - 3
159
+ version: "0.3"
121
160
  - - ">="
122
161
  - !ruby/object:Gem::Version
123
162
  hash: 25
@@ -127,11 +166,11 @@ dependencies:
127
166
  - 7
128
167
  version: 0.2.7
129
168
  name: mysql2
130
- version_requirements: *id007
169
+ version_requirements: *id009
131
170
  - !ruby/object:Gem::Dependency
132
171
  prerelease: false
133
172
  type: :development
134
- requirement: &id008 !ruby/object:Gem::Requirement
173
+ requirement: &id010 !ruby/object:Gem::Requirement
135
174
  none: false
136
175
  requirements:
137
176
  - - ">="
@@ -143,11 +182,11 @@ dependencies:
143
182
  - 1
144
183
  version: 0.10.1
145
184
  name: pg
146
- version_requirements: *id008
185
+ version_requirements: *id010
147
186
  - !ruby/object:Gem::Dependency
148
187
  prerelease: false
149
188
  type: :development
150
- requirement: &id009 !ruby/object:Gem::Requirement
189
+ requirement: &id011 !ruby/object:Gem::Requirement
151
190
  none: false
152
191
  requirements:
153
192
  - - ">="
@@ -159,11 +198,27 @@ dependencies:
159
198
  - 2
160
199
  version: 1.3.2
161
200
  name: sqlite3-ruby
162
- version_requirements: *id009
201
+ version_requirements: *id011
202
+ - !ruby/object:Gem::Dependency
203
+ prerelease: false
204
+ type: :development
205
+ requirement: &id012 !ruby/object:Gem::Requirement
206
+ none: false
207
+ requirements:
208
+ - - "="
209
+ - !ruby/object:Gem::Version
210
+ hash: 63
211
+ segments:
212
+ - 0
213
+ - 10
214
+ - 4
215
+ version: 0.10.4
216
+ name: ruby-debug
217
+ version_requirements: *id012
163
218
  - !ruby/object:Gem::Dependency
164
219
  prerelease: false
165
220
  type: :runtime
166
- requirement: &id010 !ruby/object:Gem::Requirement
221
+ requirement: &id013 !ruby/object:Gem::Requirement
167
222
  none: false
168
223
  requirements:
169
224
  - - ">="
@@ -175,11 +230,11 @@ dependencies:
175
230
  - 0
176
231
  version: 2.3.0
177
232
  name: activerecord
178
- version_requirements: *id010
233
+ version_requirements: *id013
179
234
  - !ruby/object:Gem::Dependency
180
235
  prerelease: false
181
236
  type: :development
182
- requirement: &id011 !ruby/object:Gem::Requirement
237
+ requirement: &id014 !ruby/object:Gem::Requirement
183
238
  none: false
184
239
  requirements:
185
240
  - - ~>
@@ -191,7 +246,7 @@ dependencies:
191
246
  - 0
192
247
  version: 2.3.0
193
248
  name: rspec
194
- version_requirements: *id011
249
+ version_requirements: *id014
195
250
  description: allows you to declare database triggers in ruby in your models, and then generate appropriate migrations as they change
196
251
  email: jenseng@gmail.com
197
252
  executables: []
@@ -214,9 +269,8 @@ files:
214
269
  - lib/hair_trigger/adapter.rb
215
270
  - lib/hair_trigger/base.rb
216
271
  - lib/hair_trigger/builder.rb
217
- - lib/hair_trigger/migration.rb
272
+ - lib/hair_trigger/migration_reader.rb
218
273
  - lib/hair_trigger/migrator.rb
219
- - lib/hair_trigger/schema.rb
220
274
  - lib/hair_trigger/schema_dumper.rb
221
275
  - lib/tasks/hair_trigger.rake
222
276
  - rails/init.rb
@@ -261,11 +315,5 @@ rubygems_version: 1.6.2
261
315
  signing_key:
262
316
  specification_version: 3
263
317
  summary: easy database triggers for active record
264
- test_files:
265
- - spec/builder_spec.rb
266
- - spec/migrations/20110331212003_initial_tables.rb
267
- - spec/migrations/20110331212631_user_trigger.rb
268
- - spec/migrations/20110417185102_manual_user_trigger.rb
269
- - spec/models/group.rb
270
- - spec/models/user.rb
271
- - spec/schema_dumper_spec.rb
318
+ test_files: []
319
+
@@ -1,39 +0,0 @@
1
- module HairTrigger
2
- module Migration
3
- attr_reader :trigger_builders
4
-
5
- def method_missing_with_trigger_building(method, *arguments, &block)
6
- if extract_trigger_builders
7
- extract_this_trigger = extract_all_triggers
8
- trigger = if method.to_sym == :create_trigger
9
- arguments.unshift({}) if arguments.empty?
10
- arguments.unshift(nil) if arguments.first.is_a?(Hash)
11
- extract_this_trigger ||= arguments[1].delete(:generated)
12
- arguments[1][:compatibility] ||= HairTrigger::Builder.base_compatibility
13
- ::HairTrigger::Builder.new(*arguments)
14
- elsif method.to_sym == :drop_trigger
15
- extract_this_trigger ||= arguments[2].delete(:generated) if arguments[2]
16
- ::HairTrigger::Builder.new(arguments[0], {:table => arguments[1], :drop => true})
17
- end
18
- (@trigger_builders ||= []) << trigger if trigger && extract_this_trigger
19
- trigger
20
-
21
- # normally we would fall through to the connection for everything
22
- # else, but we don't want to do that since we are not actually
23
- # running the migration
24
- else
25
- method_missing_without_trigger_building(method, *arguments, &block)
26
- end
27
- end
28
-
29
- def self.extended(base)
30
- base.class_eval do
31
- class << self
32
- alias_method_chain :method_missing, :trigger_building
33
- cattr_accessor :extract_trigger_builders
34
- cattr_accessor :extract_all_triggers
35
- end
36
- end
37
- end
38
- end
39
- end
@@ -1,17 +0,0 @@
1
- module HairTrigger
2
- module Schema
3
- attr_reader :info
4
- def define_with_no_save(info={}, &block)
5
- instance_eval(&block)
6
- @info = info
7
- end
8
-
9
- def self.extended(base)
10
- base.instance_eval do
11
- class << self
12
- alias_method_chain :define, :no_save
13
- end
14
- end
15
- end
16
- end
17
- end