hairtrigger 0.2.21 → 0.2.25

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/spec/builder_spec.rb DELETED
@@ -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
@@ -1,18 +0,0 @@
1
- class InitialTables < ActiveRecord::Migration[5.0]
2
- def up
3
- create_table "users" do |t|
4
- t.integer "user_group_id"
5
- t.string "name"
6
- end
7
-
8
- create_table "user_groups" do |t|
9
- t.integer "bob_count", :default => 0
10
- t.integer "updated_joe_count", :default => 0
11
- end
12
- end
13
-
14
- def down
15
- drop_table "users"
16
- drop_table "user_groups"
17
- end
18
- end
@@ -1,18 +0,0 @@
1
- # This migration was auto-generated via `rake db:generate_trigger_migration'.
2
- # While you can edit this file, any changes you make to the definitions here
3
- # will be undone by the next auto-generated trigger migration.
4
-
5
- class UserTrigger < ActiveRecord::Migration[5.0]
6
- def up
7
- create_trigger("users_after_insert_row_when_new_name_bob__tr", :generated => true, :compatibility => 1).
8
- on("users").
9
- after(:insert).
10
- where("NEW.name = 'bob'") do
11
- "UPDATE user_groups SET bob_count = bob_count + 1"
12
- end
13
- end
14
-
15
- def down
16
- drop_trigger("users_after_insert_row_when_new_name_bob__tr", "users")
17
- end
18
- end
@@ -1,10 +0,0 @@
1
- class ManualUserTrigger < ActiveRecord::Migration[5.0]
2
- def up
3
- create_trigger(:compatibility => 1).
4
- on("users").
5
- after(:update).
6
- where("NEW.name = 'joe'") do
7
- "UPDATE user_groups SET updated_joe_count = updated_joe_count + 1"
8
- end
9
- end
10
- end
@@ -1,18 +0,0 @@
1
- class InitialTables < ActiveRecord::Migration
2
- def up
3
- create_table "users" do |t|
4
- t.integer "user_group_id"
5
- t.string "name"
6
- end
7
-
8
- create_table "user_groups" do |t|
9
- t.integer "bob_count", :default => 0
10
- t.integer "updated_joe_count", :default => 0
11
- end
12
- end
13
-
14
- def down
15
- drop_table "users"
16
- drop_table "user_groups"
17
- end
18
- end
@@ -1,18 +0,0 @@
1
- # This migration was auto-generated via `rake db:generate_trigger_migration'.
2
- # While you can edit this file, any changes you make to the definitions here
3
- # will be undone by the next auto-generated trigger migration.
4
-
5
- class UserTrigger < ActiveRecord::Migration
6
- def up
7
- create_trigger("users_after_insert_row_when_new_name_bob__tr", :generated => true, :compatibility => 1).
8
- on("users").
9
- after(:insert).
10
- where("NEW.name = 'bob'") do
11
- "UPDATE user_groups SET bob_count = bob_count + 1"
12
- end
13
- end
14
-
15
- def down
16
- drop_trigger("users_after_insert_row_when_new_name_bob__tr", "users")
17
- end
18
- end
@@ -1,10 +0,0 @@
1
- class ManualUserTrigger < ActiveRecord::Migration
2
- def up
3
- create_trigger(:compatibility => 1).
4
- on("users").
5
- after(:update).
6
- where("NEW.name = 'joe'") do
7
- "UPDATE user_groups SET updated_joe_count = updated_joe_count + 1"
8
- end
9
- end
10
- end
@@ -1,18 +0,0 @@
1
- class InitialTables < ActiveRecord::Migration
2
- def self.up
3
- create_table "users" do |t|
4
- t.integer "user_group_id"
5
- t.string "name"
6
- end
7
-
8
- create_table "user_groups" do |t|
9
- t.integer "bob_count", :default => 0
10
- t.integer "updated_joe_count", :default => 0
11
- end
12
- end
13
-
14
- def self.down
15
- drop_table "users"
16
- drop_table "user_groups"
17
- end
18
- end
@@ -1,18 +0,0 @@
1
- # This migration was auto-generated via `rake db:generate_trigger_migration'.
2
- # While you can edit this file, any changes you make to the definitions here
3
- # will be undone by the next auto-generated trigger migration.
4
-
5
- class UserTrigger < ActiveRecord::Migration
6
- def self.up
7
- create_trigger("users_after_insert_row_when_new_name_bob__tr", :generated => true, :compatibility => 1).
8
- on("users").
9
- after(:insert).
10
- where("NEW.name = 'bob'") do
11
- "UPDATE user_groups SET bob_count = bob_count + 1"
12
- end
13
- end
14
-
15
- def self.down
16
- drop_trigger("users_after_insert_row_when_new_name_bob__tr", "users")
17
- end
18
- end
@@ -1,10 +0,0 @@
1
- class ManualUserTrigger < ActiveRecord::Migration
2
- def self.up
3
- create_trigger(:compatibility => 1).
4
- on("users").
5
- after(:update).
6
- where("NEW.name = 'joe'") do
7
- "UPDATE user_groups SET updated_joe_count = updated_joe_count + 1"
8
- end
9
- end
10
- end
@@ -1,60 +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 "migrations" do
8
- include_context "hairtrigger utils"
9
- let(:adapter) { :sqlite3 }
10
-
11
- describe "migrations_current?" do
12
-
13
- it "should return false if there are pending model triggers" do
14
- reset_tmp(:migration_glob => "*initial_tables*")
15
- initialize_db
16
- HairTrigger.should_not be_migrations_current
17
- end
18
-
19
- it "should return true if migrations are current" do
20
- # just one trigger migration
21
- reset_tmp(:migration_glob => "20110331212*")
22
- initialize_db
23
- migrate_db
24
- HairTrigger.should be_migrations_current
25
-
26
- # or multiple
27
- reset_tmp
28
- initialize_db
29
- migrate_db
30
- HairTrigger.should be_migrations_current
31
- end
32
-
33
- it "should return true even if migrations haven't run" do
34
- reset_tmp
35
- initialize_db
36
- migrate_db
37
- HairTrigger.should be_migrations_current
38
- end
39
- end
40
-
41
- describe "current_triggers" do
42
- it "should be inferred from self.up methods" do
43
- reset_tmp(:migration_glob => "20110331212*")
44
- initialize_db
45
-
46
- migrations = HairTrigger.current_migrations
47
- migrations.size.should == 1
48
- migrations[0][1].prepared_name.should == "users_after_insert_row_when_new_name_bob__tr"
49
- end
50
-
51
- it "should not be inferred from change methods" do
52
- reset_tmp(:migration_glob => "*manual*")
53
- replace_file_contents("tmp/migrations/20110417185102_manual_user_trigger.rb", "def up", "def change")
54
- initialize_db
55
-
56
- migrations = HairTrigger.current_migrations
57
- migrations.size.should == 0
58
- end
59
- end
60
- end
data/spec/models/user.rb DELETED
@@ -1,6 +0,0 @@
1
- class User < ActiveRecord::Base
2
- belongs_to :group
3
- trigger.after(:insert).where("NEW.name = 'bob'") do
4
- "UPDATE user_groups SET bob_count = bob_count + 1"
5
- end
6
- end
@@ -1,3 +0,0 @@
1
- class UserGroup < ActiveRecord::Base
2
- has_many :users
3
- end