hairtrigger 0.2.21 → 0.2.22

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f20c9f46678e0f212a8dddf861f0872609c4a519
4
- data.tar.gz: 3dd34ed254df8230ff6ffd32020be89d9b63c386
3
+ metadata.gz: 935691cbc5895c59ed3a1302e29112a6bdfd3722
4
+ data.tar.gz: fcc0c590a2c112a895d303e6027a202c3b192c1b
5
5
  SHA512:
6
- metadata.gz: a8db4cbe050c37d6aed248679e72770767e4b1b05812f8dc704ae2234e02ff5c7a8d38e3f4663ef23642b9c2aa3d39d18a813b73eb3f5ccc25cc24b5019170ca
7
- data.tar.gz: 827a7536fcee643bc4652a6cb7b073e13ab182b1b03f69bf80b63deb63f7618c6b564d3a91df5fa48acda4059f3e8c7eed03eb06510c79e4ed45d3aa7ffa6794
6
+ metadata.gz: 15b0da9b7da0f7c0534393a6ee88c034ec6b69e03589f6675fec1d8f3d221d69ec672d3bf9b7058c0ef5095897a071ad7247a904b3fd1d0a133c0548fdbf4c54
7
+ data.tar.gz: bed6e7c495e5dd1d6866f7ce86ddaa7521244ba7e3176e99c983494715a804d5fdeaeeab0ffaada4b78348bbd3b964f9c56d4df7b80fa601fc65c9a8ac64e965
data/README.md CHANGED
@@ -7,8 +7,15 @@ and a simple rake task does all the dirty work for you.
7
7
 
8
8
  ## Installation
9
9
 
10
- If you are using Rails 3 or beyond, just add `gem 'hairtrigger'` to your
11
- Gemfile. For Rails 2, it's [slightly more involved](RAILS2.md)
10
+ HairTrigger works with Rails 5.0 onwards. Add the following line to your Gemfile:
11
+
12
+ ```ruby
13
+ gem 'hairtrigger'
14
+ ```
15
+
16
+ Then run `bundle install`
17
+
18
+ For older versions of Rails check the last [0.2 release](https://github.com/jenseng/hair_trigger/tree/v0.2.21)
12
19
 
13
20
  ## Usage
14
21
 
@@ -248,7 +255,7 @@ create.
248
255
 
249
256
  ## Warnings and Errors
250
257
 
251
- There are a couple classes of errors: declaration errors and generation
258
+ There are a couple classes of errors: declaration errors and generation
252
259
  errors/warnings.
253
260
 
254
261
  Declaration errors happen if your trigger declaration is obviously wrong, and
@@ -313,10 +320,26 @@ existing trigger if you wish to redefine it.
313
320
  * Manual `create_trigger` statements have some gotchas. See the section
314
321
  "Manual triggers and :compatibility"
315
322
 
323
+ ## Contributing
324
+
325
+ Contributions welcome! I don't write much Ruby these days 😢 (and haven't used this
326
+ gem in years 😬) but am happy to take contributions. If I'm slow to respond, don't
327
+ hesitate to @ me repeatedly, sometimes those github notifications slip through
328
+ the cracks. 😆.
329
+
330
+ If you want to add a feature/bugfix, you can rely on Travis to run the tests, but
331
+ do also run them locally (especially if you are changing supported railses/etc).
332
+ HairTrigger uses [appraisal](https://github.com/thoughtbot/appraisal) to manage all
333
+ that w/ automagical gemfiles. So the tl;dr when testing locally is:
334
+
335
+ 1. make sure you have mysql and postgres installed (homebrew or whatever)
336
+ 2. `bundle exec appraisal install` -- get all the dependencies
337
+ 3. `bundle exec appraisal rake` -- run the specs every which way
338
+
316
339
  ## Compatibility
317
340
 
318
- * Ruby 1.8.7+
319
- * Rails 2.3+
341
+ * Ruby 2.3.0+
342
+ * Rails 5.0+
320
343
  * PostgreSQL 8.0+
321
344
  * MySQL 5.0.10+
322
345
  * SQLite 3.3.8+
@@ -325,4 +348,4 @@ existing trigger if you wish to redefine it.
325
348
 
326
349
  ## Copyright
327
350
 
328
- Copyright (c) 2011-2017 Jon Jensen. See LICENSE.txt for further details.
351
+ Copyright (c) 2011-2019 Jon Jensen. See LICENSE.txt for further details.
@@ -22,7 +22,7 @@ module HairTrigger
22
22
  end
23
23
 
24
24
  def models
25
- if defined?(Rails) && Rails::VERSION::MAJOR > 2
25
+ if defined?(Rails)
26
26
  Rails.application.eager_load!
27
27
  else
28
28
  Dir[model_path + '/*rb'].each do |model|
@@ -35,17 +35,13 @@ module HairTrigger
35
35
  end
36
36
  end
37
37
  end
38
- ActiveRecord::VERSION::STRING < "3.0." ?
39
- ActiveRecord::Base.send(:subclasses) :
40
- ActiveRecord::Base.descendants
38
+ ActiveRecord::Base.descendants
41
39
  end
42
40
 
43
41
  def migrator
44
42
  version = ActiveRecord::VERSION::STRING
45
43
  if version >= "5.2."
46
44
  migrations = ActiveRecord::MigrationContext.new(migration_path).migrations
47
- elsif version < "4.0."
48
- migrations = migration_path
49
45
  else # version >= "4.0."
50
46
  migrations = ActiveRecord::Migrator.migrations(migration_path)
51
47
  end
@@ -59,7 +55,7 @@ module HairTrigger
59
55
  options[:schema_rb_first] = true
60
56
  options[:skip_pending_migrations] = true
61
57
  end
62
-
58
+
63
59
  # if we're in a db:schema:dump task (explict or kicked off by db:migrate),
64
60
  # we evaluate the previous schema.rb (if it exists), and then all applied
65
61
  # migrations in order (even ones older than schema.rb). this ensures we
@@ -76,7 +72,7 @@ module HairTrigger
76
72
  triggers = MigrationReader.get_triggers(migration, options)
77
73
  migrations << [migration, triggers] unless triggers.empty?
78
74
  end
79
-
75
+
80
76
  if previous_schema = (options.has_key?(:previous_schema) ? options[:previous_schema] : File.exist?(schema_rb_path) && File.read(schema_rb_path))
81
77
  base_triggers = MigrationReader.get_triggers(previous_schema, options)
82
78
  unless base_triggers.empty?
@@ -84,9 +80,9 @@ module HairTrigger
84
80
  migrations.unshift [OpenStruct.new({:version => version}), base_triggers]
85
81
  end
86
82
  end
87
-
83
+
88
84
  migrations = migrations.sort_by{|(migration, triggers)| migration.version} unless options[:schema_rb_first]
89
-
85
+
90
86
  all_builders = []
91
87
  migrations.each do |(migration, triggers)|
92
88
  triggers.each do |new_trigger|
@@ -97,7 +93,7 @@ module HairTrigger
97
93
  all_builders << [migration.name, new_trigger] unless new_trigger.options[:drop]
98
94
  end
99
95
  end
100
-
96
+
101
97
  all_builders
102
98
  end
103
99
 
@@ -108,27 +104,27 @@ module HairTrigger
108
104
  def generate_migration(silent = false)
109
105
  begin
110
106
  canonical_triggers = current_triggers
111
- rescue
107
+ rescue
112
108
  $stderr.puts $!
113
109
  exit 1
114
110
  end
115
-
111
+
116
112
  migrations = current_migrations
117
113
  migration_names = migrations.map(&:first)
118
114
  existing_triggers = migrations.map(&:last)
119
-
115
+
120
116
  up_drop_triggers = []
121
117
  up_create_triggers = []
122
118
  down_drop_triggers = []
123
119
  down_create_triggers = []
124
-
120
+
125
121
  # see which triggers need to be dropped
126
122
  existing_triggers.each do |existing|
127
123
  next if canonical_triggers.any?{ |t| t.prepared_name == existing.prepared_name }
128
124
  up_drop_triggers.concat existing.drop_triggers
129
125
  down_create_triggers << existing
130
126
  end
131
-
127
+
132
128
  # see which triggers need to be added/replaced
133
129
  (canonical_triggers - existing_triggers).each do |new_trigger|
134
130
  up_create_triggers << new_trigger
@@ -141,29 +137,28 @@ module HairTrigger
141
137
  down_create_triggers << existing
142
138
  end
143
139
  end
144
-
140
+
145
141
  return if up_drop_triggers.empty? && up_create_triggers.empty?
146
142
 
147
143
  migration_name = infer_migration_name(migration_names, up_create_triggers, up_drop_triggers)
148
144
  migration_version = infer_migration_version(migration_name)
149
145
  file_name = migration_path + '/' + migration_version + "_" + migration_name.underscore + ".rb"
150
146
  FileUtils.mkdir_p migration_path
151
- prefix = ActiveRecord::VERSION::STRING < "3.1." ? "self." : ""
152
- File.open(file_name, "w"){ |f| f.write <<-MIGRATION }
147
+ File.open(file_name, "w") { |f| f.write <<-RUBY }
153
148
  # This migration was auto-generated via `rake db:generate_trigger_migration'.
154
149
  # While you can edit this file, any changes you make to the definitions here
155
150
  # will be undone by the next auto-generated trigger migration.
156
151
 
157
- class #{migration_name} < ActiveRecord::Migration
158
- def #{prefix}up
152
+ class #{migration_name} < ActiveRecord::Migration[#{ActiveRecord::Migration.current_version}]
153
+ def up
159
154
  #{(up_drop_triggers + up_create_triggers).map{ |t| t.to_ruby(' ') }.join("\n\n").lstrip}
160
155
  end
161
156
 
162
- def #{prefix}down
157
+ def down
163
158
  #{(down_drop_triggers + down_create_triggers).map{ |t| t.to_ruby(' ') }.join("\n\n").lstrip}
164
159
  end
165
160
  end
166
- MIGRATION
161
+ RUBY
167
162
  file_name
168
163
  end
169
164
 
@@ -226,10 +221,6 @@ end
226
221
  end
227
222
 
228
223
  ActiveRecord::Base.send :extend, HairTrigger::Base
229
- if ActiveRecord::VERSION::STRING < "4.1."
230
- ActiveRecord::Migrator.send :extend, HairTrigger::Migrator
231
- else
232
- ActiveRecord::Migration.send :include, HairTrigger::Migrator
233
- end
224
+ ActiveRecord::Migration.send :include, HairTrigger::Migrator
234
225
  ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval { include HairTrigger::Adapter }
235
226
  ActiveRecord::SchemaDumper.class_eval { include HairTrigger::SchemaDumper }
@@ -335,10 +335,11 @@ module HairTrigger
335
335
  block.call(self)
336
336
  raise DeclarationError, "trigger group did not define any triggers" if @triggers.empty?
337
337
  else
338
- @actions = block.call
339
- (@actions.is_a?(Hash) ? @actions.values : [@actions]).each do |actions|
340
- actions.sub!(/(\s*)\z/, ';\1') if actions && actions !~ /;\s*\z/
341
- end
338
+ @actions =
339
+ case (actions = block.call)
340
+ when Hash then actions.map { |key, action| [key, ensure_semicolon(action)] }.to_h
341
+ else ensure_semicolon(actions)
342
+ end
342
343
  end
343
344
  # only the top-most block actually executes
344
345
  if !@trigger_group
@@ -350,6 +351,10 @@ module HairTrigger
350
351
  self
351
352
  end
352
353
 
354
+ def ensure_semicolon(action)
355
+ action && action !~ /;\s*\z/ ? action.sub(/(\s*)\z/, ';\1') : action
356
+ end
357
+
353
358
  def validate_names!
354
359
  subtriggers = all_triggers(false)
355
360
  named_subtriggers = subtriggers.select{ |t| t.options[:name] }
@@ -55,7 +55,7 @@ module HairTrigger
55
55
  stream.puts " # no candidate create_trigger statement could be found, creating an adapter-specific one"
56
56
  end
57
57
  if definition =~ /\n/
58
- stream.print " execute(<<-TRIGGERSQL)\n#{definition.rstrip}\n TRIGGERSQL\n\n"
58
+ stream.print " execute(<<-SQL)\n#{definition.rstrip}\n SQL\n\n"
59
59
  else
60
60
  stream.print " execute(#{definition.inspect})\n\n"
61
61
  end
@@ -99,9 +99,8 @@ module HairTrigger
99
99
  def self.included(base)
100
100
  base.class_eval do
101
101
  prepend TrailerWithTriggersSupport
102
- class << self
103
- attr_accessor :previous_schema
104
- end
102
+
103
+ class_attribute :previous_schema
105
104
  end
106
105
  end
107
106
  end
@@ -1,5 +1,5 @@
1
1
  module HairTrigger
2
- VERSION = "0.2.21"
2
+ VERSION = "0.2.22"
3
3
 
4
4
  def VERSION.<=>(other)
5
5
  split(/\./).map(&:to_i) <=> other.split(/\./).map(&:to_i)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hairtrigger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.21
4
+ version: 0.2.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Jensen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-08 00:00:00.000000000 Z
11
+ date: 2019-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '2.3'
19
+ version: '5.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '6.0'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: '2.3'
29
+ version: '5.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '6.0'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: ruby_parser
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -73,22 +79,6 @@ files:
73
79
  - lib/hair_trigger/version.rb
74
80
  - lib/hairtrigger.rb
75
81
  - lib/tasks/hair_trigger.rake
76
- - spec/adapter_spec.rb
77
- - spec/builder_spec.rb
78
- - spec/migrations-3.2/20110331212003_initial_tables.rb
79
- - spec/migrations-3.2/20110331212631_user_trigger.rb
80
- - spec/migrations-3.2/20110417185102_manual_user_trigger.rb
81
- - spec/migrations-pre-3.1/20110331212003_initial_tables.rb
82
- - spec/migrations-pre-3.1/20110331212631_user_trigger.rb
83
- - spec/migrations-pre-3.1/20110417185102_manual_user_trigger.rb
84
- - spec/migrations/20110331212003_initial_tables.rb
85
- - spec/migrations/20110331212631_user_trigger.rb
86
- - spec/migrations/20110417185102_manual_user_trigger.rb
87
- - spec/migrations_spec.rb
88
- - spec/models/user.rb
89
- - spec/models/user_group.rb
90
- - spec/schema_dumper_spec.rb
91
- - spec/spec_helper.rb
92
82
  homepage: http://github.com/jenseng/hair_trigger
93
83
  licenses:
94
84
  - MIT
@@ -101,12 +91,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
101
91
  requirements:
102
92
  - - ">="
103
93
  - !ruby/object:Gem::Version
104
- version: 1.8.7
94
+ version: 2.3.0
105
95
  required_rubygems_version: !ruby/object:Gem::Requirement
106
96
  requirements:
107
97
  - - ">="
108
98
  - !ruby/object:Gem::Version
109
- version: 1.3.5
99
+ version: '0'
110
100
  requirements: []
111
101
  rubyforge_project:
112
102
  rubygems_version: 2.6.13
@@ -1,94 +0,0 @@
1
- require 'spec_helper'
2
-
3
- # for this spec to work, you need to have postgres and mysql installed (in
4
- # addition to the gems), and you should make sure that you have set up
5
- # appropriate users and permissions. see database.yml for more info
6
-
7
- describe "adapter" do
8
- include_context "hairtrigger utils"
9
-
10
- describe ".triggers" do
11
- before do
12
- reset_tmp(:migration_glob => "*initial_tables*")
13
- initialize_db
14
- migrate_db
15
- end
16
-
17
- shared_examples_for "mysql" do
18
- # have to stub SHOW TRIGGERS to get back a '%' host, since GRANTs
19
- # and such get a little dicey for testing (local vs travis, etc.)
20
- it "matches the generated trigger with a '%' grant" do
21
- conn.instance_variable_get(:@config)[:host] = "somehost" # wheeeee!
22
- implicit_definer = "'root'@'somehost'"
23
- show_triggers_definer = "root@%"
24
-
25
- builder = trigger.on(:users).before(:insert){ "UPDATE foos SET bar = 1" }
26
- triggers = builder.generate.select{|t|t !~ /\ADROP/}
27
- expect(conn).to receive(:implicit_mysql_definer).and_return(implicit_definer)
28
- expect(conn).to receive(:select_rows).with("SHOW TRIGGERS").and_return([
29
- ['users_before_insert_row_tr', 'INSERT', 'users', "BEGIN\n UPDATE foos SET bar = 1;\nEND", 'BEFORE', 'NULL', 'STRICT_ALL_TABLES', show_triggers_definer]
30
- ])
31
-
32
- expect(db_triggers).to eq(triggers)
33
- end
34
-
35
- it "quotes table names" do
36
- conn.execute <<-SQL
37
- CREATE TRIGGER foos_tr AFTER DELETE ON users
38
- FOR EACH ROW
39
- BEGIN
40
- UPDATE user_groups SET bob_count = bob_count - 1;
41
- END
42
- SQL
43
-
44
- expect(conn.triggers["foos_tr"]).to match(/CREATE TRIGGER foos_tr AFTER DELETE ON `users`/)
45
- end
46
- end
47
-
48
- context "mysql" do
49
- let(:adapter) { :mysql }
50
- it_behaves_like "mysql"
51
- end if ADAPTERS.include? :mysql
52
-
53
- context "mysql2" do
54
- let(:adapter) { :mysql2 }
55
- it_behaves_like "mysql"
56
- end if ADAPTERS.include? :mysql2
57
-
58
- context "postgresql" do
59
- let(:adapter) { :postgresql }
60
-
61
- it "quotes table names" do
62
- conn.execute <<-SQL
63
- CREATE FUNCTION foos_tr()
64
- RETURNS TRIGGER AS $$
65
- BEGIN
66
- UPDATE user_groups SET bob_count = bob_count - 1;
67
- END;
68
- $$ LANGUAGE plpgsql;
69
-
70
- CREATE TRIGGER foos_tr AFTER DELETE ON users
71
- FOR EACH ROW EXECUTE PROCEDURE foos_tr();
72
- SQL
73
-
74
- expect(conn.triggers["foos_tr"]).to match(/CREATE TRIGGER foos_tr AFTER DELETE ON "users"/)
75
- end
76
- end
77
-
78
- context "sqlite3" do
79
- let(:adapter) { :sqlite3 }
80
-
81
- it "quotes table names" do
82
- conn.execute <<-SQL
83
- CREATE TRIGGER foos_tr AFTER DELETE ON users
84
- FOR EACH ROW
85
- BEGIN
86
- UPDATE user_groups SET bob_count = bob_count - 1;
87
- END;
88
- SQL
89
-
90
- expect(conn.triggers["foos_tr"]).to match(/CREATE TRIGGER foos_tr AFTER DELETE ON "users"/)
91
- end
92
- end
93
- end
94
- end
@@ -1,433 +0,0 @@
1
- require 'spec_helper'
2
-
3
- HairTrigger::Builder.show_warnings = false
4
-
5
- class MockAdapter
6
- attr_reader :adapter_name
7
- def initialize(type, methods = {})
8
- @adapter_name = type
9
- methods.each do |key, value|
10
- instance_eval("def #{key}; #{value.inspect}; end")
11
- end
12
- end
13
-
14
- def quote_table_name(table)
15
- table
16
- end
17
- end
18
-
19
- def builder(name = nil)
20
- HairTrigger::Builder.new(name, :adapter => @adapter)
21
- end
22
-
23
- describe "builder" do
24
- context "chaining" do
25
- it "should use the last redundant chained call" do
26
- @adapter = MockAdapter.new("mysql")
27
- builder.where(:foo).where(:bar).options[:where].should be(:bar)
28
- end
29
- end
30
-
31
- context "generation" do
32
- it "should tack on a semicolon if none is provided" do
33
- @adapter = MockAdapter.new("mysql")
34
- builder.on(:foos).after(:update){ "FOO " }.generate.
35
- grep(/FOO;/).size.should eql(1)
36
- end
37
- end
38
-
39
- context "comparison" do
40
- it "should view identical triggers as identical" do
41
- @adapter = MockAdapter.new("mysql")
42
- builder.on(:foos).after(:update){ "FOO" }.
43
- should eql(builder.on(:foos).after(:update){ "FOO" })
44
- end
45
-
46
- it "should view incompatible triggers as different" do
47
- @adapter = MockAdapter.new("mysql")
48
- HairTrigger::Builder.new(nil, :adapter => @adapter, :compatibility => 0).on(:foos).after(:update){ "FOO" }.
49
- should_not eql(builder.on(:foos).after(:update){ "FOO" })
50
- end
51
- end
52
-
53
- describe "name" do
54
- it "should be inferred if none is provided" do
55
- builder.on(:foos).after(:update){ "foo" }.prepared_name.
56
- should == "foos_after_update_row_tr"
57
- end
58
-
59
- it "should respect the last chained name" do
60
- builder("lolwut").on(:foos).after(:update){ "foo" }.prepared_name.
61
- should == "lolwut"
62
- builder("lolwut").on(:foos).name("zomg").after(:update).name("yolo"){ "foo" }.prepared_name.
63
- should == "yolo"
64
- end
65
- end
66
-
67
- describe "`of' columns" do
68
- it "should be disallowed for non-update triggers" do
69
- lambda {
70
- builder.on(:foos).after(:insert).of(:bar, :baz){ "BAR" }
71
- }.should raise_error /of may only be specified on update triggers/
72
- end
73
- end
74
-
75
- describe "groups" do
76
- it "should allow chained methods" do
77
- triggers = builder.on(:foos){ |t|
78
- t.where('bar=1').name('bar'){ 'BAR;' }
79
- t.where('baz=1').name('baz'){ 'BAZ;' }
80
- }.triggers
81
- triggers.map(&:prepare!)
82
- triggers.map(&:prepared_name).should == ['bar', 'baz']
83
- triggers.map(&:prepared_where).should == ['bar=1', 'baz=1']
84
- triggers.map(&:prepared_actions).should == ['BAR;', 'BAZ;']
85
- end
86
- end
87
-
88
- context "adapter-specific actions" do
89
- before(:each) do
90
- @adapter = MockAdapter.new("mysql")
91
- end
92
-
93
- it "should generate the appropriate trigger for the adapter" do
94
- sql = builder.on(:foos).after(:update).where('BAR'){
95
- {:default => "DEFAULT", :mysql => "MYSQL"}
96
- }.generate
97
-
98
- sql.grep(/DEFAULT/).size.should eql(0)
99
- sql.grep(/MYSQL/).size.should eql(1)
100
-
101
- sql = builder.on(:foos).after(:update).where('BAR'){
102
- {:default => "DEFAULT", :postgres => "POSTGRES"}
103
- }.generate
104
-
105
- sql.grep(/POSTGRES/).size.should eql(0)
106
- sql.grep(/DEFAULT/).size.should eql(1)
107
- end
108
-
109
- it "should complain if no actions are provided for this adapter" do
110
- lambda {
111
- builder.on(:foos).after(:update).where('BAR'){ {:postgres => "POSTGRES"} }.generate
112
- }.should raise_error
113
- end
114
- end
115
-
116
- context "mysql" do
117
- before(:each) do
118
- @adapter = MockAdapter.new("mysql")
119
- end
120
-
121
- it "should create a single trigger for a group" do
122
- trigger = builder.on(:foos).after(:update){ |t|
123
- t.where('BAR'){ 'BAR' }
124
- t.where('BAZ'){ 'BAZ' }
125
- }
126
- trigger.generate.grep(/CREATE.*TRIGGER/).size.should eql(1)
127
- end
128
-
129
- it "should disallow nested groups" do
130
- lambda {
131
- builder.on(:foos){ |t|
132
- t.after(:update){ |t|
133
- t.where('BAR'){ 'BAR' }
134
- t.where('BAZ'){ 'BAZ' }
135
- }
136
- }.generate
137
- }.should raise_error
138
- end
139
-
140
- it "should warn on explicit subtrigger names and no group name" do
141
- trigger = builder.on(:foos){ |t|
142
- t.where('bar=1').name('bar'){ 'BAR;' }
143
- t.where('baz=1').name('baz'){ 'BAZ;' }
144
- }
145
- trigger.warnings.size.should == 1
146
- trigger.warnings.first.first.should =~ /nested triggers have explicit names/
147
- end
148
-
149
- it "should accept security" do
150
- builder.on(:foos).after(:update).security(:definer){ "FOO" }.generate.
151
- grep(/DEFINER/).size.should eql(0) # default, so we don't include it
152
- builder.on(:foos).after(:update).security("CURRENT_USER"){ "FOO" }.generate.
153
- grep(/DEFINER = CURRENT_USER/).size.should eql(1)
154
- builder.on(:foos).after(:update).security("'user'@'host'"){ "FOO" }.generate.
155
- grep(/DEFINER = 'user'@'host'/).size.should eql(1)
156
- end
157
-
158
- it "should infer `if' conditionals from `of' columns" do
159
- builder.on(:foos).after(:update).of(:bar){ "BAZ" }.generate.join("\n").
160
- should include("IF NEW.bar <> OLD.bar OR (NEW.bar IS NULL) <> (OLD.bar IS NULL) THEN")
161
- end
162
-
163
- it "should merge `where` and `of` into an `if` conditional" do
164
- builder.on(:foos).after(:update).of(:bar).where("lol"){ "BAZ" }.generate.join("\n").
165
- should include("IF (lol) AND (NEW.bar <> OLD.bar OR (NEW.bar IS NULL) <> (OLD.bar IS NULL)) THEN")
166
- end
167
-
168
- it "should reject :invoker security" do
169
- lambda {
170
- builder.on(:foos).after(:update).security(:invoker){ "FOO" }.generate
171
- }.should raise_error
172
- end
173
-
174
- it "should reject for_each :statement" do
175
- lambda {
176
- builder.on(:foos).after(:update).for_each(:statement){ "FOO" }.generate
177
- }.should raise_error
178
- end
179
-
180
- it "should reject multiple events" do
181
- lambda {
182
- builder.on(:foos).after(:update, :delete){ "FOO" }.generate
183
- }.should raise_error
184
- end
185
-
186
- it "should reject truncate" do
187
- lambda {
188
- builder.on(:foos).after(:truncate){ "FOO" }.generate
189
- }.should raise_error
190
- end
191
-
192
- describe "#to_ruby" do
193
- it "should fully represent the builder" do
194
- code = <<-CODE.strip.gsub(/^ +/, '')
195
- on("foos").
196
- security(:definer).
197
- for_each(:row).
198
- before(:update) do |t|
199
- t.where("NEW.foo") do
200
- "FOO;"
201
- end
202
- end
203
- CODE
204
- b = builder
205
- b.instance_eval(code)
206
- b.to_ruby.strip.gsub(/^ +/, '').should be_include(code)
207
- end
208
- end
209
- end
210
-
211
- context "postgresql" do
212
- before(:each) do
213
- @adapter = MockAdapter.new("postgresql", :postgresql_version => 94000)
214
- end
215
-
216
- it "should create multiple triggers for a group" do
217
- trigger = builder.on(:foos).after(:update){ |t|
218
- t.where('BAR'){ 'BAR' }
219
- t.where('BAZ'){ 'BAZ' }
220
- }
221
- trigger.generate.grep(/CREATE.*TRIGGER/).size.should eql(2)
222
- end
223
-
224
- it "should allow nested groups" do
225
- trigger = builder.on(:foos){ |t|
226
- t.after(:update){ |t|
227
- t.where('BAR'){ 'BAR' }
228
- t.where('BAZ'){ 'BAZ' }
229
- }
230
- t.after(:insert){ 'BAZ' }
231
- }
232
- trigger.generate.grep(/CREATE.*TRIGGER/).size.should eql(3)
233
- end
234
-
235
- it "should warn on an explicit group names and no subtrigger names" do
236
- trigger = builder.on(:foos).name('foos'){ |t|
237
- t.where('bar=1'){ 'BAR;' }
238
- t.where('baz=1'){ 'BAZ;' }
239
- }
240
- trigger.warnings.size.should == 1
241
- trigger.warnings.first.first.should =~ /trigger group has an explicit name/
242
- end
243
-
244
- it "should accept `of' columns" do
245
- trigger = builder.on(:foos).after(:update).of(:bar, :baz){ "BAR" }
246
- trigger.generate.grep(/AFTER UPDATE OF bar, baz/).size.should eql(1)
247
- end
248
-
249
- it "should accept security" do
250
- builder.on(:foos).after(:update).security(:invoker){ "FOO" }.generate.
251
- grep(/SECURITY/).size.should eql(0) # default, so we don't include it
252
- builder.on(:foos).after(:update).security(:definer){ "FOO" }.generate.
253
- grep(/SECURITY DEFINER/).size.should eql(1)
254
- end
255
-
256
- it "should reject arbitrary user security" do
257
- lambda {
258
- builder.on(:foos).after(:update).security("'user'@'host'"){ "FOO" }.
259
- generate
260
- }.should raise_error
261
- end
262
-
263
- it "should accept multiple events" do
264
- builder.on(:foos).after(:update, :delete){ "FOO" }.generate.
265
- grep(/UPDATE OR DELETE/).size.should eql(1)
266
- end
267
-
268
- it "should reject long names" do
269
- lambda {
270
- builder.name('A'*65).on(:foos).after(:update){ "FOO" }.generate
271
- }.should raise_error
272
- end
273
-
274
- it "should allow truncate with for_each statement" do
275
- builder.on(:foos).after(:truncate).for_each(:statement){ "FOO" }.generate.
276
- grep(/TRUNCATE.*FOR EACH STATEMENT/m).size.should eql(1)
277
- end
278
-
279
- it "should reject truncate with for_each row" do
280
- lambda {
281
- builder.on(:foos).after(:truncate){ "FOO" }.generate
282
- }.should raise_error
283
- end
284
-
285
- it "should add a return statement if none is provided" do
286
- builder.on(:foos).after(:update){ "FOO" }.generate.
287
- grep(/RETURN NULL;/).size.should eql(1)
288
- end
289
-
290
- it "should not wrap the action in a function" do
291
- builder.on(:foos).after(:update).nowrap{ 'existing_procedure()' }.generate.
292
- grep(/CREATE FUNCTION/).size.should eql(0)
293
- end
294
-
295
- it "should reject combined use of security and nowrap" do
296
- lambda {
297
- builder.on(:foos).after(:update).security("'user'@'host'").nowrap{ "FOO" }.generate
298
- }.should raise_error
299
- end
300
-
301
- it "should allow variable declarations" do
302
- builder.on(:foos).after(:insert).declare("foo INT"){ "FOO" }.generate.join("\n").
303
- should match(/DECLARE\s*foo INT;\s*BEGIN\s*FOO/)
304
- end
305
-
306
- context "legacy" do
307
- it "should reject truncate pre-8.4" do
308
- @adapter = MockAdapter.new("postgresql", :postgresql_version => 80300)
309
- lambda {
310
- builder.on(:foos).after(:truncate).for_each(:statement){ "FOO" }.generate
311
- }.should raise_error
312
- end
313
-
314
- it "should use conditionals pre-9.0" do
315
- @adapter = MockAdapter.new("postgresql", :postgresql_version => 80400)
316
- builder.on(:foos).after(:insert).where("BAR"){ "FOO" }.generate.
317
- grep(/IF BAR/).size.should eql(1)
318
- end
319
-
320
- it "should reject combined use of where and nowrap pre-9.0" do
321
- @adapter = MockAdapter.new("postgresql", :postgresql_version => 80400)
322
- lambda {
323
- builder.on(:foos).after(:insert).where("BAR").nowrap{ "FOO" }.generate
324
- }.should raise_error
325
- end
326
-
327
- it "should infer `if' conditionals from `of' columns on pre-9.0" do
328
- @adapter = MockAdapter.new("postgresql", :postgresql_version => 80400)
329
- builder.on(:foos).after(:update).of(:bar){ "BAZ" }.generate.join("\n").
330
- should include("IF NEW.bar <> OLD.bar OR (NEW.bar IS NULL) <> (OLD.bar IS NULL) THEN")
331
- end
332
- end
333
-
334
- describe "#to_ruby" do
335
- it "should fully represent the builder" do
336
- code = <<-CODE.strip.gsub(/^ +/, '')
337
- on("foos").
338
- of("bar").
339
- security(:invoker).
340
- for_each(:row).
341
- before(:update) do |t|
342
- t.where("NEW.foo").declare("row RECORD") do
343
- "FOO;"
344
- end
345
- end
346
- CODE
347
- b = builder
348
- b.instance_eval(code)
349
- b.to_ruby.strip.gsub(/^ +/, '').should be_include(code)
350
- end
351
- end
352
- end
353
-
354
- context "sqlite" do
355
- before(:each) do
356
- @adapter = MockAdapter.new("sqlite")
357
- end
358
-
359
- it "should create multiple triggers for a group" do
360
- trigger = builder.on(:foos).after(:update){ |t|
361
- t.where('BAR'){ 'BAR' }
362
- t.where('BAZ'){ 'BAZ' }
363
- }
364
- trigger.generate.grep(/CREATE.*TRIGGER/).size.should eql(2)
365
- end
366
-
367
- it "should allow nested groups" do
368
- trigger = builder.on(:foos){ |t|
369
- t.after(:update){ |t|
370
- t.where('BAR'){ 'BAR' }
371
- t.where('BAZ'){ 'BAZ' }
372
- }
373
- t.after(:insert){ 'BAZ' }
374
- }
375
- trigger.generate.grep(/CREATE.*TRIGGER/).size.should eql(3)
376
- end
377
-
378
- it "should warn on an explicit group names and no subtrigger names" do
379
- trigger = builder.on(:foos).name('foos'){ |t|
380
- t.where('bar=1'){ 'BAR;' }
381
- t.where('baz=1'){ 'BAZ;' }
382
- }
383
- trigger.warnings.size.should == 1
384
- trigger.warnings.first.first.should =~ /trigger group has an explicit name/
385
- end
386
-
387
- it "should accept `of' columns" do
388
- trigger = builder.on(:foos).after(:update).of(:bar, :baz){ "BAR" }
389
- trigger.generate.grep(/AFTER UPDATE OF bar, baz/).size.should eql(1)
390
- end
391
-
392
- it "should reject security" do
393
- lambda {
394
- builder.on(:foos).after(:update).security(:definer){ "FOO" }.generate
395
- }.should raise_error
396
- end
397
-
398
- it "should reject for_each :statement" do
399
- lambda {
400
- builder.on(:foos).after(:update).for_each(:statement){ "FOO" }.generate
401
- }.should raise_error
402
- end
403
-
404
- it "should reject multiple events" do
405
- lambda {
406
- builder.on(:foos).after(:update, :delete){ "FOO" }.generate
407
- }.should raise_error
408
- end
409
-
410
- it "should reject truncate" do
411
- lambda {
412
- builder.on(:foos).after(:truncate){ "FOO" }.generate
413
- }.should raise_error
414
- end
415
-
416
- describe "#to_ruby" do
417
- it "should fully represent the builder" do
418
- code = <<-CODE.strip.gsub(/^ +/, '')
419
- on("foos").
420
- of("bar").
421
- before(:update) do |t|
422
- t.where("NEW.foo") do
423
- "FOO;"
424
- end
425
- end
426
- CODE
427
- b = builder
428
- b.instance_eval(code)
429
- b.to_ruby.strip.gsub(/^ +/, '').should be_include(code)
430
- end
431
- end
432
- end
433
- end