EasyTime - Ruby学习笔记
自我介绍
切换风格
订阅我的Blog
博客日历
文章归档...
最新发表...
博客统计...
网站链接...
资源
===========================================================
Agile Web Development with Rails 翻译(二十九)
===========================================================

Agile Web Development with Rails 翻译(二十九)

new()构造函数是在内存中创建一个新的Order对象。我们必须记得在某个时间点上要存储它。Active Record还有一个方便的方法create()。它是既实例化model对象又把它存储到数据库中。


an_order = Order.create(

:name => "Dave Thomas",

:email => "dave@pragprog.com",

:address => "

123 Main St
",

:pay_type => "check")

你还可以给create()传递一个属性哈希表数组;它将在数据库中创建多行记录并返回一个对应的model对象数组。

orders = Order.create(

[ { :name => "Dave Thomas",

:email => "dave@pragprog.com",

:address => "

123 Main St
",

:pay_type => "check"

},

{ :name => "Andy Hunt",

:email => "andy@pragprog.com",

:address => "

456 Gentle Drive
",

:pay_type => "po"

} ] )

new()create()接受一个值的哈希表的真正原因就是你可以从表格参数中直接构造model对象。

order = Order.create(params)

读现有的行

从数据库中读取数据首先就要涉及到确定你所关心的特定的数据行--你必须给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

虽然,通常你需要读入基于一定标准而不是它们主键值的行。Active Record对完成这些查询提供了一个范围选项。我们开始查看低级的find()方法,并最终走向高级的动态查找。

到现在为止,我们只是在表面上接触find()方法,使用它来返回一或多个基于我们做为参数传递给它的id的行。但是,find()还有一些微秒的性格。如果你传递符号:first:all做为第一个参数,则原本不起眼的find()会变成强大的搜索机器。

find():first变量返回匹配一定标准的第一行,而:all则返回一个匹配行的数组。这两种形式接受一组关键字参数,这些参数控制它们能做什么。但在我们查看这些之前,我们需要花几页来解释Active Record是如何处理SQL的。

SQL and Active Record

要演示Active Record如何使用SQL的,让我们行看看find(:all,:conditions=>...)方法调用中的:conditons参数。这个:conditions参数决定了find()返回那些记录行,它对应着SQLwhere子句。比如,要返回符全付款类型”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注入攻击。

强大的 find()

现在已经知道了怎样指定查询条件,让我们关注由find(:first,...)find(:all,...)支持的各种选项。

首先,重要的是理解find(:first,...)生成一个与带有同样条件的find(:all,...)相同的SQL查询语句,区别就在于对结果集的限制是单行还是多行。我们在一个地方描述用于两者的参数,并演示使用这些参数find(:all,…)。我们直接把第一个参数为:first:allfinder方法称作find()

没有其他参数,finder执行的是一个select from ...语句。带有:all形参的返回表中所有记录行,带有:first形参的返回一行。不保证返回记录的次序(因此Order.find(:first)没有必要返回由应用程序创建第一个定单)

-------------------------------------------------------

David Says. . .

But Isn’t SQL Dirty?

Ever since programmers started to layer object-oriented systems on top of relational databases, they’ve struggled with the question of how deep to run the abstraction. Some object-relational mappers seek to eradicate the use of SQL entirely, striving for object-oriented purity by forcing all queries through another OO layer.

Active Record does not. It was built on the notion that SQL is neither dirty nor bad, just verbose in the trivial cases. The focus is on removing the need to deal with the verbosity in those trivial cases (writing a 10-attribute insert by hand will leave any programmer tired) but keeping the expressiveness around for the hard queries—the type SQL was created to deal with elegantly.

Therefore, you shouldn’t feel guilty when you use find_by_sql( ) to handle either performance bottlenecks or hard queries. Start out using the object-oriented interface for productivity and pleasure, and then dip beneath the surface for a close-to-the-metal experience when you need to.

------------------------------------------------------

:conditions参数让你指定传给由find()方法内SQLwhere子句中用到的条件。这个条件即可以是一个包含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])

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

orders = Order.find(:all,

:conditions => "name = 'Dave'",

:order => "pay_type, shipped_at DESC")

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

orders = Order.find(:all,

:conditions => "name = 'Dave'",

:order => "pay_type, shipped_at DESC",

:limit => 10)

: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

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")

就像我们在21614.6节将看到的,你或许不想在find()使用:joins参数--Active Record会为你处理大多数普通的表连接。

还有一个额外的参数是,: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()方法可为你构造一个完整的SQL查询语句。而find_by_sql()方法则允许你的应用程序获得更完全的控制。它只接受包含SQLselect语句作为唯一参数,并且从结果集中返回一个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' ")

只有从查询中返回的这些属性才能在结果的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 xxxSQL语法给定结果集中的一个列的名字,这个名字就将成为属性名。

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]])

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则充分使用Ruby强大的动态特性来为你做这些。

例如,我们的Order model有以下属性如:nameemail,和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'])

--------------------------------------

David Says. . .

To Raise, or Not to Raise?

When you use a finder driven by primary keys, you’re looking for a particular record. You expect it to exist. A call to Person.find(5) is based on our knowledge of the person table. We want the row with an id of 5. If this call is unsuccessful—if the record with the id of 5 has been destroyed—we’re in an exceptional situation. This mandates the raising of an exception, so Rails raises RecordNotFound.

On the other hand, finders that use criteria to search are looking for a match. So, Person.find(:first,:condition=>"name=’Dave’") is the equivalent of telling the database (as a black box), “Give me the first person row that has the name Dave.” This exhibits a distinctly different approach to retrieval; we’re not certain up front that we’ll get a result. It’s entirely possible the result set may be empty. Thus, returning nil in the case of finders that search for one row and an empty array for finders that search for many rows is the natural, nonexceptional response.

-------------------------------------------------------

如果你调用一个model的类方法,它的名字以find_by_find_all_by开头的话,Active Record会把它转换成一个finder,并使用方法名的余下部分来决定哪些列会被检查。因此下面这个调用

order = Order.

my4java 发表于:2006.04.15 13:51 ::分类: ( Ruby on Rails ) ::阅读:(1615次) :: 评论 (0) :: 引用 (0)

发表评论
标题

在此添加评论

称呼

邮箱地址(可选)

个人主页(可选)

authimage