-
Notifications
You must be signed in to change notification settings - Fork 0
/
active_record_callbacks.html
624 lines (567 loc) · 33.2 KB
/
active_record_callbacks.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Active Record 回呼 — Ruby on Rails 指南</title>
<meta name="description" content="Ruby on Rails 指南:系統學習 Rails(Rails 4.2 版本)" >
<meta name="keywords" content="Ruby on Rails Guides 指南 中文 學習 免費 網路 Web 開發" >
<meta name="author" content="http://git.io/G_R1sA">
<meta property="fb:admins" content="1340181291">
<meta property="og:title" content="Active Record 回呼 — Ruby on Rails 指南" >
<meta property="og:site_name" content="Ruby on Rails 指南">
<meta property="og:image" content="http://rails.ruby.tw/images/rails_guides_cover.jpg">
<meta property="og:url" content="http://rails.ruby.tw/">
<meta property="og:type" content="article">
<meta property="og:description" content="Ruby on Rails 指南:系統學習 Rails(Rails 4.2 版本)">
<link rel="stylesheet" href="stylesheets/application.css">
<link href="http://fonts.googleapis.com/css?family=Noto+Sans:400,700|Noto+Serif:700|Source+Code+Pro" rel="stylesheet">
<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon">
</head>
<body class="guide">
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/zh-TW/sdk.js#xfbml=1&appId=837401439623727&version=v2.0";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
<script type="text/javascript">
window.twttr=(function(d,s,id){var t,js,fjs=d.getElementsByTagName(s)[0];if(d.getElementById(id)){return}js=d.createElement(s);js.id=id;js.src="https://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);return window.twttr||(t={_e:[],ready:function(f){t._e.push(f)}})}(document,"script","twitter-wjs"));
</script>
<div id="topNav">
<div class="wrapper">
<strong class="more-info-label">更多內容 <a href="http://rubyonrails.org/">rubyonrails.org:</a></strong>
<span class="red-button more-info-button">
更多內容
</span>
<ul class="more-info-links s-hidden">
<li class="more-info"><a href="http://rubyonrails.org/">綜覽</a></li>
<li class="more-info"><a href="http://rubyonrails.org/download">下載</a></li>
<li class="more-info"><a href="http://rubyonrails.org/deploy">部署</a></li>
<li class="more-info"><a href="https://github.com/rails/rails">原始碼</a></li>
<li class="more-info"><a href="http://rubyonrails.org/screencasts">影片</a></li>
<li class="more-info"><a href="http://rubyonrails.org/documentation">文件</a></li>
<li class="more-info"><a href="http://rubyonrails.org/community">社群</a></li>
<li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
</ul>
</div>
</div>
<div id="header">
<div class="wrapper clearfix">
<h1><a href="index.html" title="回首頁">Guides.rubyonrails.org</a></h1>
<ul class="nav">
<li><a class="nav-item" href="index.html">首頁</a></li>
<li class="guides-index guides-index-large">
<a href="index.html" id="guidesMenu" class="guides-index-item nav-item">指南目錄</a>
<div id="guides" class="clearfix" style="display: none;">
<hr>
<dl class="L">
<dt>起步走</dt>
<dd><a href="getting_started.html">Rails 起步走</a></dd>
<dt>Models</dt>
<dd><a href="active_record_basics.html">Active Record 基礎</a></dd>
<dd><a href="active_record_migrations.html">Active Record 遷移</a></dd>
<dd><a href="active_record_validations.html">Active Record 驗證</a></dd>
<dd><a href="active_record_callbacks.html">Active Record 回呼</a></dd>
<dd><a href="association_basics.html">Active Record 關聯</a></dd>
<dd><a href="active_record_querying.html">Active Record 查詢</a></dd>
<dt>Views</dt>
<dd><a href="layouts_and_rendering.html">Rails 算繪與版型</a></dd>
<dd><a href="form_helpers.html">Action View 表單輔助方法</a></dd>
<dt>Controllers</dt>
<dd><a href="action_controller_overview.html">Action Controller 綜覽</a></dd>
<dd><a href="routing.html">Rails 路由:深入淺出</a></dd>
</dl>
<dl class="R">
<dt>深入了解</dt>
<dd><a href="active_support_core_extensions.html">Active Support 核心擴展</a></dd>
<dd><a href="i18n.html">Rails 國際化 API</a></dd>
<dd><a href="action_mailer_basics.html">Action Mailer 基礎</a></dd>
<dd><a href="active_job_basics.html">Active Job 基礎</a></dd>
<dd><a href="security.html">Rails 安全指南</a></dd>
<dd><a href="debugging_rails_applications.html">除錯 Rails 應用程式</a></dd>
<dd><a href="configuring.html">Rails 應用程式設定</a></dd>
<dd><a href="command_line.html">Rake 任務與 Rails 命令列工具</a></dd>
<dd><a href="asset_pipeline.html">Asset Pipeline</a></dd>
<dd><a href="working_with_javascript_in_rails.html">在 Rails 使用 JavaScript</a></dd>
<dd><a href="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</a></dd>
<dt>擴充 Rails</dt>
<dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
<dd><a href="generators.html">客製與新建 Rails 產生器</a></dd>
<dd><a href="rails_application_templates.html">Rails 應用程式模版</a></dd>
<dt>貢獻 Ruby on Rails</dt>
<dd><a href="contributing_to_ruby_on_rails.html">貢獻 Ruby on Rails</a></dd>
<dd><a href="api_documentation_guidelines.html">API 文件準則</a></dd>
<dd><a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南準則</a></dd>
<dt>維護方針</dt>
<dd><a href="maintenance_policy.html">維護方針</a></dd>
<dt>發佈記</dt>
<dd><a href="upgrading_ruby_on_rails.html">升級 Ruby on Rails</a></dd>
<dd><a href="4_2_release_notes.html">Ruby on Rails 4.2 發佈記</a></dd>
<dd><a href="4_1_release_notes.html">Ruby on Rails 4.1 發佈記</a></dd>
<dd><a href="4_0_release_notes.html">Ruby on Rails 4.0 發佈記</a></dd>
<dd><a href="3_2_release_notes.html">Ruby on Rails 3.2 發佈記</a></dd>
<dd><a href="3_1_release_notes.html">Ruby on Rails 3.1 發佈記</a></dd>
<dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 發佈記</a></dd>
<dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 發佈記</a></dd>
<dd><a href="2_2_release_notes.html">Ruby on Rails 2.2 發佈記</a></dd>
<dt>Rails 指南翻譯術語</dt>
<dd><a href="translation_terms.html">翻譯術語</a></dd>
</dl>
</div>
</li>
<li><a class="nav-item" href="//github.com/docrails-tw/guides">貢獻翻譯</a></li>
<li><a class="nav-item" href="contributing_to_ruby_on_rails.html">貢獻</a></li>
<li><a class="nav-item" href="credits.html">致謝</a></li>
<li class="guides-index guides-index-small">
<select class="guides-index-item nav-item">
<option value="index.html">指南目錄</option>
<optgroup label="起步走">
<option value="getting_started.html">Rails 起步走</option>
</optgroup>
<optgroup label="Models">
<option value="active_record_basics.html">Active Record 基礎</option>
<option value="active_record_migrations.html">Active Record 遷移</option>
<option value="active_record_validations.html">Active Record 驗證</option>
<option value="active_record_callbacks.html">Active Record 回呼</option>
<option value="association_basics.html">Active Record 關聯</option>
<option value="active_record_querying.html">Active Record 查詢</option>
</optgroup>
<optgroup label="Views">
<option value="layouts_and_rendering.html">Rails 算繪與版型</option>
<option value="form_helpers.html">Action View 表單輔助方法</option>
</optgroup>
<optgroup label="Controllers">
<option value="action_controller_overview.html">Action Controller 綜覽</option>
<option value="routing.html">Rails 路由:深入淺出</option>
</optgroup>
<optgroup label="深入了解">
<option value="active_support_core_extensions.html">Active Support 核心擴展</option>
<option value="i18n.html">Rails 國際化 API</option>
<option value="action_mailer_basics.html">Action Mailer 基礎</option>
<option value="active_job_basics.html">Active Job 基礎</option>
<option value="security.html">Rails 安全指南</option>
<option value="debugging_rails_applications.html">除錯 Rails 應用程式</option>
<option value="configuring.html">Rails 應用程式設定</option>
<option value="command_line.html">Rake 任務與 Rails 命令列工具</option>
<option value="asset_pipeline.html">Asset Pipeline</option>
<option value="working_with_javascript_in_rails.html">在 Rails 使用 JavaScript</option>
<option value="constant_autoloading_and_reloading.html">Constant Autoloading and Reloading</option>
</optgroup>
<optgroup label="擴充 Rails">
<option value="rails_on_rack.html">Rails on Rack</option>
<option value="generators.html">客製與新建 Rails 產生器</option>
<option value="rails_application_templates.html">Rails 應用程式模版</option>
</optgroup>
<optgroup label="貢獻 Ruby on Rails">
<option value="contributing_to_ruby_on_rails.html">貢獻 Ruby on Rails</option>
<option value="api_documentation_guidelines.html">API 文件準則</option>
<option value="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南準則</option>
</optgroup>
<optgroup label="維護方針">
<option value="maintenance_policy.html">維護方針</option>
</optgroup>
<optgroup label="發佈記">
<option value="upgrading_ruby_on_rails.html">升級 Ruby on Rails</option>
<option value="4_2_release_notes.html">Ruby on Rails 4.2 發佈記</option>
<option value="4_1_release_notes.html">Ruby on Rails 4.1 發佈記</option>
<option value="4_0_release_notes.html">Ruby on Rails 4.0 發佈記</option>
<option value="3_2_release_notes.html">Ruby on Rails 3.2 發佈記</option>
<option value="3_1_release_notes.html">Ruby on Rails 3.1 發佈記</option>
<option value="3_0_release_notes.html">Ruby on Rails 3.0 發佈記</option>
<option value="2_3_release_notes.html">Ruby on Rails 2.3 發佈記</option>
<option value="2_2_release_notes.html">Ruby on Rails 2.2 發佈記</option>
</optgroup>
<optgroup label="Rails 指南翻譯術語">
<option value="translation_terms.html">翻譯術語</option>
</optgroup>
</select>
</li>
</ul>
</div>
</div>
</div>
<hr class="hide">
<div id="feature">
<div class="wrapper">
<h2>Active Record 回呼</h2><p>本篇講解如何掛載事件到 Active Record 物件的生命週期。</p><p>讀完本篇,您將了解:</p>
<ul>
<li>Active Record 物件的生命週期。</li>
<li>如何建立回呼方法回應生命週期裡的事件。</li>
<li>如何封裝回呼常見的行為到特殊的類別裡。</li>
</ul>
<div id="subCol">
<h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />Chapters</h3>
<ol class="chapters">
<li><a href="#%E7%89%A9%E4%BB%B6%E7%9A%84%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F">物件的生命週期</a></li>
<li>
<a href="#%E5%9B%9E%E5%91%BC%E7%B6%9C%E8%A6%BD">回呼綜覽</a>
<ul>
<li><a href="#%E8%A8%BB%E5%86%8A%E5%9B%9E%E5%91%BC">註冊回呼</a></li>
</ul>
</li>
<li>
<a href="#%E5%8F%AF%E7%94%A8%E7%9A%84%E5%9B%9E%E5%91%BC">可用的回呼</a>
<ul>
<li><a href="#%E6%96%B0%E5%BB%BA%E7%89%A9%E4%BB%B6">新建物件</a></li>
<li><a href="#%E6%9B%B4%E6%96%B0%E7%89%A9%E4%BB%B6">更新物件</a></li>
<li><a href="#%E5%88%AA%E9%99%A4%E7%89%A9%E4%BB%B6">刪除物件</a></li>
<li><a href="#after-initialize-%E8%88%87-after-find"><code>after_initialize</code> 與 <code>after_find</code></a></li>
<li><a href="#after-touch"><code>after_touch</code></a></li>
</ul>
</li>
<li><a href="#%E5%9F%B7%E8%A1%8C%E5%9B%9E%E5%91%BC">執行回呼</a></li>
<li><a href="#%E7%95%A5%E9%81%8E%E5%9B%9E%E5%91%BC">略過回呼</a></li>
<li><a href="#%E7%B5%82%E6%AD%A2%E5%9F%B7%E8%A1%8C">終止執行</a></li>
<li><a href="#%E9%97%9C%E8%81%AF%E5%9B%9E%E5%91%BC">關聯回呼</a></li>
<li>
<a href="#%E6%A2%9D%E4%BB%B6%E5%BC%8F%E5%9B%9E%E5%91%BC">條件式回呼</a>
<ul>
<li><a href="#%E4%BD%BF%E7%94%A8%E7%AC%A6%E8%99%9F%E6%8C%87%E5%AE%9A-if-%E8%88%87-unless">使用符號指定 <code>:if</code> 與 <code>:unless</code></a></li>
<li><a href="#%E4%BD%BF%E7%94%A8%E5%AD%97%E4%B8%B2%E6%8C%87%E5%AE%9A-if-%E8%88%87-unless">使用字串指定 <code>:if</code> 與 <code>:unless</code></a></li>
<li><a href="#%E4%BD%BF%E7%94%A8-proc-%E6%8C%87%E5%AE%9A-if-%E8%88%87-unless">使用 <code>Proc</code> 指定 <code>:if</code> 與 <code>:unless</code></a></li>
<li><a href="#%E5%A4%9A%E9%87%8D%E6%A2%9D%E4%BB%B6%E5%9B%9E%E5%91%BC">多重條件回呼</a></li>
</ul>
</li>
<li><a href="#%E5%9B%9E%E5%91%BC%E9%A1%9E%E5%88%A5">回呼類別</a></li>
<li><a href="#%E4%BA%A4%E6%98%93%E5%9B%9E%E5%91%BC">交易回呼</a></li>
</ol>
</div>
</div>
</div>
<div id="container">
<div class="wrapper">
<div id="mainCol">
<h3 id="物件的生命週期">1 物件的生命週期</h3><p>Rails 應用程式常見的操作裡,物件可以被新建、更新與刪除。Active Record 提供了掛載機制,可以掛載事件到物件的生命週期裡,用來控制應用程式與資料。</p><p>回呼允許你在物件狀態前後,觸發特定的邏輯。</p><h3 id="回呼綜覽">2 回呼綜覽</h3><p>回呼是在物件生命週期特定時間點所呼叫的方法。有了回呼便可以在 Active Record 物件,<strong>新建、儲存、更新、刪除、驗證、或從資料庫讀出</strong>前後,執行想要的邏輯。</p><h4 id="註冊回呼">2.1 註冊回呼</h4><p>需要先註冊方可使用回呼。註冊回呼可以使用一般的方法或是宏風格的方法:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class User < ActiveRecord::Base
validates :login, :email, presence: true
before_validation :ensure_login_has_a_value
protected
def ensure_login_has_a_value
if login.nil?
self.login = email unless email.blank?
end
end
end
</pre>
</div>
<p>宏風格的類別方法也接受區塊。如果回呼邏輯很短只有一行,可以考慮使用區塊形式:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class User < ActiveRecord::Base
validates :login, :email, presence: true
before_create do
self.name = login.capitalize if name.blank?
end
end
</pre>
</div>
<p>回呼也可只針對 Active Record 物件生命週期裡特定的事件觸發:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class User < ActiveRecord::Base
before_validation :normalize_name, on: :create
# :on takes an array as well
after_validation :set_location, on: [ :create, :update ]
protected
def normalize_name
self.name = self.name.downcase.titleize
end
def set_location
self.location = LocationService.query(self)
end
end
</pre>
</div>
<p>通常會把回呼方法宣告為 <code>protected</code> 或 <code>private</code> 方法。若是 <code>public</code> 方法,有可能會在 Model 外被呼叫,則違反了物件封裝的精神。</p><h3 id="可用的回呼">3 可用的回呼</h3><p>以下是 Active Record 可用的回呼,<strong>依照執行順序排序</strong>:</p><h4 id="新建物件">3.1 新建物件</h4>
<ul>
<li><code>before_validation</code></li>
<li><code>after_validation</code></li>
<li><code>before_save</code></li>
<li><code>around_save</code></li>
<li><code>before_create</code></li>
<li><code>around_create</code></li>
<li><code>after_create</code></li>
<li><code>after_save</code></li>
<li><code>after_commit/after_rollback</code></li>
</ul>
<h4 id="更新物件">3.2 更新物件</h4>
<ul>
<li><code>before_validation</code></li>
<li><code>after_validation</code></li>
<li><code>before_save</code></li>
<li><code>around_save</code></li>
<li><code>before_update</code></li>
<li><code>around_update</code></li>
<li><code>after_update</code></li>
<li><code>after_save</code></li>
<li><code>after_commit/after_rollback</code></li>
</ul>
<h4 id="刪除物件">3.3 刪除物件</h4>
<ul>
<li><code>before_destroy</code></li>
<li><code>around_destroy</code></li>
<li><code>after_destroy</code></li>
<li><code>after_commit/after_rollback</code></li>
</ul>
<div class="warning"><p><code>after_save</code> 在 <code>create</code> 與 <code>update</code> 都會執行。但不論回呼註冊的順序為何,<code>after_save</code> 總是在更為具體的 <code>after_create</code> 與 <code>after_update</code> 之後執行。</p></div><h4 id="after-initialize-與-after-find">3.4 <code>after_initialize</code> 與 <code>after_find</code>
</h4><p>不管是實體化 Active Record 物件,還是從資料庫裡讀出記錄時,都會呼叫 <code>after_initialize</code>。使用 <code>after_initialize</code> 比覆蓋 Active Record 的 <code>initialize</code> 方法好多了。</p><p>無論何時從資料庫取出 Active Record 物件時,如果同時使用了 <code>after_find</code> 與 <code>after_initialize</code>,會先呼叫 <code>after_find</code>。</p><p><code>after_initialize</code> 與 <code>after_find</code> 沒有對應的 <code>before_*</code>。<code>after_initialize</code> 與 <code>after_find</code> 註冊的方法與一般回呼相同。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class User < ActiveRecord::Base
after_initialize do |user|
puts "You have initialized an object!"
end
after_find do |user|
puts "You have found an object!"
end
end
>> User.new
You have initialized an object!
=> #<User id: nil>
>> User.first
You have found an object!
You have initialized an object!
=> #<User id: 1>
</pre>
</div>
<h4 id="after-touch">3.5 <code>after_touch</code>
</h4><p><code>after_touch</code> 回呼會在 Active Record 執行完 <code>touch</code> 之後呼叫。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class User < ActiveRecord::Base
after_touch do |user|
puts "You have touched an object"
end
end
>> u = User.create(name: 'Kuldeep')
=> #<User id: 1, name: "Kuldeep", created_at: "2013-11-25 12:17:49", updated_at: "2013-11-25 12:17:49">
>> u.touch
You have touched an object
=> true
</pre>
</div>
<p>可與 <code>belongs_to</code> 搭配使用:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Employee < ActiveRecord::Base
belongs_to :company, touch: true
after_touch do
puts 'An Employee was touched'
end
end
class Company < ActiveRecord::Base
has_many :employees
after_touch :log_when_employees_or_company_touched
private
def log_when_employees_or_company_touched
puts 'Employee/Company was touched'
end
end
>> @employee = Employee.last
=> #<Employee id: 1, company_id: 1, created_at: "2013-11-25 17:04:22", updated_at: "2013-11-25 17:05:05">
# triggers @employee.company.touch
>> @employee.touch
Employee/Company was touched
An Employee was touched
=> true
</pre>
</div>
<h3 id="執行回呼">4 執行回呼</h3><p>以下方法會觸發回呼:</p>
<ul>
<li><code>create</code></li>
<li><code>create!</code></li>
<li><code>decrement!</code></li>
<li><code>destroy</code></li>
<li><code>destroy!</code></li>
<li><code>destroy_all</code></li>
<li><code>increment!</code></li>
<li><code>save</code></li>
<li><code>save!</code></li>
<li><code>save(validate: false)</code></li>
<li><code>toggle!</code></li>
<li><code>touch</code></li>
<li><code>update_attribute</code></li>
<li><code>update</code></li>
<li><code>update!</code></li>
<li><code>valid?</code></li>
</ul>
<p>另外 <code>after_find</code> 由下列查詢方法觸發:</p>
<ul>
<li><code>all</code></li>
<li><code>first</code></li>
<li><code>find</code></li>
<li><code>find_by</code></li>
<li><code>find_by_*</code></li>
<li><code>find_by_*!</code></li>
<li><code>find_by_sql</code></li>
<li><code>last</code></li>
</ul>
<p><code>after_initialize</code> 在每次 Active Record 物件實體化時觸發。</p><div class="note"><p>這些查詢方法是 Active Record 給每個屬性動態產生的,參見<a href="/active_record_querying.html#%E5%8B%95%E6%85%8B%E6%9F%A5%E8%A9%A2%E6%96%B9%E6%B3%95">動態查詢方法</a> 一節。</p></div><h3 id="略過回呼">5 略過回呼</h3><p>驗證可以略過,回呼同樣也可以。使用下列方法來略過回呼:</p>
<ul>
<li><code>decrement</code></li>
<li><code>decrement_counter</code></li>
<li><code>delete</code></li>
<li><code>delete_all</code></li>
<li><code>increment</code></li>
<li><code>increment_counter</code></li>
<li><code>toggle</code></li>
<li><code>update_column</code></li>
<li><code>update_columns</code></li>
<li><code>update_all</code></li>
<li><code>update_counters</code></li>
</ul>
<p>小心使用這些方法,因為回呼裡可能有重要的業務邏輯。沒弄懂回呼的用途,便直接跳過可能會導致存入不合法的資料。</p><h3 id="終止執行">6 終止執行</h3><p>為 Model 註冊新的回呼時,回呼便會加入佇列裡等待執行。這個佇列包含了所有需要執行的驗證、回呼以及資料庫操作。</p><p>整條回呼鏈(Callback Chain)被包在一筆交易(Transaction)裡。如果有任何的 <code>before_*</code> 回呼方法回傳 <code>false</code> 或拋出異常,則執行鏈會被終止,並回滾取消此次交易。而 <code>after_*</code> 回呼則需要拋出異常才可取消交易。</p><div class="warning"><p>即便回呼鏈已終止,任何非 <code>ActiveRecord::Rollback</code> 的異常會在回呼鏈終止時被 Rails 重複拋出。拋出非 <code>ActiveRecord::Rollback</code> 可能會導致不期望收到異常的方法像是 <code>save</code> 與 <code>update</code> 執行異常(通常會回傳 <code>true</code> 或 <code>false</code>)。</p></div><h3 id="關聯回呼">7 關聯回呼</h3><p>回呼也可穿透 Model 之間的關係,甚至可以透過關聯來定義。舉個例子,假設使用者有許多文章,使用者的文章應在刪除使用者時一併刪除。可以在與 <code>User</code> Model 相關聯的 <code>Article</code> Model 裡加入 <code>after_destroy</code> 回呼:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class User < ActiveRecord::Base
has_many :articles, dependent: :destroy
end
class Article < ActiveRecord::Base
after_destroy :log_destroy_action
def log_destroy_action
puts 'Article also destroyed'
end
end
>> user = User.first
=> #<User id: 1>
>> user.posts.create!
=> #<Article id: 1, user_id: 1>
>> user.destroy
Article destroyed
=> #<User id: 1>
</pre>
</div>
<h3 id="條件式回呼">8 條件式回呼</h3><p>回呼和驗證一樣,也可以在滿足給定條件時才執行。條件透過 <code>:if</code>、<code>:unless</code> 選項指定,接受 <code>Symbol</code>、<code>String</code>、<code>Proc</code> 或 <code>Array</code>。當回呼滿足某條件則執行時,請用 <code>:if</code>;回呼不滿足某條件則執行時,請用 <code>:unless</code>。</p><h4 id="使用符號指定-if-與-unless">8.1 使用符號指定 <code>:if</code> 與 <code>:unless</code>
</h4><p><code>:if</code> 與 <code>:unless</code> 選項傳入符號(Symbol)時,符號代表執行回呼前,所要呼叫的謂詞方法名稱。當使用 <code>:if</code> 選項時,若謂詞方法回傳 <code>false</code>,則不會執行回呼;當使用 <code>:unless</code> 選項時,則是 <code>true</code> 不會執行回呼。使用符號是最常見。這種註冊回呼的方式,還可以使用多個謂詞方法來決定是否要執行回呼。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
before_save :normalize_card_number, if: :paid_with_card?
end
</pre>
</div>
<h4 id="使用字串指定-if-與-unless">8.2 使用字串指定 <code>:if</code> 與 <code>:unless</code>
</h4><p>傳入的字串將會使用 <code>eval</code> 求值,所以字串必須是合法的 Ruby 程式碼。應該只在條件夠簡短的情況下再使用字串:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
before_save :normalize_card_number, if: "paid_with_card?"
end
</pre>
</div>
<h4 id="使用-proc-指定-if-與-unless">8.3 使用 <code>Proc</code> 指定 <code>:if</code> 與 <code>:unless</code>
</h4><p>最後,也可以使用 <code>Proc</code> 物件來指定 <code>:if</code> 與 <code>:unless</code>,適合撰寫簡短驗證方法的場景下使用,通常是單行:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Order < ActiveRecord::Base
before_save :normalize_card_number,
if: Proc.new { |order| order.paid_with_card? }
end
</pre>
</div>
<h4 id="多重條件回呼">8.4 多重條件回呼</h4><p>撰寫條件式回呼時,<code>:if</code> 與 <code>:unless</code> 也可混用在同個回呼裡:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Comment < ActiveRecord::Base
after_create :send_email_to_author, if: :author_wants_emails?,
unless: Proc.new { |comment| comment.article.ignore_comments? }
end
</pre>
</div>
<h3 id="回呼類別">9 回呼類別</h3><p>若某個回呼別的 Model 也可重複使用,此時便可把回呼封裝成類別。Active Record 使封裝回呼方法到類別裡格外簡單,重用便更容易了。</p><p>以下是個範例。我們建立一個 <code>PictureFile</code> Model,並註冊一個 <code>after_destroy</code> 回呼:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class PictureFileCallbacks
def after_destroy(picture_file)
if File.exists?(picture_file.filepath)
File.delete(picture_file.filepath)
end
end
end
</pre>
</div>
<p>回呼在類別裡宣告時(如上),回呼方法會收到 Model 實體(<code>picture_file</code>)作為參數。回呼類別在 Model 裡的使用方式如下:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class PictureFile < ActiveRecord::Base
after_destroy PictureFileCallbacks.new
end
</pre>
</div>
<p>注意我們需要建立一個新的 <code>PictureFileCallbacks</code> 實體,因為回呼寫在 <code>PictureFileCallbacks</code> 類裡是實體方法。這在回呼使用到了實體變數的場景下特別有用。但通常回呼宣告成類別方法更合理:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class PictureFileCallbacks
def self.after_destroy(picture_file)
if File.exists?(picture_file.filepath)
File.delete(picture_file.filepath)
end
end
end
</pre>
</div>
<p>若回呼方法如此定義,則使用時便不用實體化 <code>PictureFileCallbacks</code> 了:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class PictureFile < ActiveRecord::Base
after_destroy PictureFileCallbacks
end
</pre>
</div>
<p>回呼類別裡可宣告任意數量個回呼方法。</p><h3 id="交易回呼">10 交易回呼</h3><p>完成資料庫交易操作時會觸發兩個條件式回呼:<code>after_commit</code> 與 <code>after_rollback</code>。它們與 <code>after_save</code> 回呼非常類似,不同點在於 <code>after_commit</code> 是提交到資料庫後才執行,而 <code>after_rollback</code> 則是在資料庫回滾後執行。當 Active Record Model 需要與資料庫交易之外的外部系統互動時,這兩個回呼非常有用。</p><p>舉個例子,上例 <code>PictureFile</code> Model 需要在某個特定記錄刪除後,刪除一個檔案。若 <code>after_destroy</code> 拋出任何異常,則交易取消。但檔案卻被刪除了,Model 會處於一種不一致的狀態。舉例來說,假設下例的 <code>picture_file_2</code> 不是合法的檔案,<code>save!</code> 會拋出一個錯誤。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
PictureFile.transaction do
picture_file_1.destroy
picture_file_2.save!
end
</pre>
</div>
<p>使用 <code>after_commit</code> 回呼便可以解決這個問題。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class PictureFile < ActiveRecord::Base
after_commit :delete_picture_file_from_disk, on: [:destroy]
def delete_picture_file_from_disk
if File.exist?(filepath)
File.delete(filepath)
end
end
end
</pre>
</div>
<div class="note"><p><code>:on</code> 選項指定何時觸發這個回呼。沒指定時對所有方法都會觸發。</p></div><div class="warning"><p><code>after_commit</code> 與 <code>after_rollback</code> 在新建、更新、刪除 Model 時一定會執行。如果 <code>after_commit</code> 或 <code>after_rollback</code> 回呼其中一個拋出異常時,異常會被忽略,來確保彼此不會互相干擾。也是因為如此,如果回呼會拋出異常,記得自己 <code>rescue</code> 回來,並在回呼做適當的處理。</p></div>
<h3>反饋</h3>
<p>
歡迎幫忙改善指南的品質。
</p>
<p>
如發現任何錯誤之處,歡迎修正。開始貢獻前,可以先閱讀<a href="http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation">貢獻指南:文件</a>。
</p>
<p>翻譯如有錯誤,深感抱歉,歡迎 <a href="https://github.com/docrails-tw/guides/fork">Fork</a> 修正,或至此處<a href="https://github.com/docsrails-tw/guides/issues/new">回報</a>。</p>
<p>
文章可能有未完成或過時的內容。請先檢查 <a href="http://edgeguides.rubyonrails.org">Edge Guides</a> 來確定問題在 master 是否已經修掉了。再上 master 補上缺少的文件。內容參考 <a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南準則</a>來了解行文風格。
</p>
<p>最後,任何關於 Ruby on Rails 文件的討論,歡迎至 <a href="http://groups.google.com/group/rubyonrails-docs">rubyonrails-docs 郵件論壇</a>。
</p>
</div>
</div>
</div>
<hr class="hide">
<div id="footer">
<div class="wrapper">
<p>本著作係採用<a href="https://creativecommons.org/licenses/by-sa/4.0/deed.zh_TW">創用 CC 姓名標示-相同方式分享 4.0 國際授權條款</a>授權。</p>
<p>“Rails”、“Ruby on Rails”,以及 Rails logo 為 David Heinemeier Hansson 的商標。版權所有。</p>
</div>
</div>
<script src="javascripts/jquery.min.js"></script>
<script src="javascripts/responsive-tables.js"></script>
<script src="javascripts/guides.js"></script>
<script src="javascripts/syntaxhighlighter/shCore.js"></script>
<script src="javascripts/syntaxhighlighter/shBrushRuby.js"></script>
<script src="javascripts/syntaxhighlighter/shBrushXml.js"></script>
<script src="javascripts/syntaxhighlighter/shBrushSql.js"></script>
<script src="javascripts/syntaxhighlighter/shBrushPlain.js"></script>
<script type="text/javascript">
SyntaxHighlighter.all();
$(guidesIndex.bind);
</script>
<script>
(function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,"script","//www.google-analytics.com/analytics.js","ga");
ga("create", "UA-49903900-1", "auto");
ga("require", "displayfeatures");
ga("send", "pageview");
</script>
</body>
</html>