hairtrigger 0.2.4 → 0.2.5
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/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.
|