Ruby on Rails 3 アプリケーションプログラミング(5/9)
モデル開発
基本メソッド
- Model.find(keys) : 主キーによる検索、配列可
- select * from Model where Model.id in ( keys )
- Model.find_by_xxxx(value) : 動的ファインダによる検索
- select * from Model where Model.xxxx = "value" limit 1
- Model.find_all_by_xxxx(value) : 動的ファインダによる検索
- select * from Model where Model.xxxx = "value"
- Model.find_all_by_xxxx_and_yyyy(value1, value2) : 動的ファインダによる検索
- select * from Model where Model.xxxx = "value1" and Model.yyyy = "value2"
クエリメソッド
- Model.where(条件式のHash) : 基本的な条件式
- Model.where(:publish => ['A','B'] )
- select * from Model where publish in ('A','B')
- Model.where(:publish => ['A','B'] )
- Model.where(条件式, values ) : プレースホルダ
- (名前なし) Model.where('ID > ? and price >= ?', param[:id], param[:price])
- (名前付き) Model.where('ID > :id and price >= :price', {id=>param[:id], price=>param[:price]})
- select * from Model where ID > param[:id] and price >= param[:price]
- 実行は同じ。名前なしは対応付けがわかりにくい、名前アリは冗長。
- Model.order(sortColumns) : 並べ替え
- Model.select(columns) : 抽出対象の指定
- Model.limit(rows) : 最大取得行
- model.offset(rows) : 取得開始位置
- Model.where('publish in ("A","B")' ).order('publish desc, id asc').select('id,publish')
- select id,publish from Model where publish in ("A","B") order by publish desc, id asc
- Model.where('publish in ("A","B")' ).order('publish desc, id asc').select('id,publish')
- Model.first : 先頭レコード
- Model.last : 末尾レコード
- 遅延ロードの対象外
- チェーンメソッドの最後尾に付けないと☓
- ex) Model.order('price DESC').limit(3).last
- select * from Model order by price asc limit 1
- 呼び出し方がヘボくてもSQLで最適化してくれる
- グルーピング : group(式)
- 絞込み : having(式)
- Model.select('publish, min(price) as min, max(price) as max, count(*) as count').group('publish').having('min(price)>?', 1000)
- select publish, min(price) as min, max(price) as max, count(*) as count from Model group by publish having min(price)>1000
- Model.select('publish, min(price) as min, max(price) as max, count(*) as count').group('publish').having('min(price)>?', 1000)
データ取得メソッド
- データの存在を確認 : Model.exist?
- さいごがハテナ
- Model.where('publish = "hogehoge"').exists?
- select id from Model where publish = "hogehoge" limit 1
- 名前付きスコープ : scope スコープ名, 式
- デフォルトスコープ : default_scope スコープ名, 式
- モデルクラスに定義。
- scope hoge, where( :id > 100 )
- scope top10, newer.limit(10)
- Model.hoge.top10
- select * from Model where id > 100 limit 10
- Model.hoge.top10
- パラメータ化
scope :whats_new, lambda { |pub| where(:publish => pub).order('published DESC').limit(5) }
@books = Book.whats_new('ほげ')
selectメソッドとgroupメソッドのコンボで実現しそうだけど、件数とか最大値とかしか使わない場合は記述が簡単。
- Model.count
- Model.average('カラム')
- Model.minimum('カラム')
- Model.maximum('カラム')
- Model.sum('カラム')
登録/更新/削除
- 追加/更新 : @model.save
- @models.attributes = params[:model] : ポストデータをまとめてセット
- @models.update_attribute(:column => param[:column]) : 単一フィールドの書き換え
- 登録更新時にフィールドを保護 : attr_protected :フィールド名
- 指定した項目だけ値を設定(上と逆) : attr_accepted: フィールド名
- 例)
class User < ActiveRecord::Base ... attr_protected :roles ... end
def add_user @user = User.new({ :username => 'tyamada', :password => '12345', :email => 'tyamada@wings.msn.to', :dm => false, :roles => 'admin' }) render :text => "ロール情報: #{@user.roles}" end
上記では「@user.roles」に値が設定されない。
値をセットしたければ、明示的に
@user.roles = '値'
とするらしい。
- まとめて更新 : update_all( SET句, 条件式, オプション)
- 戻り値は更新件数
- オプションは limit など
- レコードを削除 : destroy
- select → delete
- 削除後も呼び出し済み変数の格納値は参照できる
- いきなりレコードを削除 : delete
- delete
- 消える。
- 複数レコードの削除 : destroy_all ( where句 )
- select → ユニークキーで1件ずつ delete
トランザクション実装 - transaction
MysqlのMyISAMストレージエンジンはトランザクションに対応していない。
テーブル作成時にInnoDBを明示的に宣言する。
- 必ず例外を吐くメソッド
- b1.save! は無視される。
def transact Book.transaction do b1 = Book.new({:isbn => '978-4-7741-4223-0', :title => 'Rubyポケットリファレンス', :price => 2000, :publish => '技術評論社', :published => '2011-01-01'}) b1.save! raise '例外発生:処理はキャンセルされました。' b2 = Book.new({:isbn => '978-4-7741-4223-2', :title => 'Tomcatポケットリファレンス', :price => 2500, :publish => '技術評論社', :published => '2011-01-01'}) b2.save! end render :text => 'トランザクションは成功しました。' rescue => e render :text => e.message end
競合チェックの実装 - オプティミスティック同時実行制御(楽観的同時実行制御)
テーブルに lock_version 列を追加
$ rails g scaffold member name:string email:string lock_version:integer $ vi db/migrate/yyyymmddhhmmss_create_members.rb ...lock_version 列に初期値ゼロを追加 t.integer :lock_version, :default => 0 ... $ rake db:migrate
例外のcatch - ActiveRecord::StaleObjectError
rescue ActiveRecord::StaleObjectError render :text => '競合エラーが発生しました。'
更新系メソッド
- increment(attr,num) - atty値を num(規定値1) でインクリメント
- decrement(attr,num) - 上のデクリメント
- new_record? - 現在のレコードが保存前かチェック
- persisted? - 上の逆。レコードが保存済みかチェック
- touch([name]) - undated_on/at 列を現在日時で更新、Name が指定されればその列も更新
- changed? - カラムに変更があったかをチェック
- changed - 変更されたカラムの配列
- changed_attributes - 変更されたカラムのハッシュ(カラム名=> 変更前の値)
- changeds - 変更されたカラムのハッシュ(カラム名=> [変更前の値,変更後の値])
- previou_changes - 保存前の変更情報ハッシュ (カラム名=> [変更前の値,変更後の値])
検証機能 - validates カラム [,...] 検証パラメータ名=>値
モデルへの入力値が正しいかを判定。
モデルクラスに記載。
検証処理はデータ保存時に自動的に行われる。
任意のタイミングで検証をする場合は @Model.valid?
例)
class Book < ActiveRecord::Base ... validates :isbn, :presence => true, :uniqueness => { :allow_blank => true }, :length => { :is => 17 , :allow_blank => true }, :format => { :with => /^[0-9]{3}-[0-9]{1}-[0-9]{3,5}-[0-9]{4}-[0-9X]{1}$/, :allow_blank => true } :isbn => { :allow_old => true } ...
検証パラメータ
- :allow_nil / :allow_blank - 空白時に検証をスキップ。 ex) :length => { :is => 17 , :allow_blank => true },
- {:on => :create(新規のみ)/:update(更新のみ)/:save(全部) } - 検証のタイミングを制限。規定値は :save。
- :if => 式/ :unless => 式 - 条件付き実行
- :message => 'コメント' - エラーメッセージを変更。ex) :presence => {:message => 'は必須です'}
その他の検証クラス
:acceptance - 受諾検証
例えば「利用規約にチェックしたか」などDBにカラムを作成する必要がない項目のチェックに利用。
例)
- app/models/user.rb
class User < ActiveRecord::Base validates :email, :confirmation => true end
- app/views/users/_form.html.erb
<%= form_for(@user) do |f| %> <div class="field"> <%= f.label :agreement %><br /> <%= f.check_box :agreement %> </div> <div class="actions"> <%= f.submit %> </div> <% end %>
:confirmation - 同一検証
パスワード、e-mailアドレスの再入力が一致しているかなど。
例)
- app/models/user.rb
class User < ActiveRecord::Base validates :email, :confirmation => true end
- app/views/users/_form.html.erb
<div class="field"> <%= f.label :email %><br /> <%= f.text_field :email %> </div> <div class="field"> <%= f.label :email_confirmation %><br /> <%= f.text_field :email_confirmation %> </div>
:uniqueness - 一意性検証
登録しようとするデータが存在しないこと。
例)
- app/models/book.rb
class Book < ActiveRecord::Base ... validates :title, :uniqueness => { :scope => :publish } ... end
- 発行されるSQL
select id from books where title = ? and publish = ? limit 1
アソシエーションによる複数テーブルの処理
books テーブルに対する reviews テーブルのような関係。
blogs テーブルに対する entries テーブルに対する comments テーブルのような関係。