モデルの更新系

  • 保存はsaveね!
    • この辺は、Scaffoldingのnew/create(登録)辺りでやってるか。
      • 要するに、オブジェクトをIDを作成もしくは取得したオブジェクトを更新して、saveで単一レコードの更新/登録をするのねー。
    • で、createメソッドで書いてたコードだと、単にパラメタの:bookをまとめて渡してたけど、当然1つづつでも書ける
      • createメソッド側では、こう
         def create
           @book = Book.new(params[:book])
           respond_to do |format|
             if @book.save
             …省略…
           end
         end
        
      • 各パラメータを書くなら、こんな感じ
         def create
           @book = Book.new
           @book.isbn = params[:book][:isbn]
           @book.title = params[:book][:title]
           …省略…
           respond_to do |format|
             if @book.save
             …省略…
           end
         end
        
      • ?なんか、日付型、更新されんな…。なんじゃろ?
      • 当然、updateでも同様ね。
    • あ、ちなみにメソッド名を「update!」とかにすると「更新が成功することを信じている」ことになるので、いきなり例外が出るようになるよー
  • attributes/update_attributeでフィールド毎の更新もできるけど、全てのカラムを更新しちゃうからセキュリティ的にまずい
  • attr_protectedなら、一括更新時に 保護したいフィールド を指定できる
    • Userクラスで、rolesフィールドを保護する場合、こんな感じ
      class User < ActiveRecord::Base
        attr_protected :roles
      end
      
    • すると、こう書いても、usernameは更新されても、rolesは更新されないよ
         @user = User.new({
           :username => 'こっちは更新される',
           :roles => 'ここは更新されない'
         })
      
    • これは、一括更新時だけなんで、以下のコードを書けば更新可能
         @user.roles = 'これなら、更新可能'
      
    • attr_accessibleなら、 一括更新可能なフィールド を指定できるよ
  • update_allなら、複数レコードの更新
    • こう書けば
       def update_all
         count = Book.update_all('price = price * 1.05',['publish = ?','出版社A'])
         render :text => "#{count}件更新!"
       end
      
    • こんなSQLが実行される
      UPDATE "books" SET price = price * 1.05 WHERE (publish = '出版社A')
      
      • うーん、SETとWHEREを指定する感じか…。この辺は、微妙だなー。
    • orderとlimitを一緒に指定するには、こう
       def update_all2
         count = Book.update_all('price = price * 1.5',nil,{:order => 'published ASC', :limit => 3 })
         render :text => "#{count}件更新!"
       end
      
    • で、こんなSQLになる
      SELECT "books".* FROM "books" ORDER BY published ASC LIMIT 5
      
      • あり?本だと、IN句を使うSQLになるけど…。Rails3.1だからかな?
  • destroy/deleteで、レコードの削除
    • こんな感じで書けば、
      Book.destroy(params[:id])
      
    • こんなSQLが発行される
      SELECT "books".* FROM "books" WHERE "books"."id" = ? LIMIT 1  [["id", "IDの値"]]
      DELETE FROM "books" WHERE "books"."id" = ?  [["id", IDの値]] 
      
      • destroyの場合、一旦SELECTしてからDELETEするよ。これは、アソシエーションやコールバックで必要だからみたい。
    • deleteだと、単にDELETE文を発行するだけ。
  • destroy_allだと、まとめて削除ね
    • こんな風に書くと
       def destroy_all
         books = Book.destroy_all(['publish = ?','出版社A'])
         render :text => "削除完了"
       end
      
    • こんなSQLが発行されると
      SELECT "books".* FROM "books" WHERE (publish = '出版社A')
      DELETE FROM "books" WHERE "books"."id" = ?  [["id", IDの値1]]
      DELETE FROM "books" WHERE "books"."id" = ?  [["id", IDの値2]]
      
      • あー、戻りは[books]になるんだ。削除されたレコードが返ってくるんだな、きっと。
      • ん?destroy_allのソースを見ると、retrunが無いのに、結果が返る?
      • おお!それは、Rubyだとreturnを書かないと最後の判定などの結果(ここではfindね)が返るからなのかー。すごいなー
  • transactionで、トランザクション管理できるのね
    • 要するに、例外発生時はロールバックし、通常のsave時はコミットするってことみたいだが、例が良くないな…
    • ちなみに、saveではなくsave!メソッドにすると、ture/falseじゃなくて例外が返るらしい
  • lock_versionカラムを追加すると、ActiveRecord側で同時書き込みを抑制できるんだ!
    • オプティミスティック同時実行制御(楽観的同時実行制御)というらしい。
      • 要するに、事前にチェックとかしないで、「いきなり更新して、更新済みだったらエラー」にするってことね。
    • 設定は、まずはScaffoildingを使って、レコードにカラムを追加する
      rails g scaffold member name:string email:string lock_version:integer
      
    • で、デフォルト値を0にしないとまずいから、マイグレーションファイルを修正(default=>0のトコだけね)
      class CreateMembers < ActiveRecord::Migration
        def change
          create_table :members do |t|
            t.string :name
            t.string :email
            t.integer :lock_version, :default => 0
      
            t.timestamps
          end
        end
      end
      
    • 後は、マイグレーションでOK
      rake db:migrate
      
    • 使い方は、Veiwのフォームにhiddenで「:lock」という名前で「:lock_version」を指定すればOKみたい
      • ここだと、app/views/members/_form.html.erbファイルに、以下1行目を追加ね
         <% hidden_field :lock, :lock_version %>
         <div class="field">
           <%= f.label :name %><br />
           <%= f.text_field :name %>
         </div>
        
    • 後は、必要に応じてコントローラ側で例外をrescue処理すれば、OK
    • の、はずなんだけど、HTMLにhiddenが見当たらない?しかも、削除してもちゃんと例外出るよ?
      • 3.1からデフォルトになったのかな?後で調べよう!
      • うーん、ちょいと調べただけだが、hiddenはアリもナシの事例もありそう。しかも、3.1とかの話じゃなくて。
      • ただ、はっきりしてるのは、Railsでは「lock_version」というカラム名指定で楽観的同時実行できるってこと。
  • その他、いろいろ役に立ちそうな更新系
    increment(attribute, by = 1) まさに、インクリメントする
    decrement(attribute, by = 1) こっちはデクリメントね
    new_record?() 新規レコードチェック
    persisted?() 保存済みチェック
    toggle(attribute) ブール型のON/OFFの反転。これ、いいね!
    touch(attribute = nil) 日付型を現在時刻で更新
    changed() 変更されたカラム名を取得
    changed?() 変更されたかチェック
    changed_attributes() 変更された情報のハッシュ(カラム名 => 変更前の値)を返す
    changes() 変更された情報のハッシュ(カラム名 => [変更前,変更後])を返す。つうか、これ、すごいな。
    previous_changes() 保存前の変更情報のハッシュ(カラム名 => [変更前,変更後])を返す

-
最終更新:2013年08月19日 06:29