-
Notifications
You must be signed in to change notification settings - Fork 2
/
active_record_callbacks.html
595 lines (539 loc) · 30.8 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
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Active Record 回调 — Ruby on Rails 指南</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" />
<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
</head>
<body class="guide">
<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>模型</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>视图</dt>
<dd><a href="layouts_and_rendering.html">Rails 布局和视图渲染</a></dd>
<dd><a href="form_helpers.html">Action View 表单帮助方法</a></dd>
<dt>控制器</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">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>
</dl>
</div>
</li>
<!-- <li><a class="nav-item" href="//github.com/docrails-tw/wiki">参与翻译</a></li> -->
<li><a class="nav-item" href="https://github.com/ruby-china/guides/blob/master/CONTRIBUTING.md">贡献</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="模型">
<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="视图">
<option value="layouts_and_rendering.html">Rails 布局和视图渲染</option>
<option value="form_helpers.html">Action View 表单帮助方法</option>
</optgroup>
<optgroup label="控制器">
<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">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>
</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="#%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F">对象的生命周期</a></li>
<li>
<a href="#%E5%9B%9E%E8%B0%83%E7%AE%80%E4%BB%8B">回调简介</a>
<ul>
<li><a href="#%E6%B3%A8%E5%86%8C%E5%9B%9E%E8%B0%83">注册回调</a></li>
</ul>
</li>
<li>
<a href="#%E5%8F%AF%E7%94%A8%E7%9A%84%E5%9B%9E%E8%B0%83">可用的回调</a>
<ul>
<li><a href="#%E5%88%9B%E5%BB%BA%E5%AF%B9%E8%B1%A1">创建对象</a></li>
<li><a href="#%E6%9B%B4%E6%96%B0%E5%AF%B9%E8%B1%A1">更新对象</a></li>
<li><a href="#%E9%94%80%E6%AF%81%E5%AF%B9%E8%B1%A1">销毁对象</a></li>
<li><a href="#after_initialize-%E5%92%8C-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="#%E6%89%A7%E8%A1%8C%E5%9B%9E%E8%B0%83">执行回调</a></li>
<li><a href="#%E8%B7%B3%E8%BF%87%E5%9B%9E%E8%B0%83">跳过回调</a></li>
<li><a href="#%E7%BB%88%E6%AD%A2%E6%89%A7%E8%A1%8C">终止执行</a></li>
<li><a href="#%E5%85%B3%E8%81%94%E5%9B%9E%E8%B0%83">关联回调</a></li>
<li>
<a href="#%E6%9D%A1%E4%BB%B6%E5%9B%9E%E8%B0%83">条件回调</a>
<ul>
<li><a href="#%E4%BD%BF%E7%94%A8-symbol">使用 Symbol</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8%E5%AD%97%E7%AC%A6%E4%B8%B2">使用字符串</a></li>
<li><a href="#%E4%BD%BF%E7%94%A8-proc">使用 Proc</a></li>
<li><a href="#%E5%9B%9E%E8%B0%83%E7%9A%84%E5%A4%9A%E9%87%8D%E6%9D%A1%E4%BB%B6">回调的多重条件</a></li>
</ul>
</li>
<li><a href="#%E5%9B%9E%E8%B0%83%E7%B1%BB">回调类</a></li>
<li><a href="#%E4%BA%8B%E5%8A%A1%E5%9B%9E%E8%B0%83">事务回调</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 对象创建、保存、更新、删除、验证或从数据库中读出时执行。</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>注册回调时可以指定只在对象生命周期的特定事件发生时执行:</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>一般情况下,都把回调方法定义为受保护的方法或私有方法。如果定义成公共方法,回调就可以在模型外部调用,违背了对象封装原则。</p><h3 id="可用的回调">3 可用的回调</h3><p>下面列出了所有可用的 Active Record 回调,按照执行各操作时触发的顺序:</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>
</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>
</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>
</ul>
<div class="warning"><p>创建和更新对象时都会触发 <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><code>after_initialize</code> 回调在 Active Record 对象初始化时执行,包括直接使用 <code>new</code> 方法初始化和从数据库中读取记录。<code>after_initialize</code> 回调不用直接重定义 Active Record 的 <code>initialize</code> 方法。</p><p><code>after_find</code> 回调在从数据库中读取记录时执行。如果同时注册了 <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> 回调,但可以像其他回调一样注册。</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 对象时执行。</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>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> 回调在新对象初始化时触发执行。</p><div class="note"><p><code>find_by_*</code> 和 <code>find_by_*!</code> 是为每个属性生成的动态查询方法,详情参见“<a href="active_record_querying.html#dynamic-finders">动态查询方法</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>touch</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>在模型中注册回调后,回调会加入一个执行队列。这个队列中包含模型的数据验证,注册的回调,以及要执行的数据库操作。</p><p>整个回调链包含在一个事务中。如果任何一个 <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_attributes</code>,应该返回 <code>true</code> 或 <code>false</code>)无法执行。</p></div><h3 id="关联回调">7 关联回调</h3><p>回调能在模型关联中使用,甚至可由关联定义。假如一个用户发布了多篇文章,如果用户删除了,他发布的文章也应该删除。下面我们在 <code>Post</code> 模型中注册一个 <code>after_destroy</code> 回调,应用到 <code>User</code> 模型上:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
class Post < ActiveRecord::Base
after_destroy :log_destroy_action
def log_destroy_action
puts 'Post destroyed'
end
end
>> user = User.first
=> #<User id: 1>
>> user.posts.create!
=> #<Post id: 1, user_id: 1>
>> user.destroy
Post destroyed
=> #<User id: 1>
</pre>
</div>
<h3 id="条件回调">8 条件回调</h3><p>和数据验证类似,也可以在满足指定条件时再调用回调方法。条件通过 <code>:if</code> 和 <code>:unless</code> 选项指定,选项的值可以是 Symbol、字符串、<code>Proc</code> 或数组。<code>:if</code> 选项指定什么时候调用回调。如果要指定何时不调用回调,使用 <code>:unless</code> 选项。</p><h4 id="使用-symbol">8.1 使用 Symbol</h4><p>:if 和 :unless 选项的值为 Symbol 时,表示要在调用回调之前执行对应的判断方法。使用 <code>:if</code> 选项时,如果判断方法返回 <code>false</code>,就不会调用回调;使用 <code>:unless</code> 选项时,如果判断方法返回 <code>true</code>,就不会调用回调。Symbol 是最常用的设置方式。使用这种方式注册回调时,可以使用多个判断方法检查是否要调用回调。</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="使用字符串">8.2 使用字符串</h4><p><code>:if</code> 和 <code>:unless</code> 选项的值还可以是字符串,但必须是 RUby 代码,传入 <code>eval</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="使用-proc">8.3 使用 Proc</h4><p><code>:if</code> 和 <code>:unless</code> 选项的值还可以是 Proc 对象。这种形式最适合用在一行代码能表示的条件上。</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.post.ignore_comments? }
end
</pre>
</div>
<h3 id="回调类">9 回调类</h3><p>有时回调方法可以在其他模型中重用,我们可以将其封装在类中。</p><p>在下面这个例子中,我们为 <code>PictureFile</code> 模型定义了一个 <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.exist?(picture_file.filepath)
File.delete(picture_file.filepath)
end
end
end
</pre>
</div>
<p>在类中定义回调方法时(如上),可把模型对象作为参数传入。然后可以在模型中使用这个回调:</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>。如果回调要使用实例化对象的状态,使用这种定义方式很有用。不过,一般情况下,定义为类方法更说得通:</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class PictureFileCallbacks
def self.after_destroy(picture_file)
if File.exist?(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> 很像,只不过在数据库操作提交或回滚之前不会执行。如果模型要和数据库事务之外的系统交互,就可以使用这两个回调。</p><p>例如,在前面的例子中,<code>PictureFile</code> 模型中的记录删除后,还要删除相应的文件。如果执行 <code>after_destroy</code> 回调之后程序抛出了异常,事务就会回滚,文件会被删除,但模型的状态前后不一致。假设在下面的代码中,<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> 选项指定什么时候出发回调。如果不设置 <code>:on</code> 选项,每各个操作都会触发回调。</p></div><div class="warning"><p><code>after_commit</code> 和 <code>after_rollback</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/ruby-china/guides/fork">Fork</a> 修正,或至此处<a href="https://github.com/ruby-china/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/">创用 CC 姓名标示-相同方式分享 4.0 国际授权条款</a>授权。</p>
<p>“Rails”、“Ruby on Rails”,以及 Rails logo 为 David Heinemeier Hansson 的商标。版权所有。</p>
</div>
</div>
<script type="text/javascript" src="javascripts/jquery.min.js"></script>
<script type="text/javascript" src="javascripts/responsive-tables.js"></script>
<script type="text/javascript" src="javascripts/guides.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shCore.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushRuby.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushXml.js"></script>
<script type="text/javascript" src="javascripts/syntaxhighlighter/shBrushSql.js"></script>
<script type="text/javascript" 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', '', 'ruby-china.github.io');
ga('require', 'displayfeatures');
ga('send', 'pageview');
</script>
</body>
</html>