hairtrigger 0.2.21 → 0.2.22

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.
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