这一章讲的就是如何从数据库中查找数据,他们是数据库无关的
我们将使用以下模型进行讲解
class Client < ActiveRecord::Base
has_one :address
has_many :orders
has_and_belongs_to_many :roles
end
class Address < ActiveRecord::Base
belongs_to :client
end
class Order < ActiveRecord::Base
belongs_to :client, counter_cache: true
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :clients
end
一、从数据库中查找数据
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
*
FROM
clients 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!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 time
Invoice.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)
# OR
Client.order(created_at: :asc)
# OR
Client.order("created_at DESC")
# OR
Client.order("created_at ASC")
假如你想先按照orders_count排序,再按照created_at排序,再。。。你只要在参数中依次写出就好了,用法如下
Client.order(orders_count: :asc, created_at: :desc)
# OR
Client.order(:orders_count, created_at: :desc)
# OR
Client.order("orders_count ASC, created_at DESC")
# OR
Client.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_price
FROM orders
GROUP BY date(created_at)
七、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_price
FROM orders
GROUP 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
end
end
十、只读object
函数readonly返回一个只读记录
client = Client.readonly.first
client.visits += 1
client.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_column
end
2.悲观锁
它就是使用数据库底层的互斥锁对访问的数据进行加锁,使用lock函数实现。一般我们把lock函数放在transaction(事务)函数的数据块中。就像下面的使用Item.transaction do
i = Item.lock.first
i.name = 'Jones'
i.save
end
他将产生如下sql
SQL (0.2ms) BEGIN
Item Load (0.3ms) SELECT * FROM `items` LIMIT 1 FOR UPDATE
Item Update (0.4ms) UPDATE `items` SET `updated_at` = '2009-02-07 18:05:56', `name` = 'Jones' WHERE `id` = 1
SQL (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.first
item.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 :posts
end
class Post < ActiveRecord::Base
belongs_to :category
has_many :comments
has_many :tags
end
class Comment < ActiveRecord::Base
belongs_to :post
has_one :guest
end
class Guest < ActiveRecord::Base
belongs_to :comment
end
class Tag < ActiveRecord::Base
belongs_to :post
end
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.midnight
Client.joins(:orders).where('orders.created_at' => time_range)
和下面的是一样的
time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Client.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.postcode
end
乍看起来,这段代码很正常,但是它是有问题的。它查询了多少次?11次,第1次是加载clients;然后是10个clients遍历时,每次都要查询和当前clients相关的address,共十次。但我们可以将此类查询减少到2次,这对于提高系统效率有很重要的作用
clients = Client.includes(:address).limit(10)
clients.each do |client|
puts client.address.postcode
end
简单一个修改,但他仅需要2次查询,因为他用了in代替了10次重复加载。
SELECT * FROM clients LIMIT 10
SELECT 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)
end
end
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)
end
end
你可以这样使用这个函数
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.
end
end
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
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 1
BEGIN
INSERT 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 = false
end
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
Client.connection.select_all("SELECT * FROM clients WHERE id = '1'")
2.pluck
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
十八、object存在性检验
Client.exists?(1)
这个将检测id为1的记录是否存在,同样还可以检测多个id是否存在
Client.exists?(1,2,3)
# or
Client.exists?([1,2,3])
都存在时返回true,否则返回false。
Client.exists?
假如clients表为空,返回false,否则返回true。
# via a model
Post.any?
Post.many?
# via a named scope
Post.recent.any?
Post.recent.many?
# via a relation
Post.where(published: true).any?
Post.where(published: true).many?
# via an association
Post.first.categories.any?
Post.first.categories.many?
十九、计算
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
Client.minimum("age")
4.maxmum
返回最大值,和average用法一样Client.minimum("age")
5.sum
Client.sum("orders_count")