hairtrigger 0.2.4 → 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +290 -0
- data/lib/hair_trigger/version.rb +1 -1
- data/spec/builder_spec.rb +17 -3
- metadata +7 -8
- data/README.rdoc +0 -289
data/README.md
ADDED
@@ -0,0 +1,290 @@
|
|
1
|
+
# HairTrigger
|
2
|
+
[<img src="https://secure.travis-ci.org/jenseng/hair_trigger.png?branch=master" />](http://travis-ci.org/jenseng/hair_trigger)
|
3
|
+
|
4
|
+
HairTrigger lets you create and manage database triggers in a concise,
|
5
|
+
db-agnostic, Rails-y way. You declare triggers right in your models in Ruby,
|
6
|
+
and a simple rake task does all the dirty work for you.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
### Rails 3 or 4
|
11
|
+
|
12
|
+
If you are using Rails 3 or 4, just `gem 'hairtrigger'`
|
13
|
+
|
14
|
+
### Rails 2
|
15
|
+
|
16
|
+
#### Step 1.
|
17
|
+
|
18
|
+
Put hairtrigger in your Gemfile, or if you're not using bundler, you can
|
19
|
+
`gem install hairtrigger` and then put hairtrigger in environment.rb
|
20
|
+
|
21
|
+
#### Step 2.
|
22
|
+
|
23
|
+
Create lib/tasks/hair_trigger.rake with the following:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
$VERBOSE = nil
|
27
|
+
Dir["#{Gem::Specification.find_by_name('hairtrigger').full_gem_path}/lib/tasks/*.rake"].each { |ext| load ext }
|
28
|
+
```
|
29
|
+
|
30
|
+
This will give you the `db:generate_trigger_migration` task, and will ensure
|
31
|
+
that hairtrigger hooks into `db:schema:dump`.
|
32
|
+
|
33
|
+
If you are unpacking the gem in vendor/plugins, this step is not needed
|
34
|
+
(though you'll then want to delete its Gemfile to avoid possible conflicts).
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
### Models
|
39
|
+
|
40
|
+
Declare triggers in your models and use a rake task to auto-generate the
|
41
|
+
appropriate migration. For example:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
class AccountUser < ActiveRecord::Base
|
45
|
+
trigger.after(:insert) do
|
46
|
+
"UPDATE accounts SET user_count = user_count + 1 WHERE id = NEW.account_id;"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
and then:
|
52
|
+
|
53
|
+
```bash
|
54
|
+
rake db:generate_trigger_migration
|
55
|
+
```
|
56
|
+
|
57
|
+
This will create a db-agnostic migration for the trigger that mirrors the
|
58
|
+
model declaration. The end result in MySQL will be something like this:
|
59
|
+
|
60
|
+
```sql
|
61
|
+
CREATE TRIGGER account_users_after_insert_row_tr AFTER INSERT ON account_users
|
62
|
+
FOR EACH ROW
|
63
|
+
BEGIN
|
64
|
+
UPDATE accounts SET user_count = user_count + 1 WHERE id = NEW.account_id;
|
65
|
+
END
|
66
|
+
```
|
67
|
+
|
68
|
+
Note that these auto-generated `create_trigger` statements in the migration
|
69
|
+
contain the `:generated => true` option, indicating that they were created
|
70
|
+
from the model definition. This is important, as the rake task will also
|
71
|
+
generate appropriate drop/create statements for any model triggers that get
|
72
|
+
removed or updated. It does this by diffing the current model trigger
|
73
|
+
declarations and any auto-generated triggers in schema.rb (and subsequent
|
74
|
+
migrations).
|
75
|
+
|
76
|
+
### Chainable Methods
|
77
|
+
|
78
|
+
Triggers are built by chaining several methods together, ending in a block
|
79
|
+
that specifies the SQL to be run when the trigger fires. Supported methods
|
80
|
+
include:
|
81
|
+
|
82
|
+
#### name(trigger_name)
|
83
|
+
Optional, inferred from other calls.
|
84
|
+
|
85
|
+
#### on(table_name)
|
86
|
+
Ignored in models, required in migrations.
|
87
|
+
|
88
|
+
#### for_each(item)
|
89
|
+
Defaults to `:row`, PostgreSQL allows `:statement`.
|
90
|
+
|
91
|
+
#### before(*events)
|
92
|
+
Shorthand for `timing(:before).events(*events)`.
|
93
|
+
|
94
|
+
#### after(*events)
|
95
|
+
Shorthand for `timing(:after).events(*events)`.
|
96
|
+
|
97
|
+
#### where(conditions)
|
98
|
+
Optional, SQL snippet limiting when the trigger will fire. Supports delayed interpolation of variables.
|
99
|
+
|
100
|
+
#### security(user)
|
101
|
+
Permissions/role to check when calling trigger. PostgreSQL supports `:invoker` (default) and `:definer`, MySQL supports `:definer` (default) and arbitrary users (syntax: `'user'@'host'`).
|
102
|
+
|
103
|
+
#### timing(timing)
|
104
|
+
Required (but may be satisified by `before`/`after`). Possible values are `:before`/`:after`.
|
105
|
+
|
106
|
+
#### events(*events)
|
107
|
+
Required (but may be satisified by `before`/`after`). Possible values are `:insert`/`:update`/`:delete`/`:truncate`. MySQL/SQLite only support one action per trigger, and don't support `:truncate`.
|
108
|
+
|
109
|
+
#### all
|
110
|
+
Noop, useful for trigger groups (see below).
|
111
|
+
|
112
|
+
### Trigger Groups
|
113
|
+
|
114
|
+
Trigger groups allow you to use a slightly more concise notation if you have
|
115
|
+
several triggers that fire on a given model. This is also important for MySQL,
|
116
|
+
since it does not support multiple triggers on a table for the same action
|
117
|
+
and timing. For example:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
trigger.after(:update) do |t|
|
121
|
+
t.all do # every row
|
122
|
+
# some sql
|
123
|
+
end
|
124
|
+
t.where("OLD.foo != NEW.foo") do
|
125
|
+
# some more sql
|
126
|
+
end
|
127
|
+
t.where("OLD.bar != NEW.bar") do
|
128
|
+
# some other sql
|
129
|
+
end
|
130
|
+
end
|
131
|
+
```
|
132
|
+
|
133
|
+
For MySQL, this will just create a single trigger with conditional logic
|
134
|
+
(since it doesn't support multiple triggers). PostgreSQL and SQLite will have
|
135
|
+
distinct triggers. This same notation is also used within trigger migrations.
|
136
|
+
MySQL does not currently support nested trigger groups.
|
137
|
+
|
138
|
+
### Database-specific trigger bodies
|
139
|
+
|
140
|
+
Although HairTrigger aims to be totally db-agnostic, at times you do need a
|
141
|
+
little more control over the body of the trigger. You can tailor it for
|
142
|
+
specific databases by returning a hash rather than a string. Make sure to set
|
143
|
+
a `:default` value if you aren't explicitly specifying all of them.
|
144
|
+
|
145
|
+
For example, MySQL generally performs poorly with subselects in `UPDATE`
|
146
|
+
statements, and it has its own proprietary syntax for multi-table `UPDATE`s. So
|
147
|
+
you might do something like the following:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
trigger.after(:insert) do
|
151
|
+
{:default => <<-DEFAULT_SQL, :mysql => <<-MYSQL}
|
152
|
+
|
153
|
+
UPDATE users SET item_count = item_count + 1
|
154
|
+
WHERE id IN (SELECT user_id FROM buckets WHERE id = NEW.bucket_id)
|
155
|
+
DEFAULT_SQL
|
156
|
+
|
157
|
+
UPDATE users, buckets SET item_count = item_count + 1
|
158
|
+
WHERE users.id = user_id AND buckets.id = NEW.bucket_id
|
159
|
+
MYSQL
|
160
|
+
end
|
161
|
+
```
|
162
|
+
|
163
|
+
### Manual Migrations
|
164
|
+
|
165
|
+
You can also manage triggers manually in your migrations via `create_trigger` and
|
166
|
+
`drop_trigger`. They are a little more verbose than model triggers, and they can
|
167
|
+
be more work since you need to figure out the up/down create/drop logic when
|
168
|
+
you change things. A sample trigger:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
create_trigger(:compatibility => 1).on(:users).after(:insert) do
|
172
|
+
"UPDATE accounts SET user_count = user_count + 1 WHERE id = NEW.account_id;"
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
#### Manual triggers and :compatibility
|
177
|
+
|
178
|
+
As bugs are fixed and features are implemented in hairtrigger, it's possible
|
179
|
+
that the generated trigger SQL will change (this has only happened once so
|
180
|
+
far). If you upgrade to a newer version of hairtrigger, it needs a way of
|
181
|
+
knowing which previous version generated the original trigger. You only need
|
182
|
+
to worry about this for manual trigger migrations, as the model ones
|
183
|
+
automatically take care of this. For your manual triggers you can either:
|
184
|
+
|
185
|
+
* pass `:compatibility => x` to your `create_trigger` statement, where x is
|
186
|
+
whatever HairTrigger::Builder.compatiblity is (1 for this version).
|
187
|
+
* set `HairTrigger::Builder.base_compatibility = x` in an initializer, where
|
188
|
+
x is whatever HairTrigger::Builder.compatiblity is. This is like doing the
|
189
|
+
first option on every `create_trigger`. Note that once the compatibility
|
190
|
+
changes, you'll need to set `:compatibility` on new triggers (unless you
|
191
|
+
just redo all your triggers and bump the `base_compatibility`).
|
192
|
+
|
193
|
+
If you upgrade to a newer version of hairtrigger and see that the SQL
|
194
|
+
compatibility has changed, you'll need to set the appropriate compatibility
|
195
|
+
on any new triggers that you create.
|
196
|
+
|
197
|
+
## rake db:schema:dump
|
198
|
+
|
199
|
+
HairTrigger hooks into `rake db:schema:dump` (and rake tasks that call it) to
|
200
|
+
make it trigger-aware. A newly generated schema.rb will contain:
|
201
|
+
|
202
|
+
* `create_trigger` statements for any database triggers that exactly match a
|
203
|
+
`create_trigger` statement in an applied migration or in the previous
|
204
|
+
schema.rb file. this includes both generated and manual `create_trigger`
|
205
|
+
calls.
|
206
|
+
* adapter-specific `execute('CREATE TRIGGER..')` statements for any unmatched
|
207
|
+
database triggers.
|
208
|
+
|
209
|
+
As long as you don't delete old migrations and schema.rb prior to running
|
210
|
+
`rake db:schema:dump`, the result should be what you expect (and portable).
|
211
|
+
If you have deleted all trigger migrations, you can regenerate a new
|
212
|
+
baseline for model triggers via rake db:generate_trigger_migration.
|
213
|
+
|
214
|
+
## Testing
|
215
|
+
|
216
|
+
To stay on top of things, it's strongly recommended that you add a test or
|
217
|
+
spec to ensure your migrations/schema.rb match your models. This is as simple
|
218
|
+
as:
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
assert HairTrigger::migrations_current?
|
222
|
+
```
|
223
|
+
|
224
|
+
This way you'll know if there are any outstanding migrations you need to
|
225
|
+
create.
|
226
|
+
|
227
|
+
## Warnings and Errors
|
228
|
+
|
229
|
+
There are a couple classes of errors: declaration errors and generation
|
230
|
+
errors/warnings.
|
231
|
+
|
232
|
+
Declaration errors happen if your trigger declaration is obviously wrong, and
|
233
|
+
will cause a runtime error in your model or migration class. An example would
|
234
|
+
be `trigger.after(:never)`, since `:never` is not a valid event.
|
235
|
+
|
236
|
+
Generation errors happen if you try something that your adapter doesn't
|
237
|
+
support. An example would be something like `trigger.security(:invoker)` for
|
238
|
+
MySQL. These errors only happen when the trigger is actually generated, e.g.
|
239
|
+
when you attempt to run the migration.
|
240
|
+
|
241
|
+
Generation warnings are similar but they don't stop the trigger from being
|
242
|
+
generated. If you do something adapter-specific supported by your database,
|
243
|
+
you will still get a warning ($stderr) that your trigger is not portable. You
|
244
|
+
can silence warnings via `HairTrigger::Builder.show_warnings = false`
|
245
|
+
|
246
|
+
You can validate your triggers beforehand using the `Builder#validate!` method.
|
247
|
+
It will throw the appropriate errors/warnings so that you know what to fix,
|
248
|
+
e.g.
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
User.triggers.each(&:validate!)
|
252
|
+
```
|
253
|
+
|
254
|
+
HairTrigger does not validate your SQL, so be sure to test it in all databases
|
255
|
+
you want to support.
|
256
|
+
|
257
|
+
## Gotchas
|
258
|
+
|
259
|
+
* As is the case with ActiveRecord::Base.update_all or any direct SQL you do,
|
260
|
+
be careful to reload updated objects from the database. For example, the
|
261
|
+
following code will display the wrong count since we aren't reloading the
|
262
|
+
account:
|
263
|
+
|
264
|
+
```ruby
|
265
|
+
a = Account.find(123)
|
266
|
+
a.account_users.create(:name => 'bob')
|
267
|
+
puts "count is now #{a.user_count}"
|
268
|
+
```
|
269
|
+
* For repeated chained calls, the last one wins, there is currently no
|
270
|
+
merging.
|
271
|
+
* If you want your code to be portable, the trigger actions should be
|
272
|
+
limited to `INSERT`/`UPDATE`/`DELETE`/`SELECT`, and conditional logic should be
|
273
|
+
handled through the `:where` option/method. Otherwise you'll likely run into
|
274
|
+
trouble due to differences in syntax and supported features.
|
275
|
+
* Manual `create_trigger` statements have some gotchas. See the section
|
276
|
+
"Manual triggers and :compatibility"
|
277
|
+
|
278
|
+
## Compatibility
|
279
|
+
|
280
|
+
* Ruby 1.8.7+
|
281
|
+
* Rails 2.3+
|
282
|
+
* Postgres 8.0+
|
283
|
+
* MySQL 5.0.10+
|
284
|
+
* SQLite 3.3.8+
|
285
|
+
|
286
|
+
## [Changelog](blob/master/CHANGELOG.md)
|
287
|
+
|
288
|
+
## Copyright
|
289
|
+
|
290
|
+
Copyright (c) 2011-2014 Jon Jensen. See LICENSE.txt for further details.
|
data/lib/hair_trigger/version.rb
CHANGED
data/spec/builder_spec.rb
CHANGED
@@ -14,8 +14,8 @@ class MockAdapter
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
-
def builder
|
18
|
-
HairTrigger::Builder.new(
|
17
|
+
def builder(name = nil)
|
18
|
+
HairTrigger::Builder.new(name, :adapter => @adapter)
|
19
19
|
end
|
20
20
|
|
21
21
|
describe "builder" do
|
@@ -48,6 +48,20 @@ describe "builder" do
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
describe "name" do
|
52
|
+
it "should be inferred if none is provided" do
|
53
|
+
builder.on(:foos).after(:update){ "foo" }.prepared_name.
|
54
|
+
should == "foos_after_update_row_tr"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should respect the last chained name" do
|
58
|
+
builder("lolwut").on(:foos).after(:update){ "foo" }.prepared_name.
|
59
|
+
should == "lolwut"
|
60
|
+
builder("lolwut").on(:foos).name("zomg").after(:update).name("yolo"){ "foo" }.prepared_name.
|
61
|
+
should == "yolo"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
51
65
|
context "adapter-specific actions" do
|
52
66
|
before(:each) do
|
53
67
|
@adapter = MockAdapter.new("mysql")
|
@@ -263,4 +277,4 @@ describe "builder" do
|
|
263
277
|
}.should raise_error
|
264
278
|
end
|
265
279
|
end
|
266
|
-
end
|
280
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hairtrigger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2014-03-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
requirements:
|
35
35
|
- - ~>
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: 3.
|
37
|
+
version: '3.4'
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +42,7 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: 3.
|
45
|
+
version: '3.4'
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: ruby2ruby
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -160,12 +160,11 @@ description: allows you to declare database triggers in ruby in your models, and
|
|
160
160
|
email: jenseng@gmail.com
|
161
161
|
executables: []
|
162
162
|
extensions: []
|
163
|
-
extra_rdoc_files:
|
164
|
-
- README.rdoc
|
163
|
+
extra_rdoc_files: []
|
165
164
|
files:
|
166
165
|
- LICENSE.txt
|
167
166
|
- Rakefile
|
168
|
-
- README.
|
167
|
+
- README.md
|
169
168
|
- lib/hair_trigger/adapter.rb
|
170
169
|
- lib/hair_trigger/base.rb
|
171
170
|
- lib/hair_trigger/builder.rb
|
@@ -208,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
208
207
|
version: 1.3.5
|
209
208
|
requirements: []
|
210
209
|
rubyforge_project:
|
211
|
-
rubygems_version: 1.8.
|
210
|
+
rubygems_version: 1.8.23
|
212
211
|
signing_key:
|
213
212
|
specification_version: 3
|
214
213
|
summary: easy database triggers for active record
|
data/README.rdoc
DELETED
@@ -1,289 +0,0 @@
|
|
1
|
-
= HairTrigger
|
2
|
-
{<img src="https://secure.travis-ci.org/jenseng/hair_trigger.png?branch=master" />}[http://travis-ci.org/jenseng/hair_trigger]
|
3
|
-
|
4
|
-
HairTrigger lets you create and manage database triggers in a concise,
|
5
|
-
db-agnostic, Rails-y way. You declare triggers right in your models in Ruby,
|
6
|
-
and a simple rake task does all the dirty work for you.
|
7
|
-
|
8
|
-
== Installation
|
9
|
-
|
10
|
-
=== Rails 3 or 4
|
11
|
-
|
12
|
-
If you are using Rails 3 or 4, just put hairtrigger in your Gemfile.
|
13
|
-
|
14
|
-
=== Rails 2
|
15
|
-
|
16
|
-
==== Step 1.
|
17
|
-
|
18
|
-
Put hairtrigger in your Gemfile, or if you're not using bundler, you can
|
19
|
-
"gem install hairtrigger" and then put hairtrigger in environment.rb
|
20
|
-
|
21
|
-
==== Step 2.
|
22
|
-
|
23
|
-
Create lib/tasks/hair_trigger.rake with the following:
|
24
|
-
|
25
|
-
$VERBOSE = nil
|
26
|
-
Dir["#{Gem::Specification.find_by_name('hairtrigger').full_gem_path}/lib/tasks/*.rake"].each { |ext| load ext }
|
27
|
-
|
28
|
-
This will give you the db:generate_trigger_migration task, and will ensure
|
29
|
-
that hairtrigger hooks into db:schema:dump.
|
30
|
-
|
31
|
-
If you are unpacking the gem in vendor/plugins, this step is not needed
|
32
|
-
(though you'll then want to delete its Gemfile to avoid possible conflicts).
|
33
|
-
|
34
|
-
== Usage
|
35
|
-
|
36
|
-
=== Models
|
37
|
-
|
38
|
-
Declare triggers in your models and use a rake task to auto-generate the
|
39
|
-
appropriate migration. For example:
|
40
|
-
|
41
|
-
class AccountUser < ActiveRecord::Base
|
42
|
-
trigger.after(:insert) do
|
43
|
-
"UPDATE accounts SET user_count = user_count + 1 WHERE id = NEW.account_id;"
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
and then:
|
48
|
-
|
49
|
-
rake db:generate_trigger_migration
|
50
|
-
|
51
|
-
This will create a db-agnostic migration for the trigger that mirrors the
|
52
|
-
model declaration. The end result in MySQL will be something like this:
|
53
|
-
|
54
|
-
CREATE TRIGGER account_users_after_insert_row_tr AFTER INSERT ON account_users
|
55
|
-
FOR EACH ROW
|
56
|
-
BEGIN
|
57
|
-
UPDATE accounts SET user_count = user_count + 1 WHERE id = NEW.account_id;
|
58
|
-
END
|
59
|
-
|
60
|
-
Note that these auto-generated create_trigger statements in the migration
|
61
|
-
contain the ":generated => true" option, indicating that they were created
|
62
|
-
from the model definition. This is important, as the rake task will also
|
63
|
-
generate appropriate drop/create statements for any model triggers that get
|
64
|
-
removed or updated. It does this by diffing the current model trigger
|
65
|
-
declarations and any auto-generated triggers in schema.rb (and subsequent
|
66
|
-
migrations).
|
67
|
-
|
68
|
-
=== Chainable Methods
|
69
|
-
|
70
|
-
Triggers are built by chaining several methods together, ending in a block
|
71
|
-
that specifies the SQL to be run when the trigger fires. Supported methods
|
72
|
-
include:
|
73
|
-
|
74
|
-
==== name(trigger_name)
|
75
|
-
Optional, inferred from other calls.
|
76
|
-
|
77
|
-
==== on(table_name)
|
78
|
-
Ignored in models, required in migrations.
|
79
|
-
|
80
|
-
==== for_each(item)
|
81
|
-
Defaults to :row, PostgreSQL allows :statement.
|
82
|
-
|
83
|
-
==== before(*events)
|
84
|
-
Shorthand for timing(:before).events(*events).
|
85
|
-
|
86
|
-
==== after(*events)
|
87
|
-
Shorthand for timing(:after).events(*events).
|
88
|
-
|
89
|
-
==== where(conditions)
|
90
|
-
Optional, SQL snippet limiting when the trigger will fire. Supports delayed interpolation of variables.
|
91
|
-
|
92
|
-
==== security(user)
|
93
|
-
Permissions/role to check when calling trigger. PostgreSQL supports :invoker (default) and :definer, MySQL supports :definer (default) and arbitrary users (syntax: 'user'@'host').
|
94
|
-
|
95
|
-
==== timing(timing)
|
96
|
-
Required (but may be satisified by before/after). Possible values are :before/:after.
|
97
|
-
|
98
|
-
==== events(*events)
|
99
|
-
Required (but may be satisified by before/after). Possible values are :insert/:update/:delete/:truncate. MySQL/SQLite only support one action per trigger, and don't support :truncate.
|
100
|
-
|
101
|
-
==== all
|
102
|
-
Noop, useful for trigger groups (see below).
|
103
|
-
|
104
|
-
=== Trigger Groups
|
105
|
-
|
106
|
-
Trigger groups allow you to use a slightly more concise notation if you have
|
107
|
-
several triggers that fire on a given model. This is also important for MySQL,
|
108
|
-
since it does not support multiple triggers on a table for the same action
|
109
|
-
and timing. For example:
|
110
|
-
|
111
|
-
trigger.after(:update) do |t|
|
112
|
-
t.all do # every row
|
113
|
-
# some sql
|
114
|
-
end
|
115
|
-
t.where("OLD.foo != NEW.foo") do
|
116
|
-
# some more sql
|
117
|
-
end
|
118
|
-
t.where("OLD.bar != NEW.bar") do
|
119
|
-
# some other sql
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
For MySQL, this will just create a single trigger with conditional logic
|
124
|
-
(since it doesn't support multiple triggers). PostgreSQL and SQLite will have
|
125
|
-
distinct triggers. This same notation is also used within trigger migrations.
|
126
|
-
MySQL does not currently support nested trigger groups.
|
127
|
-
|
128
|
-
=== Database-specific trigger bodies
|
129
|
-
|
130
|
-
Although HairTrigger aims to be totally db-agnostic, at times you do need a
|
131
|
-
little more control over the body of the trigger. You can tailor it for
|
132
|
-
specific databases by returning a hash rather than a string. Make sure to set
|
133
|
-
a :default value if you aren't explicitly specifying all of them.
|
134
|
-
|
135
|
-
For example, MySQL generally performs poorly with subselects in UPDATE
|
136
|
-
statements, and it has its own proprietary syntax for multi-table UPDATEs. So
|
137
|
-
you might do something like the following:
|
138
|
-
|
139
|
-
trigger.after(:insert) do
|
140
|
-
{:default => <<-DEFAULT_SQL, :mysql => <<-MYSQL}
|
141
|
-
|
142
|
-
UPDATE users SET item_count = item_count + 1
|
143
|
-
WHERE id IN (SELECT user_id FROM buckets WHERE id = NEW.bucket_id)
|
144
|
-
DEFAULT_SQL
|
145
|
-
|
146
|
-
UPDATE users, buckets SET item_count = item_count + 1
|
147
|
-
WHERE users.id = user_id AND buckets.id = NEW.bucket_id
|
148
|
-
MYSQL
|
149
|
-
end
|
150
|
-
|
151
|
-
=== Manual Migrations
|
152
|
-
|
153
|
-
You can also manage triggers manually in your migrations via create_trigger and
|
154
|
-
drop_trigger. They are a little more verbose than model triggers, and they can
|
155
|
-
be more work since you need to figure out the up/down create/drop logic when
|
156
|
-
you change things. A sample trigger:
|
157
|
-
|
158
|
-
create_trigger(:compatibility => 1).on(:users).after(:insert) do
|
159
|
-
"UPDATE accounts SET user_count = user_count + 1 WHERE id = NEW.account_id;"
|
160
|
-
end
|
161
|
-
|
162
|
-
==== Manual triggers and :compatibility
|
163
|
-
|
164
|
-
As bugs are fixed and features are implemented in hairtrigger, it's possible
|
165
|
-
that the generated trigger SQL will change (this has only happened once so
|
166
|
-
far). If you upgrade to a newer version of hairtrigger, it needs a way of
|
167
|
-
knowing which previous version generated the original trigger. You only need
|
168
|
-
to worry about this for manual trigger migrations, as the model ones
|
169
|
-
automatically take care of this. For your manual triggers you can either:
|
170
|
-
|
171
|
-
* pass ":compatibility => x" to your create_trigger statement, where x is
|
172
|
-
whatever HairTrigger::Builder.compatiblity is (1 for this version).
|
173
|
-
* set "HairTrigger::Builder.base_compatibility = x" in an initializer, where
|
174
|
-
x is whatever HairTrigger::Builder.compatiblity is. This is like doing the
|
175
|
-
first option on every create_trigger. Note that once the compatibility
|
176
|
-
changes, you'll need to set :compatibility on new triggers (unless you
|
177
|
-
just redo all your triggers and bump the base_compatibility).
|
178
|
-
|
179
|
-
If you upgrade to a newer version of hairtrigger and see that the SQL
|
180
|
-
compatibility has changed, you'll need to set the appropriate compatibility
|
181
|
-
on any new triggers that you create.
|
182
|
-
|
183
|
-
== rake db:schema:dump
|
184
|
-
|
185
|
-
HairTrigger hooks into rake db:schema:dump (and rake tasks that call it) to
|
186
|
-
make it trigger-aware. A newly generated schema.rb will contain:
|
187
|
-
|
188
|
-
* create_trigger statements for any database triggers that exactly match a
|
189
|
-
create_trigger statement in an applied migration or in the previous
|
190
|
-
schema.rb file. this includes both generated and manual create_trigger
|
191
|
-
calls.
|
192
|
-
* adapter-specific execute('CREATE TRIGGER..') statements for any unmatched
|
193
|
-
database triggers.
|
194
|
-
|
195
|
-
As long as you don't delete old migrations and schema.rb prior to running
|
196
|
-
rake db:schema:dump, the result should be what you expect (and portable).
|
197
|
-
If you have deleted all trigger migrations, you can regenerate a new
|
198
|
-
baseline for model triggers via rake db:generate_trigger_migration.
|
199
|
-
|
200
|
-
== Testing
|
201
|
-
|
202
|
-
To stay on top of things, it's strongly recommended that you add a test or
|
203
|
-
spec to ensure your migrations/schema.rb match your models. This is as simple
|
204
|
-
as:
|
205
|
-
|
206
|
-
assert HairTrigger::migrations_current?
|
207
|
-
|
208
|
-
This way you'll know if there are any outstanding migrations you need to
|
209
|
-
create.
|
210
|
-
|
211
|
-
== Warnings and Errors
|
212
|
-
|
213
|
-
There are a couple classes of errors: declaration errors and generation
|
214
|
-
errors/warnings.
|
215
|
-
|
216
|
-
Declaration errors happen if your trigger declaration is obviously wrong, and
|
217
|
-
will cause a runtime error in your model or migration class. An example would
|
218
|
-
be "trigger.after(:never)", since :never is not a valid event.
|
219
|
-
|
220
|
-
Generation errors happen if you try something that your adapter doesn't
|
221
|
-
support. An example would be something like "trigger.security(:invoker)" for
|
222
|
-
MySQL. These errors only happen when the trigger is actually generated, e.g.
|
223
|
-
when you attempt to run the migration.
|
224
|
-
|
225
|
-
Generation warnings are similar but they don't stop the trigger from being
|
226
|
-
generated. If you do something adapter-specific supported by your database,
|
227
|
-
you will still get a warning ($stderr) that your trigger is not portable. You
|
228
|
-
can silence warnings via "HairTrigger::Builder.show_warnings = false"
|
229
|
-
|
230
|
-
You can validate your triggers beforehand using the Builder#validate! method.
|
231
|
-
It will throw the appropriate errors/warnings so that you know what to fix,
|
232
|
-
e.g.
|
233
|
-
|
234
|
-
> User.triggers.each(&:validate!)
|
235
|
-
|
236
|
-
HairTrigger does not validate your SQL, so be sure to test it in all databases
|
237
|
-
you want to support.
|
238
|
-
|
239
|
-
== Gotchas
|
240
|
-
|
241
|
-
* As is the case with ActiveRecord::Base.update_all or any direct SQL you do,
|
242
|
-
be careful to reload updated objects from the database. For example, the
|
243
|
-
following code will display the wrong count since we aren't reloading the
|
244
|
-
account:
|
245
|
-
a = Account.find(123)
|
246
|
-
a.account_users.create(:name => 'bob')
|
247
|
-
puts "count is now #{a.user_count}"
|
248
|
-
* For repeated chained calls, the last one wins, there is currently no
|
249
|
-
merging.
|
250
|
-
* If you want your code to be portable, the trigger actions should be
|
251
|
-
limited to INSERT/UPDATE/DELETE/SELECT, and conditional logic should be
|
252
|
-
handled through the :where option/method. Otherwise you'll likely run into
|
253
|
-
trouble due to differences in syntax and supported features.
|
254
|
-
* Manual create_trigger statements have some gotchas. See the section
|
255
|
-
"Manual triggers and :compatibility"
|
256
|
-
|
257
|
-
== Compatibility
|
258
|
-
|
259
|
-
* Ruby 1.8.7+
|
260
|
-
* Rails 2.3+
|
261
|
-
* Postgres 8.0+
|
262
|
-
* MySQL 5.0.10+
|
263
|
-
* SQLite 3.3.8+
|
264
|
-
|
265
|
-
== Version History
|
266
|
-
|
267
|
-
* 0.2.4 Rails 4 and Ruby 2 support
|
268
|
-
* 0.2.3 Better 1.9 support, update parsing gems
|
269
|
-
* 0.2.2 PostGIS support
|
270
|
-
* 0.2.1 Bugfix for adapter-specific warnings, loosen parser dependencies
|
271
|
-
* 0.2.0 Rails 3.1+ support, easier installation, ruby 1.9 fix, travis-ci
|
272
|
-
* 0.1.14 sqlite + ruby1.9 bugfix
|
273
|
-
* 0.1.13 drop_trigger fix
|
274
|
-
* 0.1.12 DB-specific trigger body support, bugfixes
|
275
|
-
* 0.1.11 Safer migration loading, some speedups
|
276
|
-
* 0.1.10 Sped up migration evaluation
|
277
|
-
* 0.1.9 MySQL fixes for inferred root@localhost
|
278
|
-
* 0.1.7 Rails 3 support, fixed a couple manual create_trigger bugs
|
279
|
-
* 0.1.6 rake db:schema:dump support, respect non-timestamped migrations
|
280
|
-
* 0.1.4 Compatibility tracking, fixed Postgres return bug, ensure last action
|
281
|
-
has a semicolon
|
282
|
-
* 0.1.3 Better error handling, Postgres 8.x support, updated docs
|
283
|
-
* 0.1.2 Fixed Builder#security, updated docs
|
284
|
-
* 0.1.1 Fixed bug in HairTrigger.migrations_current?, fixed up Gemfile
|
285
|
-
* 0.1.0 Initial release
|
286
|
-
|
287
|
-
== Copyright
|
288
|
-
|
289
|
-
Copyright (c) 2011-2013 Jon Jensen. See LICENSE.txt for further details.
|