scribble

ottocho's blog

Home About GitHub

07 Apr 2013
ActiveRecord named_scope

named_scope


前言

本文介绍named_scope的使用。是文档和其他人的文档的汇编。以前写的文章,最近复习用到,也整理到这边来。

本文用几个例子来解释和说明named_scope,加上一些自己的想法和观点。


第一个例子

数据库定义

create table orders (
    id int not null auto_increment,
    name varchar(100) not null,
    email varchar(255) not null,
    address text not null,
    pay_type char(10) not null,
    shipped_at datetime null,
    primary key (id)
);

数据库抽象类

class Order < ActiveRecord::Base
end

简单示例

pp Order.find([22,23,24])
# 返回一个列表 列表每个元素为 Order类型(也就是继承的ActiveRecord::Base类型)
pp Order.scoped
# 返回一个 ActiveRecord::Relation类型,继承于ActiveRecord::Base类型
pp Order.find(:first, :conditions => { :name => "ottocho" })
k = Order.scoped(:conditions => { :name => "ottocho" })
pp k.find(:first)

显而易见了。

我怎么理解’scope’

我理解为接受了约束的数据抽象类对象:此对象代表在此约束下的数据集合抽象对象。

这样说来,ar的scopefind方法是非常接近的。但他们返回类型是不一样的。

  1. scope方法 返回的是ActiveRecord::Relation类型,继承于ActiveRecord::Base类型
  2. find方法 返回的是一个列表,其中每个元素为定义的数据抽象类型(也是继承于ActiveRecord::Base类型)

他们两个返回的类型不同但是都可以用each迭代。一个scope就可以利用迭代器了。如果还需要加其他约束条件。有以下两种做法:

otto_order = Order.scoped(:conditions => { :name => "ottocho" })
pp otto_order.find(:first, :conditions => { :address => "guangzhou" })
pp otto_order.scoped(:conditions => { :address => "guangzhou" }).first

第二个例子

这第二个例子,来说明在数据库抽象类实现中,利用named_scope方法来定义的快捷检索方法。

本例的数据库抽象类

class Order < ActiveRecord::Base
    named_scope :cash_pay, :conditions => { :pay_type => "cash" }
    named_scope :card_pay, :conditions => { :pay_type => "card" }
    named_scope :recent, lambda { { :conditions => ['created_at > ?', 1.week.ago] } }
end

示例

# 有人写的是"same as" 其实不同
# find返回的是列表(:all)或者单个对象(:first)
Order.cash_pay   # Order.find(:all, :conditions => { :pay_type => "cash" })
Order.card_pay   # Order.find(:all, :conditions => { :pay_type => "card" })
Order.recent     # Order.find(:all, :conditions => ['created_at > ?', 1.week.ago])
# scope可以调用scope(scope返回的relations类型继承于ActiveRecord::Base,显然拥有此方法)
Order.card_pay.recent
# 有人写的是"same as" 其实不同
Order.with_scope(:conditions => { :pay_type => "card" }) do
  Order.find(:all, :conditions => ['created_at > ?', 1.week.ago])
end
# 可以给命名的scope传递参数,使得在运行时指定条件
class Order < ActiveRecord::Base
    named_scope :shipped, lambda { |time_ago| { :conditions => ['shipped_at > ?', time_ago] }
end
Order.shipped 7.days.ago
# 类似于:
# Order.find(:all, :conditions => ['shipped_at > ?', 7.days.ago])

# 可以为定义的scope再定义方法
class Order < ActiveRecord::Base
    named_scope :shipped, :conditions => ['created_at > ?', 1.week.ago] do
        def cash_paid
            each { |i| i.update_attribute(:pay_type, "cash") }
        end
    end
end
# 把装好的order设置为cash支付的
Order.shipped.cash_paid
# scoped也是个类方法 匿名定义
card_pay = Order.scoped(:conditions => {:pay_type => "card"})
recent = Order.scoped(:conditions => ['created_at > ?', 7.days.ago])
recent_card_pay = recent.card_pay

其他例子

转发来的。

平常我们在使用named_scope时,对于一些sql语句无法表达的集合筛选,总是需要进行select处理,代码读起来就不那么优雅。

named_scope + block 则解决了这个问题。

class User < ActiveRecord::Base
  has_many :stories

  named_scope :inactive, :conditions => {:active => false} do
     def latest(number = 1, role = nil)
       collection = role.blank? ? self : self.find_all_by_role(role)
       collection[0, number]
     end
  end

end

# Re-activate all inactive users
User.inactive.latest(5,'admin')

rails中不仅named_scope可以这样写,has_many等关联方法 也可以 带block.例如:

class User < ActiveRecord::Base
  has_many :stories do
    def latest_public
      self.select{|story| story.public? }.first
    end
  end
end

这两个例子中block内的self 都是代表当前集合,所以self其实是对集合做操作。


参考

http://ar.rubyonrails.org/
http://ar.rubyonrails.org/classes/ActiveRecord/Base.html
http://ar.rubyonrails.org/classes/ActiveRecord/NamedScope.html
http://www.cnblogs.com/orez88/articles/1580930.html
https://github.com/wvanbergen/scoped_search
https://github.com/wvanbergen/scoped_search/wiki/search-definition

【完】

2013.04.07, ottocho


Til next time,
at 22:12

scribble

Home About GitHub