CRUD—Read

从数据库中读取数据首先就要涉及到确定你所关心的特定的数据行--你必须给Active
Record 一些条件值,它将返回包含匹配数据行的对象。
查找表中的一行记录最简单的方法是指定它的主键。每个model 类都支持find()方法,
它接受一个或多个主键的值作为参数。如果只是给定一个主键,它返回一个对象,该对象包
含对应记录行的数据(或者是抛出RecordNotFound 异常)。如果给定了多个主键,find()返回一个对应的对象数组。注意在这种情况下,如果任何一个id 没有找到(所以如果方法返回并没引发一个错误,结果数据的长度将等于被做为参数传递的id 数量),就会返回RecordNotFound 异常。
an_order = Order.find(27) # find the order with id == 27
# Get a list of order ids from a form, then
# sum the total value
order_list = params[:order_ids]
orders = Order.find(order_list)
count = orders.size
1、
虽然,通常你需要读入基于一定标准而不是它们主键值的行。Active Record 对完成这
些查询提供了一个范围选项。我们开始查看低级的find()方法,并最终走向高级的动态查找。到现在为止,我们只是在表面上接触find()方法,使用它来返回一或多个基于我们做为
参数传递给它的id 的行。但是,find()还有一些微秒的性格。如果你传递符号:first 或:all
做为第一个参数,则原本不起眼的find()会变成强大的搜索机器。
find()的:first 变量返回匹配一定标准的第一行,而:all 则返回一个匹配行的数组。这
两种形式接受一组关键字参数,这些参数控制它们能做什么。但在我们查看这些之前,我们
需要花几页来解释Active Record 是如何处理SQL 的。
2、
SQL and Active Record
要演示Active Record 如何使用SQL 的,让我们行看看
find(:all,:conditions=>...)
方法调用中的:conditons 参数。这个:conditions 参数决定了find()返回那些记录行,它对应着SQL 的where 子句。比如,要返回符全付款类型”po”的所有order 列表给Dave,你应该使用pos = Order.find(:all,:conditions => "name = 'dave' and pay_type = 'po'")
结果是所有匹配记录的一个数组。每一个都被包装成Order 对象。如果没有orders 匹配
这个条件,数组将是空的。最好你能将你的条件预先定义好,但是怎样处理来自外部的客户名字的所在位置呢(也许来自一个web 表单)?一种途径是替换条件字符串中的那个变量值。
# get the limit amount from the form
name = params[:name]
# DON'T DO THIS!!!
pos = Order.find(:all,:conditions => "name = '#{name}' and pay_type = 'po'")

但是上面的做法并不是好。为什么呢?因为它会导致你的数据库打开之后出现SQL 注射
攻击。更多的细节会在427 页的21 章节描述。现在只要知道从一个外部SQL 语句源来替换字符串,会将你的整个数据库暴露给全世界。相反,最安全的途径是产生动态的SQL 语句,并让Active Record 处理它。无论何时你都可以在传递一个包含SQL 语句的字符串的地方,你也可以传递一个数组。数组地第一个元素是个包含SQL 的字符串,用这个SQL,你可以嵌入占位符,它在运行中可以被数组的其他值所替换。

指定占位符的一个途径是在SQLl 中插入一个或多个问号。第一个问号标记它可被数组的
第二个元素代替,下一个被第三个代替,依次类推。例如,我们应该重写前面的查询为
name = params[:name]
pos = Order.find(:all,:conditions => ["name = ? and pay_type = 'po'", name])
你也可以使用有名字的占位符。每个占位符形同:name,并且对应的值是作为一个哈希表
来提供的,哈希表中的key 对应查询中的名字。
name = params[:name]
pay_type = params[:pay_type]
pos = Order.find(:all,:conditions => ["name = :name and pay_type = :pay_type",{:pay_type => pay_type, :name => name}])

你还可以在简单些。因为params 可是个有效哈希表,你可以简单传递哈希表字面符给条
件就行了。
pos = Order.find(:all,:conditions => ["name = :name and pay_type = :pay_type", params])
不管你使用占位符的哪种形式,Active Record 都会小心处理在SQL 中出现的双引号和
转义的值。使用这种动态SQL 形式,Active Record 可避免SQL 注入攻击。

3、
现在已经知道了怎样指定查询条件,让我们关注由find(:first,...)和find(:all,...)
支持的各种选项。
首先,重要的是理解find(:first,...)生成一个与带有同样条件的find(:all,...)相同
的SQL 查询语句,区别就在于对结果集的限制是单行还是多行。我们在一个地方描述用于两
者的参数,并演示使用这些参数find(:all,…)。我们直接把第一个参数为:first 和:all 的finder 方法称作find()。
没有其他参数,finder 执行的是一个select from ...语句。带有:all 形参的返回表中
所有记录行,带有:first 形参的返回一行。不保证返回记录的次序
(因此Order.find(:first)没有必要返回由应用程序创建第一个定单)。
:conditions 参数让你指定传给由find()方法内SQL 的where 子句中用到的条件。这个
条件即可以是一个包含SQL 的字符串,也可以是包含SQL 和替换值的数组,正如前一章所描
述的。(从现在起,我们将不再特别提醒SQL 参数的区别,都假设方法即可以接受字符串也
可以接受数组。)
daves_orders = Order.find(:all, :conditions => "name = 'Dave'")
name = params[:name]
other_orders = Order.find(:all, :conditions => ["name = ?", name])
yet_more = Order.find(:all,
:conditions => ["name = :name and pay_type = :pay_type",params])

(:order)
SQL 不保证记录行以什么次序返回,除非你明确地给查询增加一个order by 子句。:
order 参数就是让你指定你通常添加到order by 关键字后面的条件。例如,下面查询将返回
所有Dave 的定单,第一排序是付款类型,然后是发货日期(升序)。
orders = Order.find(:all,
:conditions => "name = 'Dave'",
:order => "pay_type, shipped_at DESC")

(:limit)
你还可以使用:limit 参数,限制find(:all,...)返回的记录行的数目。如果你使
用:limit 参数,你或许也想指定排序以确保得到一致的结果。例如,下面返回前10 个匹配
的定单。
orders = Order.find(:all,
:conditions => "name = 'Dave'",
:order => "pay_type, shipped_at DESC",
:limit => 10)

(:offset)减去偏移量剩下的数据
:offset 参数是要和:limit 参数一起使用的。它允许你指定由find()返回的结果集中第
一个记录的偏移量。
# The view wants to display orders grouped into pages,
# where each page shows page_size orders at a time.
# This method returns the orders on page page_num (starting
# at zero).
def Order.find_on_page(page_num, page_size)
find(:all,:order => "id",:limit => page_size,:offset => page_num*page_size)
end

(:joins)
finder 方法的:joins 参数可让你指定一个连接缺省表的附加表的一个列表。这个参数在
model 表名之后和条件之前被插入到SQL 中。join 语法是基于数据库形式的。下面代码返回
名为Programming Ruby 的书的所有商品项目列表。
LineItem.find(:all,:conditions => "pr.title = 'Programming Ruby'",
:joins => "as li inner join products as pr on li.product_id = pr.id")
就像我们在216 页14.6 节将看到的,你或许不想在find()使用:joins 参数--Active
Record 会为你处理大多数普通的表连接。

(:include)
还有一个额外的参数是,:include,如果你有关联定义的话,就要用到。我们234 页谈
论它。
find(:all,...)方法返回一个model 对象数组。相反如果你只想返回一个对象,可使用
find(:first,...)。它接受与:all 形式一样的参数,但是:limit 参数的值被强制设成1,所以只会有一行被返回。
# return an arbitrary order
order = Order.find(:first)
# return an order for Dave
order = Order.find(:first, :conditions => "name = 'Dave Thomas'")
# return the latest order for Dave
order = Order.find(:first,
:conditions => "name = 'Dave Thomas'",
:order => "id DESC")
如果给find(:first,…)使用条件,结果是从表中选择多行数据返回,这些记录的第一
被返回。如果没有行被选择,返回nil。

(find_by_sql)
find()方法可为你构造一个完整的SQL 查询语句。而find_by_sql()方法则允许你的应
用程序获得更完全的控制。它只接受包含SQL 的select 语句作为唯一参数,并且从结果集中
返回一个model 对象数组(可能空的)。这些model 的属性由查询返回得到的结果列名设置。
你通常可以使用select * 形式返回表的所有列,但这不必须的。[如果你的查询中使用主键
列,你会失败,你不能写从model 返回到数据库的update 语句。看276 页的15.7 节。]
orders = LineItem.find_by_sql("select line_items.* from line_items, orders "+" where order_id = orders.id " +
" and orders.name = 'Dave Thomas' ")

(attributes,attribute_names,attribute_presents?)
只有从查询中返回的这些属性才能在结果的model 对象中使用。你可以使用
attributes(),attribute_names(),attribute_present?()等方法来看model 对象哪些属性可用。第一个返回一个属性的(名/值)对的哈希表,第二个是名字数组,第三个是如果一个有名字的属性在model 可用的话,返回true。
orders = Order.find_by_sql("select name, pay_type from orders")
first = orders[0]
p first.attributes
p first.attribute_names
p first.attribute_present?("address")
这是输出
{"name"=>"Dave Thomas", "pay_type"=>"check"}
["name", "pay_type"]
false

find_by_sql()也可以通过包含派生出来的列数据创建model 对象。如果你使用as xxx
的SQL 语法给定结果集中的一个列的名字,这个名字就将成为属性名。
items = LineItem.find_by_sql("select *, " +
" quantity*unit_price as total_price, " +
" products.title as title " +
" from line_items, products " +
" where line_items.product_id = products.id ")
li = items[0]
puts "#{li.title}: #{li.quantity}x#{li.unit_price} =>
#{li.total_price}"
你也可以给find_by_sql()带上条件,即传递一个数组,第一个元素是一个包含占位符
的字符串。数组余下的部分即可以是哈希表也可以是用于替换的哈希表的字面值列表。
Order.find_by_sql(["select * from orders where amount > ?",
params[:amount]])

(count,count_by_sql)
Counting Rows
Aactive Record 定义了两个类方法来返回匹配的记录行数。count()返回匹配给定条件
的行数,没有条件给出所有行。count_by_sql()返回由sql 语句( select count(*) from ...)
生成的行数。
c1 = Order.count
c2 = Order.count(["name = ?", "Dave Thomas"])
c3 = LineItem.count_by_sql("select count(*) " +
" from line_items, orders " +
" where line_items.order_id = orders.id " +
" and orders.name = 'Dave Thomas' ")
puts "Dave has #{c3} line items in #{c2} orders (#{c1} orders in
all)"

Dynamic Finders
可能在数据库中最通常的查找是根据匹配的列值返回记录行。一个查询可能返回所有记
录给Dave,或者是所有符合主题”Rails”的记录。在其他语言和框架中,你要构造SQL 语
句执行这些查找。Active Record 则充分使用[color=red]Ruby 强大的动态特性来为你做这些。[/color]
例如,我们的Order model 有以下属性如:name,email,和address。我们可以在finder方法中使用这些名字,并返回那些相应匹配这些值的列的记录行。
order = Order.find_by_name("Dave Thomas")
orders = Order.find_all_by_name("Dave Thomas")
order = Order.find_all_by_email(params['email'])
如果你调用一个model 的类方法,它的名字以find_by_或find_all_by 开头的话,Active Record 会把它转换成一个finder,并使用方法名的余下部分来决定哪些列会被检查。因此下面这个调用
order = Order.find_by_name("Dave Thomas", other args...)
会由Active Record 有效地转换成
order = Order.find(:first,
:conditions => ["name = ?", "Dave
Thomas"],other_args...)
类似的,调用find_all_by_xxx 转换成find(:all,...)调用。
这种魔术并不止这些。Active Record 还将创建查找多列的finder 方法。例如,你可以

user = User.find_by_name_and_password(name, pw)
它相当于
user = User.find(:first,
:conditions => ["name = ? and password = ?", name, pw])
为决定要查询的列名,Active Record 简单地把find_by_或者find_all_by_所环绕的字
符串_and_的名字进行分解。这对大多数时侯是有效的,除非你有诸如tax_and_shipping 的
列名。这种情况下,你必须遵守finder 方法的命名约定。
目前为止,没有find_by_格式来让你在列名字之间使用_or_而不是_and_。
重新加载数据
在一个应用程序中,它的数据库很可能潜在被多个进程访问(或被多个应用程序访问),
在这些应用中总是存在这种可能:获取的model 对象已经是很久以前的数据了--因为有人已
经把新值写入了数据库。
这个问题可由事务处理机制(在237 页讨论它)来解决。但还是有可能需要人工的来更新
model 对象。Active Record 使得这种事情变得很容易,只需简单地调用reload()方法,并且对象的属性将马上从数据库中得到更新。
stock = Market.find_by_ticker("RUBY")
loop do
puts "Price = #{stock.price}"
sleep 60
stock.reload
end
根据实践来看,reload()基本上是在测试单元中使用,其他情况很少使用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值