The Ruby On Rials Gudie -- Active Record Query Interface

来源:互联网 发布:iphone照片直接导入mac 编辑:程序博客网 时间:2024/05/18 03:31

这一章讲的就是如何从数据库中查找数据,他们是数据库无关的

我们将使用以下模型进行讲解

class Client < ActiveRecord::Base  has_one :address  has_many :orders  has_and_belongs_to_many :rolesendclass Address < ActiveRecord::Base  belongs_to :clientendclass Order < ActiveRecord::Base  belongs_to :client, counter_cache: trueendclass Role < ActiveRecord::Base  has_and_belongs_to_many :clientsend



一、从数据库中查找数据

1. 首先我们先将查找单一的object

rails 为查找单一记录提供了五种方法,分别是find,take,first,last,find_by,它们之中有些函数也可以用来查找多条记录,我们这里重点介绍查找单一记录的部分。

1.1 find

这个函数是利用主键查找的,默认的也就是使用id列查找。你可以这样使用,也就是在find里面写上id号。

# Find the client with primary key (id) 10.client = Client.find(10)# => #<Client id: 10, first_name: "Ryan">

生成的sql如下:

SELECT * FROM clients WHERE (clients.id = 10) LIMIT 1
如果查找不到,那么find函数将抛出一个ActiveRecord::RecordNotFound异常。

1.2 take

这个函数从记录中拿出一个,不加任何参数时它将默认的拿出第一个。当然这个和first不一样,因为可以通过参数确定拿出几个。这个我们后面马上就要说到。你可以这样使用

client = Client.take# => #<Client id: 1, first_name: "Lifo">

它生成的sql如下

SELECT*FROMclients LIMIT 1

如果查找不到,那么他将返回nil,而不是抛出异常。

1.3 first

就像他的名字,这个函数将返回第一条记录,这个第一条值得是按照主键id升序排列,返回第一条,查找不到时返回nil。你可以这样使用。

client = Client.first# => #<Client id: 1, first_name: "Lifo">

生成的sql如下

SELECT * FROM clients ORDER BY clients.id ASC LIMIT 1

1.4 last

就像他的名字,这个函数将返回最后一条记录,这个最后一条值得是按照主键id升序排列,返回最后一条,查找不到时返回nil。你可以这样使用。

client = Client.last# => #<Client id: 221, first_name: "Russel">
生成的sql如下

SELECT * FROM clients ORDER BY clients.id DESC LIMIT 1

1.5 find_by

这个函数还是很强大的,正如它的名字(顺便感叹一句,rails的美正是这样,每个函数不需要什么注释,你只要看名字就能很清楚的看懂。同时选择了ruby语言,不带着括号,所以程序看起来更加像一篇文章。所以假如你的程序写的不能像一篇文章那样把思路缓缓道来,那么它一定是有问题的。),它是通过某些东西来查找,你可以传给他一个hash,你的key是列的名字,你的data是查找的内容。就像下面一样使用。查找不到时,返回nil

Client.find_by first_name: 'Lifo'# => #<Client id: 1, first_name: "Lifo"> Client.find_by first_name: 'Jon'# => nil
相当于

Client.where(first_name: 'Lifo').take

1.6 take!

还是返回第一个,但是它是返回实实在在的第一个,并不是按照主键排序后的。假如找不到的话抛出一个ActiveRecord::RecordNotFound异常

client = Client.take!# => #<Client id: 1, first_name: "Lifo">

就相当于下面的sql,注意没有一个按照主键排序

SELECT * FROM clients LIMIT 1

1.7 first!

和first相同,不同的就是查找不到的时候不在返回nil,而是抛出一个ActiveRecord::RecordNotFound异常

1.8 last!

和last相同,不同的就是查找不到的时候不在返回nil,而是抛出一个ActiveRecord::RecordNotFound异常

1.9 find_by!

和find_by相同,不同的就是查找不到的时候不在返回nil,而是抛出一个ActiveRecord::RecordNotFound异常

2. 查找Multiple (多的)记录,直接从数据库加载到主存

2.1 find

find 函数还是利用主键查找,只不过我们传递给find参数不在是单一的id了。而是一个array。假如array中有一个id没有找到,那么将抛出ActiveRecord::RecordNotFound异常

你可以这样使用

# Find the clients with primary keys 1 and 10.client = Client.find([1, 10]) # Or even Client.find(1, 10)# => [#<Client id: 1, first_name: "Lifo">, #<Client id: 10, first_name: "Ryan">]
相当于我们执行了下面的sql

SELECT * FROM clients WHERE (clients.id IN (1,10))

2.2 take

给take一个整数的参数n,那么就将返回前n条记录,记住,它并不是按照主键id排序的。你可以这样使用

Client.take(2)# => [#<Client id: 1, first_name: "Lifo">,      #<Client id: 2, first_name: "Raf">]
相当于执行了下面的sql

SELECT * FROM clients LIMIT 2

2.3 first

给first一个整数的参数n,那么就将返回按照id排序后的前n条记录。你可以这样使用

Client.first(2)# => [#<Client id: 1, first_name: "Lifo">,      #<Client id: 2, first_name: "Raf">]

相当于执行了以下sql

SELECT * FROM clients ORDER BY id ASC LIMIT 2

2.4 last

给last一个整数的参数n,那么就将返回按照id排序后的后n条记录。你可以这样使用
Client.last(2)# => [#<Client id: 10, first_name: "Ryan">,      #<Client id: 9, first_name: "John">]

相当于执行了一下sql

SELECT * FROM clients ORDER BY id DESC LIMIT 2

3. 查找Multiple (多的)记录,分批处理

上面的查找多条记录有一定的缺陷。假如我们查找到的数据过多,就有这种可能。我们的数据太多,主存无法全部存储,这样就会造成主存越界。一个比较明智的做法是分批处理,每次处理一部分。

3.1 find_each

这个函数默认的每次最多加载1000个记录,然后每个记录都将执行一次后面的代码块。当记录超过1000条后它将自动加载,然后再重复执行后面的代码块,用法如下

User.find_each do |user|  NewsLetter.weekly_deliver(user)end

find_each参数

3.1.1. batch_size

你可以通过这个参数设定每次加载的记录个数,比如你设置5000个就可以像下面类似的做。

User.find_each(batch_size: 5000) do |user|  NewsLetter.weekly_deliver(user)end

3.1.2. start

默认的设置是按照主键上升的顺序获得,通过start可以设置开始的id,可以这样使用

User.find_each(start: 2000, batch_size: 5000) do |user|  NewsLetter.weekly_deliver(user)end

3.2 find_in_batched

它与find_each类似,不同的是后面的代码块。后面代码块仅执行一次。也就是说find_each是把记录看成一个个独立的部分,正如它的名字,有个each的意思在里面;而find_in_batched则是取得所有的记录,把他们看成一个数组。可以参考如下的例子

# Give add_invoices an array of 1000 invoices at a timeInvoice.find_in_batches(include: :invoice_lines) do |invoices|  export.add_invoices(invoices)end

这里面有个参数是include,这个参数后面跟的是association的名称,你可以根据这个名称加载对应的mode,比如A has_many B,那么include bs就可以加载上A有的B了,注意include是复数形式。

它可以使用除了:order和:limit以外的find_ each 和find 的所有参数。

二、条件(where)

所谓条件其实主要指的是where函数,我们只不过是根据where参数的不同区分讲解。

1. 纯String条件

你可以使用一个string 来进行查询,其实他就是填到sql的where后面的东西。用法如下

Client.where("orders_count = '2'")
但这种方式我们是强烈不推荐的,因为它不安全。为什么说它不安全呢?它的是形成sql,直接添加的。所以下面的也是正确的了

 Client.where("first_name LIKE '%#{params[:first_name]}%'")
可能会把我们不想暴露出的东西暴露出去。这个在后面我们会有专门的一章讲解rails的安全性。

2. 数组条件

和C语言替换有点类似,使用方法如下

Client.where("orders_count = ?", params[:orders])

rails用?代表可替换的,然后按照顺序替换。下面的例子被两个参数替换。

Client.where("orders_count = ? AND locked = ?", params[:orders], false)
懂Ruby的人肯定会想到另一种做法,像下面的

Client.where("orders_count = #{params[:orders]}")

但为什么不推荐这种方式呢?因为它不安全,至于为什么不安全后面我们会再讲,总之这个是sql语句不安全。

用?替换读起来会可能带来疑问,我们可以为它起个名字,然后后面传一个hash过去。名字是我们hash的key,它将被hash中的data代替。就如下面的方法。我们里面引入了一个:start_date 和 :end_date的symbol变量,后面的hash中key 也是它们,然后data才是我们真正的数据。

Client.where("created_at >= :start_date AND created_at <= :end_date",  {start_date: params[:start_date], end_date: params[:end_date]})

3. Hash条件

我们还可以传递一个hash给where。key是列名,然后根据我们data的类型不同,它只能支持相等性判断(=)、范围判断(between ... and)、子集判断(in)。

3.1 相等性判断

基本形式就是key:data的hash对,注意这个data不能是symbol类型。

Client.where(locked: true)
对于belongs_to,它也可以work fine。比如 Author belongs_to Post,那么下面查找Author 是author的用法是正确的

Post.where(author: author)  #直接写就可以
同样它支持多态关系,这里暂时有点不大明白下面的用法。

Author.joins(:posts).where(posts: {author: author})

3.2 范围判断

基本形式是key:range,也就是data部分是range变量。

Client.where(created_at: (Time.now.midnight - 1.day)..Time.now.midnight)

这个相当于产生BETWEEN ... AND的sql语句,上面的将产生下面的sql

SELECT * FROM clients WHERE (clients.created_at BETWEEN '2008-12-21 00:00:00' AND '2008-12-22 00:00:00')

3.3 子集判断

基本形式是key:array,也就是data部分是数组变量

Client.where(orders_count: [1,3,5])

这个相当于IN,产生下面的sql

SELECT * FROM clients WHERE (clients.orders_count IN (1,3,5))

3. NOT 条件

就是Where.not函数,相当于调用了where,但是传递的条件是加了个否定的条件。

Post.where.not(author: author)


三、顺序(ordering)

按照某种顺序列出数据库信息,默认的使用升序排列,使用形式如下

Client.order(:created_at)

上面的意思是按照created_at列升序排列,你也可以手动的设置升序还是降序(ASC or DESC)

Client.order(created_at: :desc)# ORClient.order(created_at: :asc)# ORClient.order("created_at DESC")# ORClient.order("created_at ASC")

假如你想先按照orders_count排序,再按照created_at排序,再。。。你只要在参数中依次写出就好了,用法如下

Client.order(orders_count: :asc, created_at: :desc)# ORClient.order(:orders_count, created_at: :desc)# ORClient.order("orders_count ASC, created_at DESC")# ORClient.order("orders_count ASC", "created_at DESC")

你也可以通过多次调用来实现上述功能

Client.order("orders_count ASC").order("created_at DESC")# SELECT * FROM clients ORDER BY orders_count ASC, created_at DESC

四、选择特定的列

find函数相当于执行的是select * ,而现在我们讲的就是仅仅要特定的列了,使用select函数。使用方法如下

Client.select("viewable_by, locked")

生成的sql如下

SELECT viewable_by, locked FROM clients

它返回的是什么呢?注意,他返回的一个新的model,仅仅包含着你提供的field。但你没有任何访问这些数据的函数。假如你尝试的访问这些数据,你将得到如下错误

ActiveModel::MissingAttributeError: missing attribute: <attribute>

上面的<attribute>是你要访问的数据


你可以配合使用distinct函数返回一个互异列就像下面的用法

Client.select(:name).distinct

它生成的sql如下

SELECT DISTINCT name FROM clients

五、limit 和 offset (限制个数和设置偏移)

limit是决定最多查找到的个数,offset是设置初始偏移量。

你可以这样使用

Client.limit(5)

它将生成如下的sql

SELECT * FROM clients LIMIT 5
你还可以为它加上offset偏移量

Client.limit(5).offset(30)

他将生成如下sql

SELECT * FROM clients LIMIT 5 OFFSET 30

六、Group

合计函数 (比如 SUM) 常常需要添加 GROUP BY 语句。假如你还是不明白SQL中group by的意思,可以看这里。

你可以这样使用,也就是再后面直接加上group函数,里面参数一般写列的名字,同样支持合计函数

Order.select("date(created_at) as ordered_date, sum(price) as total_price").group("date(created_at)")
它将生成如下sql

SELECT date(created_at) as ordered_date, sum(price) as total_priceFROM ordersGROUP BY date(created_at)

七、having

在 SQL 中增加 HAVING 子句原因是,WHERE 关键字无法与合计函数一起使用,它就相当于where。假如你不明白SQL中的having的意思,可以看这里。

你可以这样使用

Order.select("date(created_at) as ordered_date, sum(price) as total_price").  group("date(created_at)").having("sum(price) > ?", 100)

也是直接后面加上having语句,参数的使用和where一样。它将生成如下SQL

SELECT date(created_at) as ordered_date, sum(price) as total_priceFROM ordersGROUP BY date(created_at)HAVING sum(price) > 100

八、重写条件

1.except

你可以用except函数忽略前面的某一函数,但它并不支持有association。比如

Post.where('id > 10').limit(20).order('id asc').except(:order)

我们写了order函数,但是except忽略的order函数,所以生成的sql也将不带order。

SELECT * FROM posts WHERE id > 10 LIMIT 20

2.unscope

Post.comments.except(:order)

假如commets有一个默认的order,那么上面的语句还是会产生一个order,为了彻底消除order,我们引入了unscope函数。

Post.order('id DESC').limit(20).unscope(:order)
相当于

Post.limit(20)

它还支持同时消除多个函数

Post.order('id DESC').limit(20).unscope(:order, :limit)

相当于

Post.all

消除where子句时,还支持消除where特定条件

Post.where(:id => 10).limit(1).unscope(where: :id, :limit).order('id DESC') 

解释下where: :id这是一个键值对,key=>data,key是where,决定了要消除where,data是:id,决定要消除id的条件。它相当于下面的

Post.order('id DESC')

3. only

相当于except的反义。

Post.where('id > 10').limit(20).order('id desc').only(:order, :where)

相当于下面的语句

SELECT * FROM posts WHERE id > 10 ORDER BY id DESC

4. reorder

用来重写默认的order,什么叫默认范围的order呢,其实就是在model中加入了order的约束。如下面的

class Post < ActiveRecord::Base  ..  ..  has_many :comments, order: 'posted_at DESC'end

默认按照posted_at降序排列。

当我们执行下面的时候,也就是不适用reorder

Post.find(10).comments

相当于执行下面的sql

SELECT * FROM posts WHERE id = 10 ORDER BY posted_at DESC

但我们使用reorder时

Post.find(10).comments.reorder('name')

相当于执行了下面的sql

ELECT * FROM posts WHERE id = 10 ORDER BY name

5.reverse_order

这个函数用来颠倒原本的顺序,它无任何参数,我们这样执行

Client.where("orders_count > 10").order(name :desc).reverse_order
相当于如下sql

SELECT * FROM clients WHERE orders_count > 10 ORDER BY name ASC

当我们的语句中没有order函数时,相当于按主键排列,然后顺序颠倒

Client.where("orders_count > 10").reverse_order

相当于如下sql

SELECT * FROM clients WHERE orders_count > 10 ORDER BY clients.id DESC

九、Null 关系

这个主要是用来返回一个符合格式的空记录,因为我们可能获得错误的请求,然后我们返回一个空的记录。但这个记录还会有后续的处理,我们需要返回一个符合格式的记录,这样后续的记录才能处理。像下面一样使用

Post.none # returns an empty Relation and fires no queries.

使用例子如下

# The visible_posts method below is expected to return a Relation.@posts = current_user.visible_posts.where(name: params[:name]) def visible_posts  case role  when 'Country Manager'    Post.where(country: country)  when 'Reviewer'    Post.published  when 'Bad User'    Post.none # => returning [] or nil breaks the caller code in this case  endend

十、只读object

函数readonly返回一个只读记录

client = Client.readonly.firstclient.visits += 1client.save

上面的代码将抛出一个ActiveRecord::ReadOnlyRecord异常,因为对只读数据进行了修改。


十一、在update时为数据上锁

更新数据时,我们需要为数据上锁。这样支持动态更改数据。

rails 提供了两种锁分别是

Optimistic Locking 乐观锁

Pessimistic Locking消极锁

1. 乐观锁

乐观锁是这样进行加锁的。对于某一资源,它不考察是否有其他进程访问该资源,它考察的是是否有其他进程对数据进行了修改。也就是说假如有资源X,进程B和C,假如B访问了X,C也可以访问X。它的原理是这样的,它增加一列叫做lock_version,是一个integer类型的。当有进程取得该资源时,该进程取得数据库记载的lock_version。当某个进程进行修改数据时,进程的lock_version值小于数据库中记载的lock_version值,那么就会抛出ActiveRecord::StaleObjectError异常,如果不小于,那么数据库中的lock_version就加一。它是rails默认的。

c1 = Client.find(1)c2 = Client.find(1) c1.first_name = "Michael"c1.save c2.name = "should fail"c2.save # Raises an ActiveRecord::StaleObjectError

上面的代码就将抛出异常,因为对于c1,假如他访问id为1的client(我们称之为X),X的lock_version为0。我们将仔细说说是怎么回事

c1 = Client.find(1) #此时的c1的lock_version=0;X的lock_version=0;c2 = Client.find(1) #此时的c2的lock_version=0;X的lock_version=0; c1.first_name = "Michael"c1.save #首先判断c1的lock_version=0 不小于X的lock_version,然后X的lock_version+1了,也就是X的版本进行了更新,X的lock_version=1 c2.name = "should fail"c2.save # 判断c2的lock_version=0 小于X的lock_version=1;也就是说c2的版本过于陈旧,所以抛出异常
你可以使用如下函数关闭乐观锁

ActiveRecord::Base.lock_optimistically = false #这里的ActiveRecord::Base是可以被其他的model名字代替的

你可以在model定义的时候修改lock_version列的名字

class Client < ActiveRecord::Base  self.locking_column = :lock_client_columnend

2.悲观锁

它就是使用数据库底层的互斥锁对访问的数据进行加锁,使用lock函数实现。一般我们把lock函数放在transaction(事务)函数的数据块中。就像下面的使用

Item.transaction do  i = Item.lock.first  i.name = 'Jones'  i.saveend

他将产生如下sql

SQL (0.2ms)   BEGINItem Load (0.3ms)   SELECT * FROM `items` LIMIT 1 FOR UPDATEItem Update (0.4ms)   UPDATE `items` SET `updated_at` = '2009-02-07 18:05:56', `name` = 'Jones' WHERE `id` = 1SQL (0.8ms)   COMMIT

你可以传递sql给lock以执行特定的锁,比如MySQL有一个命令是LOCK IN SHARE MODE,这是一种特殊的锁,指你锁上了某个数据,但这个数据可以被其他人只读的访问,

Item.transaction do  i = Item.lock("LOCK IN SHARE MODE").find(1)  i.increment!(:views)end

假如你有一个model的实例,那么你可以用with_lock函数获得锁和事务,并在其中进行操作。

item = Item.firstitem.with_lock do  # This block is called within a transaction,  # item is already locked.  item.increment!(:views)end

十二、Join tables

join的翻译还真不知道怎么翻译,就是笛卡尔积什么的,有关join,你可以从这里学习(join , inner_join, left_join, right_join)

1. 使用sql的string

就是直接填上sql中join后面的字符串

Client.joins('LEFT OUTER JOIN addresses ON addresses.client_id = clients.id')

生成的sql如下

SELECT clients.* FROM clients LEFT OUTER JOIN addresses ON addresses.client_id = clients.id

2.使用由association名字组成的Array或者Hash

这种方法只适合INNER JOIN(join 和 inner join相同)。

我们的model带有association,我们可以使用这个association名字进行join。

假设我们的model如下

class Category < ActiveRecord::Base  has_many :postsend class Post < ActiveRecord::Base  belongs_to :category  has_many :comments  has_many :tagsend class Comment < ActiveRecord::Base  belongs_to :post  has_one :guestend class Guest < ActiveRecord::Base  belongs_to :commentend class Tag < ActiveRecord::Base  belongs_to :postend

2.1 join单一的association

Category.joins(:posts)

将产生如下sql

SELECT categories.* FROM categories  INNER JOIN posts ON posts.category_id = categories.id

2.2 join 多个association

Post.joins(:category, :comments)

只要依次吧association名称写到参数就行了,他将生成如下sql

SELECT posts.* FROM posts  INNER JOIN categories ON posts.category_id = categories.id  INNER JOIN comments ON comments.post_id = posts.id

2.3 join单层嵌套的association

Post.joins(comments: :guest)

产生如下sql

SELECT posts.* FROM posts  INNER JOIN comments ON comments.post_id = posts.id  INNER JOIN guests ON guests.comment_id = comments.id

返回有哪些有一个由guest做的comments的post

2.4 join多层嵌套的association

Category.joins(posts: [{comments: :guest}, :tags])

生成下面的sql

SELECT categories.* FROM categories  INNER JOIN posts ON posts.category_id = categories.id  INNER JOIN comments ON comments.post_id = posts.id  INNER JOIN guests ON guests.comment_id = comments.id  INNER JOIN tags ON tags.post_id = posts.id

3.特定环境下的Join table

其实就是配合使用where语句

time_range = (Time.now.midnight - 1.day)..Time.now.midnightClient.joins(:orders).where('orders.created_at' => time_range)

和下面的是一样的

time_range = (Time.now.midnight - 1.day)..Time.now.midnightClient.joins(:orders).where(orders: {created_at: time_range})

他们都是返回有orders的且是昨天之后创建的clients。

十三、Eager 加载association

所谓Eager loading,指的是尽量减少加载association的次数。解决办法就是在查询是用include加载进来,这样就不用重复加载了。

考虑下面的例子

clients = Client.limit(10) clients.each do |client|  puts client.address.postcodeend

乍看起来,这段代码很正常,但是它是有问题的。它查询了多少次?11次,第1次是加载clients;然后是10个clients遍历时,每次都要查询和当前clients相关的address,共十次。但我们可以将此类查询减少到2次,这对于提高系统效率有很重要的作用

clients = Client.includes(:address).limit(10) clients.each do |client|  puts client.address.postcodeend

简单一个修改,但他仅需要2次查询,因为他用了in代替了10次重复加载。

SELECT * FROM clients LIMIT 10SELECT addresses.* FROM addresses  WHERE (addresses.client_id IN (1,2,3,4,5,6,7,8,9,10))

include函数还有很多用法

1. 加载多个association

1.1用array加载

Post.includes(:category, :comments)

加载与Post相关的category和commets。

2.1用hash加载

Category.includes(posts: [{comments: :guest}, :tags])
将eager loading 与Category相关的posts,和posts的commets、tags,和posts的tags。

注意:顺便说下hash和array的区别。在这些加载中,使用array一般都是表示并排同等级的东西;hash表示加载某物及其子等级。你可以把整个model们想象成一个树,array表示兄弟和自己,hash表示子节点和自己。

2. 特定范围内使用eager loading

就是使用where,这个和joi那里讲的使用方法是一样的。

十四、Scopes

这里讲的主要是定义个一个查询函数。和普通的定义函数不大一样的是Scopes强调是通过上述的查询函数,如where、join、includes等的组合作用,它相当于给这些组合起了个名字。用法如下,就是使用scope函数,然后第一个参数相当于函数名,可以通过实例访问,第二个参数是一个proc代码块,以->{}形式出现。

class Post < ActiveRecord::Base  scope :published, -> { where(published: true) }end

它相当于这样

class Post < ActiveRecord::Base  def self.published    where(published: true)  endend

scopes可以链式定义,就如下面的

class Post < ActiveRecord::Base  scope :published,               -> { where(published: true) } #定义published函数  scope :published_and_commented, -> { published.where("comments_count > 0") } #使用上面的published函数,然后又调用了where函数end

1. 传递参数

就是修改第二个参数,->{},为->(){},括号里面写参数

class Post < ActiveRecord::Base  scope :created_before, ->(time) { where("created_at < ?", time) }end
它相当于这样定义了个函数

class Post < ActiveRecord::Base  def self.created_before(time)    where("created_at < ?", time)  endend
你可以这样使用这个函数

Post.created_before(Time.zone.now)
也可以这样使用

category.posts.created_before(time)

2. 配合使用scope

你可以级联使用scope定义的函数

class User < ActiveRecord::Base  scope :active, -> { where state: 'active' }  scope :inactive, -> { where state: 'inactive' }end User.active.inactive# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' AND "users"."state" = 'inactive'

但你可以看出这个合并的一些小问题,为什么两个where会合并为AND?

因为它是一个竞赛的过程,最后一个sql中的where会把所有和条件用AND合并

假如你想只剩下最后一个where,那么你可以这样使用,也就是使用merge函数,但实在是感觉不出意义在何。

User.active.merge(User.inactive)# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'

3. default scope

假如你想在每个查询时都默认使用一个scope,你可以使用default_scope。就如下面的

class User < ActiveRecord::Base  default_scope  { where state: 'pending' }  scope :active, -> { where state: 'active' }  scope :inactive, -> { where state: 'inactive' }end User.all# => SELECT "users".* FROM "users" WHERE "users"."state" = 'pending' User.active# => SELECT "users".* FROM "users" WHERE "users"."state" = 'active' User.where(state: 'inactive')# => SELECT "users".* FROM "users" WHERE "users"."state" = 'inactive'

我们使用了默认的scope,所以所有没有使用where的查询都是要使用默认的scope中的where。所以上面的all使用了查询state是pending,而其余两个查询都有使用where,所以就不在使用默认的where了。

假如你想使用稍微复杂点的default_scope,default_scope这种定义就显得不好用了,它更加适合一句话的定义。你可以定义一个名叫default_scope函数来实现。

class Client < ActiveRecord::Base  def self.default_scope    # Should return an ActiveRecord::Relation.  endend

4. remove all scopes

其实就是使用unscoped函数,它会移除所有的scopes

Client.unscoped.all
这个将移除所有的scopes,执行一个最普通的all查询

你不可以将scope函数和unscoped函数级联使用。你之所以想这么用,一定是想移除所有的scope后自己添加一个新的scope,你是可以使用代码块实现的

Client.unscoped {  Client.created_before(Time.zone.now)}

十五、动态finder

其实就是find_by_列名,这种方式rails 4.0中已经不再推荐使用,而将在rails 4.1中移除这种使用方式,这种使用方式呗find_by代替了。如果你想查看更多关于这个用法的,可以查看rails 3.0的guide,假如你在rails 4.1之后使用你就需要加载一个activerecord-deprecated_finders gem。应该是这个,它是老式的finder函数。

十六、find or build 新object

这个标题名字不好,它就是这样一个意思,我们去查找某个记录,假如没有的话就create一个新的。总之是不能返回nil。

1. find_or_create_by函数

find_or_create_by函数将从数据库中查找符合参数中的记录是否存在,假如不存在它将调用model的create函数。

如果你想发现一个client,它的名字是Andy,假如没有的话它就将会create一个新的。

Client.find_or_create_by(first_name: 'Andy')# => #<Client id: 1, first_name: "Andy", orders_count: 0, locked: true, created_at: "2011-08-30 06:09:27", updated_at: "2011-08-30 06:09:27">

它生成如下的sql

SELECT * FROM clients WHERE (clients.first_name = 'Andy') LIMIT 1BEGININSERT INTO clients (created_at, first_name, locked, orders_count, updated_at) VALUES ('2011-08-30 05:22:57', 'Andy', 1, NULL, '2011-08-30 05:22:57')COMMIT

但这条记录不见得会存到数据库中,假如他能通过validation验证,那么就会存起来;假如不通过,那么就不会存。

再看上面的sql,我们会发现locked列设置为了null,假如我们想查询name为Andy的client记录,假如不存在我们希望新建一个name为Andy,locked为false的client(其实就是locked只参与create,并不参与查询)我们有两种方法实现

Client.create_with(locked: false).find_or_create_by(first_name: 'Andy')

或者

Client.find_or_create_by(first_name: 'Andy') do |c|  c.locked = falseend


一种是用一个create_with函数,另一个是在代码块里赋值。个人比较推荐第二种方式,因为所有的事情都是在find_or_create_by一个函数内完成的,但这只是个人喜好。

2. find_or_create_by!函数

这个函数会强制的去save,假如通不过validation,那么就会抛出一个异常。

假如我们的client里面有下面的约束

validates :orders_count, presence: true

执行这个函数就会有异常

Client.find_or_create_by!(first_name: 'Andy')# => ActiveRecord::RecordInvalid: Validation failed: Orders count can't be blank

3. find_or_initialize_by函数

这个和find_or_create_by函数很类似,不同的是它仅仅是在主存中create一个new object,并不save。


十七、直接使用sql查询

我们可以直接使用sql查询。使用find_by_sql

Client.find_by_sql("SELECT * FROM clients  INNER JOIN orders ON clients.id = orders.client_id  ORDER clients.created_at desc")

这个函数将使用自定义的sql,并且返回实例化的记录。

1. select_all

这个函数和find_by_sql函数很类似,只不过他返回的不是实例化的记录,而是一个由hash组成的array,每个hash代表一个记录。
Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")

2.pluck

函数中文为拉、拔出。它的意思是提取出某一列,然后一个该列记录组成的array,参数为列名
Client.where(active: true).pluck(:id)# SELECT id FROM clients WHERE active = 1# => [1, 2, 3] Client.distinct.pluck(:role)# SELECT DISTINCT role FROM clients# => ['admin', 'member', 'guest'] Client.pluck(:id, :name)# SELECT clients.id, clients.name FROM clients# => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]

为什么会有这种函数呢?因为我们每次查询都会返回一个ActiveRecord的实例记录,也就是都需要实例化,而这个函数直接把查询结果修改为array,这样对于大数据量的更快。

3. ids

这个函数相当于pluck(:id)

十八、object存在性检验

可以使用exist?函数来检测
Client.exists?(1)

这个将检测id为1的记录是否存在,同样还可以检测多个id是否存在
Client.exists?(1,2,3)# orClient.exists?([1,2,3])
都存在时返回true,否则返回false。
Client.exists?
假如clients表为空,返回false,否则返回true。
你还可以使用any?和many?函数判断是否存在
# via a modelPost.any?Post.many? # via a named scopePost.recent.any?Post.recent.many? # via a relationPost.where(published: true).any?Post.where(published: true).many? # via an associationPost.first.categories.any?Post.first.categories.many?

十九、计算

就是指哪个sum、count、min、max什么的函数。下面我们以count讲一下计算。

Client.count # SELECT count(*) AS count_all FROM clients

它将返回client的个数

Client.where(first_name: 'Ryan').count# SELECT count(*) AS count_all FROM clients WHERE (first_name = 'Ryan')
他将返回first_name为Ryan的client个数。

1. count

除了上面的用法,你还可以
Client.count(:age).
取得所有有age的clients个数。

2.average

返回某列的平均值,他与count的用法很类似
Client.average("orders_count")
其中“orders_count”是列名字

3.minimum

返回最小值,和average用法一样
Client.minimum("age")

4.maxmum

返回最大值,和average用法一样
Client.minimum("age")

5.sum

返回求和,和average用法一样
Client.sum("orders_count")



原创粉丝点击