hairtrigger 0.1.10 → 0.1.11

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