更新処理についても複数の方法が用意されています。まず、内部的な処理の違いで、以下の2系統に分類することができます。
また、1レコードを更新することに特化しているか、そうでないかでも区別できます。これらを踏まえて更新系メソッドを表に整理すると次のようになります。
(内部的に)ActiveRecordオブジェクトを介する更新 | ActiveRecordオブジェクトを介さない更新 | |
---|---|---|
1レコードを対象とする | (1) instance.save instance.save! instance.update_attribute instance.update_attributes instance.update_attributes! |
|
複数レコードを対象とする | (2) ModelClass.update | (3) ModelClass.update_all |
表2 更新系メソッドの分類 |
ActiveRecordオブジェクトを介する更新では、コールバックを通り、基本的に検証が行われるという特徴があります。大量のオブジェクトに対して一気に使うと、処理速度が遅くなる傾向があります。
他方、ActiveRecordオブジェクトを介さない更新では、検証やコールバックの恩恵を受けられないものの、処理効率が良いという特徴があります。
(1)(2)のメソッド群では、更新処理は基本的に次のステップで行われます。
これらのステップをどこまで同時にやってくれるかという観点でも、(1)(2)のメソッド群を整理することができます。
3のみ | 2、3のみ | 1、2、3すべて | |
---|---|---|---|
1レコードを対象とする | (1)-A instance.save instance.save! |
(1)-B instance.update_attributes instance.update_attributes! instance.update_attribute |
|
複数レコードを対象とする | (2) ModelClass.update | ||
表3 ActiveRecordオブジェクトを介する更新系メソッドの細分類 |
それではまず、基本の形である(1)-Aを見ていきましょう。DBを検索して対象となるActiveRecordオブジェクトを取得し、そのオブジェクトに任意の変更を加えてからsave/save!を実行します。
登録のときと同様に、更新が失敗した場合はsaveはfalseを返し、save!は例外を発生させます。
save/save!を使う場合、ActiveRecordオブジェクトの属性を変更する処理を、あらかじめ別に行う必要があります。これを同時に行える便利なメソッド群が、(1)-Bのupdate_attributes/update_attributes!、update_attributeです。
update_attributesは次のように使います。
user = User.where(:name => "yuki.torii").first user.update_attributes!(:email => 'xxx2@everyleaf.com', :name => 'yuki.yotii')
先ほどの例よりも少し簡潔にすることができました。これまでの例と同様、update_attributesは更新に失敗した場合はfalseを返し、update_attributes!は例外を発生させます。
update_attributesは便利ですが、1つの属性を変えたいだけであれば、少し冗長な表現といえます。そこで、1つの属性の変更に特化したメソッドが、update_attributeです。次のように使うことができます。
user = User.where(:name => "yuki.torii").first user.update_attribute(:email, 'xxx2@everyleaf.com')
update_attributeも更新の成否をtrue/falseで返しますが、大きな特徴として、内部では検証処理を行いません(そのほかのコールバックは通ります)。そのため、基本的にはtrueが返ってくることを期待できます。
さて、これまでに紹介したメソッドではいずれも、更新対象のオブジェクトをまず取得しておく必要がありました。この1つ目のステップまでも一括で行ってくれるメソッドがupdateです(ただし、筆者の経験でいうと、比較的利用されることの少ないメソッドのように見えます)。
updateメソッドには、更新したいレコードのidと、更新内容のハッシュを指定します。
User.update(2, :email =>'xxx2@everyleaf.com' , :name => 'yuki.yotii')
複数のレコードを更新するには、idと更新内容をそれぞれ配列で指定します。
User.update([2,4], [{:email => 'test@example.com'}),{:email => 'test2@examlple.com'}])
updateは、内部で取得したActiveRecordオブジェクト(複数の場合は配列)を返します。更新に本当に成功したかどうかを調べるには返ってきたオブジェクトを調べる必要があるので、どちらかといえば、検証エラーが起きないと想定している場合に使うのに向いています。
これまで解説したメソッドは、いずれも、明示的もしくは内部的に、ActiveRecordオブジェクトを介して更新を行うものでした。しかし、ActiveRecordオブジェクトを介さないメソッドもあります。これが最後に紹介するupdate_allです。
update_allは、SQLのupdate文に近いものとイメージしてもらえばよいでしょう。次のように、更新内容と更新条件とを引数に指定します。
User.update_all [‘new_commer = ?', false], ['created_at > ?', 1.week.ago]
上記の例では、一週間以上前に登録されたユーザーのnew_commerフラグをfalseに更新しています。
update_allにはオプションを渡すこともできます。:limitと:orderを指定できます。
上記の例は、emailが条件に合うもののうち、登録日が古いものを古い順に3件だけ更新します。
update_allはActiveRecordオブジェクトを介さないため、検証や、コールバックを通りません。そのため、これらを期待するようなシーンでは使えません。しかし一方で、不要なコールバックのために速度が遅くなったり、更新が失敗することを心配しなくてよい利点があります。「とにかく強制的にDBの一部を更新したい。連動して何かしてくれなくて構わない」という場合には、便利に使うことができます。
削除系のメソッドは、更新系と似たような分類の仕方ができます。
(内部的に)ActiveRecordオブジェクトを介する削除 <destroy系> |
ActiveRecordオブジェクトを介さない削除 <delete系> |
|
---|---|---|
1レコードを対象とする | (1) instance.destroy | |
複数レコードを対象とする | (2) ModelClass.destroy_all | (3) ModelClass.delete ModelClass.delete_all |
表4 削除系メソッドの分類 |
削除では、ActiveRecordオブジェクトを介するかどうかで用語が区別されています。ActiveRecordオブジェクトを介する削除にはdestroyという動詞が、そうでない削除にはdeleteという動詞が使われます。
ActiveRecordオブジェクトを介する削除(表4の(1)(2))では、削除するレコードに対応するActiveRecordオブジェクトをまず取得して、それを通じて削除処理を実行します。そのため、削除関係のコールバック処理(次回に解説します)を通ります。ActiveRecordには、「あるモデルのオブジェクトが削除されたら、関連するオブジェクトも削除する」機能がありますが(詳しくは関連についての記事で解説します)、これもコールバックによって実現されているので、destroy系のメソッドとともに動作します。
削除でもっとも基本的な形となるのが(1)のdestroyです。次のように使用します。
user = User.where(:name => 'yuki.torii').first user.destroy
あらかじめ対象となるオブジェクトを取得してからdestroyを呼びます。
これに対して、オブジェクトの取得と削除を一気に行うことができるのが(2)のdesrtoy_allです。引数に、削除対象を取得するための条件を指定します。
User.destroy_all("name = 'yuki.torii'")
条件を指定しなければ、全件の削除を行うことができます。
User.destroy_all
destroy_allは一見シンプルに見えますが、内部ではオブジェクト生成、コールバックの実行、削除を、該当レコード全てに対して繰り返します。そのため、多量のデータを一気に削除するときは、実行時間が長くなる危険性があります。
ActiveRecordオブジェクトを介する削除に対して、表4の(3)のdeletおよびdelete_allメソッドは、ActiveRecordを介さずに削除を行います。
delete_allはActiveRecordを介さずに削除を行うもので、いわば、SQLのdelete文に近いイメージとなります。使い方はdestroy_allとほぼ同様です。削除する条件は次のように指定します。
User.delete_all("name = 'yuki.torii'")
条件を指定しないと全件の削除を行います。
User.delete_all
delete_allは処理効率がよいですが、コールバックを実行しないため、ActiveRecordに定義してあったモデル間の制約を壊してしまうなどの危険性があります。その点に留意して使うと良いでしょう。
deleteは、ほぼdelete_allと同様ですが、idを条件とする場合に特化したメソッドです。例えば、特定のidのレコードを、ActiveRecordオブジェクトを介さずに削除するには次のようにします。
User.delete(4)
また、引数として配列を渡すことで、複数のレコードを削除することもできます。
User.delete([4,6])
Railsがデフォルトで用意している削除系メソッドは全て物理削除(本当にRDBからレコードを削除する)を行いますが、アプリケーションの目的によっては、論理削除を実装したい場合もあります。論理削除とは、レコードに「削除されているかどうかのフラグ」などを持ち、その有無で、有効なレコードがどうかを区別して利用する方式のことです。実際にRDBから削除してしまうわけではないので、削除を取り消したり、過去のデータから何か必要な情報を取り出すといったことがしやすくなります。
論理削除を実装するのには、もちろんRailsの素の機能を使って削除フラグなどを自分で更新してもよいのですが、この場合は意味としては削除なのにコードではdestroyではなくsaveやupdate_attributeを使うことになり、プログラムが分かりにくくなってしまいます。
その点、rails3_acts_as_paranoid(rails2用はacts_as_paranoid)というプラグインを使うと、簡単に論理削除を実現できます。
使用方法は、rails3_acts_as_paranoidをインストールした後、論理削除したいモデルに
class MyModel < ActiveRecord::Base acts_as_paranoid end
と記述し、該当テーブルに”deleted_at”というdatetime型のカラムを追加するだけです。
acts_as_paranoidと記述したモデルでは、destroyメソッドは、データベースからデータを削除するのではなく、deleted_atに削除時間を登録するようになります。deleted_atの値がnil(DB上ではnull)のものは未削除、値が入っているものは削除済みと判断されます。検索時は、基本的に論理削除されていないレコードを検索対象とするようになります。
また、acts_as_paranoidモデルではRailsの削除系のメソッドが論理削除用のメソッドに変化するため、destory!、delete_all!といった、物理削除用のメソッドが使えるようになります。このほか、論理削除されたレコードを参照したり、論理削除からリカバリーするメソッドも提供されており、論理削除を実装する場合には大変便利なプラグインといえます。
今回はここまでです。次回は検証と国際化などを解説したいと思います。
Copyright © ITmedia, Inc. All Rights Reserved.