ActiveRecord ModelをもっとDRYに

ActiveRecord Modelをもっと DRYに

@tkawa (from Sendagaya.rb)

DRY

  • Don't Repeat Yourself 「同じことを繰り返さない」

scope (ActiveRecord)

Rails 3 レシピブック p.130 より

where('age >= 20')order('created_at DESC')のような条件には、あらかじめ名前を付けてモデルに保持しておくことができます。これを名前付きスコープ、または、単にスコープと呼びます。

このスコープをうまく利用してモデル側に条件を閉じ込めることには、次のようなメリットがあります。

  • ロジックを 1 箇所に集約できる。
  • 呼び出し側のコードの可読性が高まる。

DRYじゃない

class User < ActiveRecord::Base
  # User.admin
  scope :admin, -> {
    where(role: 'admin')
  }

  # user.admin? # => true or false
  def admin?
    self.role == 'admin'
  end
end

scopeからは実装可能

def admin?
  User.admin.where(id: self.id).present?
  # SELECT * FROM users WHERE role = 'admin' AND id = 1
end

SQLは使いたくない!

arel_ruby (by @amatsuda)

ArelをRubyのEnumerable操作に変換する(!!!)

User.where(name: 'foo').arel.to_ruby.to_source
# => "select {|o| o.name == 'foo'}"
User.where(name: 'foo').arel.to_ruby.call(collection)
# apply to collection

arel_rubyを使って実装

def admin?
  User.admin.arel.to_ruby.call([self]).present?
end

↓抽象化

define_method("#{method_name}?") do
  self.class.send(method_name).arel.to_ruby.call([self]).present?
end

activerecord-endoscope

ただし

  • Arelならなんでも変換できるわけではありません
scope :not_admin, -> {
  where('role != "admin"')
}
user.not_admin? # => error (SQL文字列は変換不可能)

おまけ:RESTful Web APIs 読書会

cover

開催計画中。

渋谷で隔週○曜日?

http://www.circleaf.com/groups/19