ねもぷらす

ふぁいんでぃんぐねもの日記。プログラミングとか育児とか

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(条件式, 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.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.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
  • パラメータ化
  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

MysqlMyISAMストレージエンジンはトランザクションに対応していない。
テーブル作成時に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 テーブルのような関係。