hairtrigger 0.2.19 → 0.2.24
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 +4 -4
- data/README.md +29 -6
- data/lib/hair_trigger.rb +38 -31
- data/lib/hair_trigger/builder.rb +16 -11
- data/lib/hair_trigger/migrator.rb +10 -8
- data/lib/hair_trigger/schema_dumper.rb +14 -14
- data/lib/hair_trigger/version.rb +1 -1
- metadata +17 -24
- data/spec/adapter_spec.rb +0 -95
- data/spec/builder_spec.rb +0 -433
- data/spec/migrations-pre-3.1/20110331212003_initial_tables.rb +0 -18
- data/spec/migrations-pre-3.1/20110331212631_user_trigger.rb +0 -18
- data/spec/migrations-pre-3.1/20110417185102_manual_user_trigger.rb +0 -10
- data/spec/migrations/20110331212003_initial_tables.rb +0 -18
- data/spec/migrations/20110331212631_user_trigger.rb +0 -18
- data/spec/migrations/20110417185102_manual_user_trigger.rb +0 -10
- data/spec/migrations_spec.rb +0 -60
- data/spec/models/group.rb +0 -3
- data/spec/models/user.rb +0 -6
- data/spec/schema_dumper_spec.rb +0 -124
- data/spec/spec_helper.rb +0 -93
data/spec/adapter_spec.rb
DELETED
@@ -1,95 +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 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 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 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
|
95
|
-
|
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
|