Leon's Blogging

Coding blogging for hackers.

Ruby on Rails - Scopes

| Comments

在 controller 經常會用到一些資料的查詢條件 但有許多條件是同時會出現在很多地方,或是查詢條件比較複雜,無法一次就看懂在查詢什麼

這時就可以用 Scope 讓程式變得乾淨易讀,而且也可以 串接 來使用。

沒帶參數

1
2
3
def index
  @books = Book.order(create_at: :desc)
end

一般 controller 會這樣子撈出所有的資料,並且按照建立時間排序 這時就可以在 model 寫下

1
2
3
class Book < ActiveRecord::Base
    scope :news_up, -> { order(created_at: :desc) }
end

-> {…}是Ruby語法,等同於Proc.new{…}或lambda{…},用來建立一個匿名方法物件

接著 controller 就只要

1
2
3
def index
  @books = Book.news_up
end

就變得更加清楚明瞭

帶參數

找尋建立時間小於參數的資料

1
2
3
class Book < ActiveRecord::Base
  scope :recent, ->(time) {where("created_at < ?", time) }
end
1
2
3
4
5
class Book < ActiveRecord::Base
    def self.recent(time)
        where("created_at > ? ",time)
    end
end

以上兩種方式皆可

接著只要在controller

1
2
3
def index
  @books = Book.recent(Time.now)
end

串接

1
2
3
def index
  @books = Book.news_up.recent(Time.now)
end
1
2
3
4
class Book < ActiveRecord::Base
  scope :recent, ->{ where(published_at: 2.weeks.ago) }
  scope :recent_red, ->{ recent.where(color: 'red') }
end

default

設定所有 scope 的 default 值

1
2
3
4
class Book < ActiveRecord::Base
    default_scope -> { order(id: :desc) }
    #or default_scope { order(id: :desc) }
end

DRY

where(approved: true)重複了

1
2
3
4
5
6
7
8
9
10
class Comment < ActiveRecord
  belongs_to :post
  scope :approved, ->{ where(approved: true) }
end

class Post < ActiveRecord
  has_many :comments
  scope :with_approved_comments,
    -> { joins(:comments).where('comments.approved = ?', true) }
end

Post 改成用 merge 方式,去呼叫另一個 scope

1
2
3
4
5
class Post < ActiveRecord
  has_many :comments
  scope :with_approved_comments,
    -> { joins(:comments).merge(Comment.approved) }
end

Rails3 vs Rails4

1
2
3
4
class User < ActiveRecord::Base
   scope :active,   -> { where(state: 'active') }
   scope :inactive, -> { where(state: 'inactive') }
end
1
2
3
4
5
6
7
8
9
10
11
#Rails3
User.active.inactive
#SELECT * FROM users WHERE state = 'inactive'

#Rails4
User.active.inactive
#SELECT * FROM posts WHERE state = 'active' AND state = 'inactive'

#Rails4 要跟Rails3 一樣就加上merge
User.active.merge(User.inactive)
#SELECT * FROM users WHERE state = 'inactive'

官方文件:
Guides
Guides 中文
apidock

參考資料: Scopes 作用域

Comments