wengjinbao

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

belongs_to() 声明

belongs_to()声明给定的类是本身类的父类。尽管belongs_to可能不是我们考虑这种关系时最先想到的词,但是Active Record的约定是包含外键的表属于它引用的表。如果这有助于编码的话,你应该把references当成belongs_to使用。


我们假定父类的名字是属性名大小写混合的单数形式,外键字段是附加_id的属性名的单数形式。所以,给出下面代码

class LineItem < ActiveRecord::Base

belongs_to :product

belongs_to :invoice_item

end

Active Record把商品项目和类ProductInvoiceItem相关联。(特别注意:其中的单复数形式的变化。)其中隐含的意思是,它使用了外键product_idinvoice_item_id来引用表productsinvoice_itemsid列。

还可以覆写这些,其它人会假设传给belongs_to()一个选项的哈希表散列值改变这些假定。

class LineItem < ActiveRecord::Base

belongs_to :paid_order,

:class_name => "Order",

:foreign_key => "order_id",

:conditions => "paid_on is not null"

end

这个例子中,我们创建了一个paid_order的关联,它是类Order的引用(对应的表orders)。这连接是通过order_id外键建立的,但是它还是有条件限制的,如果目标行的paid_on列不为null,它将找到一个定单。这个例子中,我们并没有使用对line_items表内的单个列直接映射来建立连接。

belongs_to()方法创建许多管理连接的实例方法。这些方法都是以连接名开头来命名,例如:

item = LineItem.find(2)

# item.product is the associated Product object

puts "Current product is #{item.product.id}"

puts item.product.title

item.product = Product.new(:title => "Advanced Rails",

:description => "...",

:image_url => "http://....jpg",

:price => 34.95,

:date_available => Time.now)

item.save!

puts "New product is #{item.product.id}"

puts item.product.title

如果我们运行它(用一个适当的数据连接),可能会有以下的结果。

Current product is 2

Programming Ruby

New product is 37

Advanced Rails

我们在LIneItem类中使用方法product()product!=()来存取和更新与product对象关联的商品项目对象。在背后,Active Record是和数据库的机制是一样的。当我们保存对应的商品项目时,它自动保存创建的新product。并把新的productid号和商品项目连接。

belongs_to()会把方法添加到使用它的类中。这个描述是基于以下的假定,已定义的LineItem类属于类Product

class LineItem < ActiveRecord::Base

belongs_to :product

end

在这个例子中,下面方法将被定义用于商品项目,以及它们属于的products

1product(force_reload=false) 返回关联的产品(如果没有关联的产品存在,则返回nil)。结果被缓存,如果这个定单先前被取出过,则数据库不会再次查询,除非将true做为一个参数传递给它。

2product=(obj) 用给出的产品关联这个商品项目,在这个商品项目内设置外键给产品的主键。如果产品没有被保存,在商品项目被保存时它将被保存,键将在那时被连接。

3build_product(attributes={}) 构造一个新的product对象,并用给定的属性初始化它。这个商品项目将被连接给它。Product也不会被保存。

4create_product(attributes={}) 构建一个新的product对象,连接这个商品项目给它,然后保存这个product

has_one() 声明

has_one声明一个给定的类(缺省是混合大小写的属性名字的单数形式)是这个类的子类。has_one声明和belongs_to一样定义同一个方法集,因此给定类定义例如:

class Order < ActiveRecord::Base

has_one :invoice

end

我们可写成

order = Order.new

invoice = Invoice.new

if invoice.save

order.invoice = invoice

end

你可以通过给has_one传递一个选项的哈希表,来改变Active Record的默认行为。另外对于:class_name:foreign_key,和:conditions选项,我们可以看到belongs_to(),我们也可以使用:dependent: order

:dependent选项是指子表中的记录行不能独立于对应的父表记录行而单独存在。这就意味着如果你删除了父类的记录,而且你定义了:dependent=>true的话,Active Record将会自动删除子表中相关的记录行。

: order选项,是决定记录返回之前怎样排序。这似乎有点奇怪。我们在277页的has_many中还会讨论。

一对多关系

一对多关系允许你描述一个对象集合。例如,一个定单可能有多个关联的商品项目。在数据库中,对于一特定定单的所有商品项目行包含一个外键列来引用这个定单。

Active Record中,父对象(逻辑上是包含子对象的一个集合)使用has_many来声明对子表的关系,子表用belongs_to来表明它的父类。在我们的例子中,类LineItembelongs_to :orderordershas_many :line_items

我们已经在前面说了belongs_to()关系声明。它和在一对一关系的处理上是一样的。只是has_many声明添加了一些功能给它的model

has_many() 声明

has_many定义了一个属性,它的行为就像子对象集合一样。你可以把子对象当做一个数据来访问,查找特定的子对象,并可添加新子对象。例如,下面代码添加一些商品项目给一个定单。

order = Order.new

params[:products_to_buy].each do |prd_id, qty|

product = Product.find(prd_id)

order.line_items << LineItem.new(:product => product,:quantity => qty)

end

附加操作符(<<)不仅仅是把一个对象附加到order列表中去。它还通过设置它们的外键给这个定单的id 来将商品项目连接回到这个定单中,并且当父类定单被保存时,商品项目也会自动保存。

我们可以迭代具有has_many关系的子类--属性的行为像个数组。

order = Order.find(123)

total = 0.0

order.line_items.each do |li|

total += li.quantity * li.unit_price

end

has_one一样,你也可以提供给has_many一组哈希表选项来改变Active Record的默认行为。选项:class_name:foreign_key:conditions:class_name:order:dependent工作与在has_one内是一样的。has_many还有:exclusively_dependent:finder_sql,和:counter_sql。我们也讨论:order选项,我们只是列出它,但是在has_one中讨论它。

has_onehas_many都支持:dependent选项。当你要销毁父表中的一个记录行时,:dependent就会告诉Rails销毁子表中的记录行。它通过遍历子表,在带有外键信息的每一个记录行上调用destroy(),并删除父表中的记录。

然而,如果子表只是被父表用到(也就是说,没有其他的依赖关系),并且它没有完成任何删除操作这样的钩子方法,你可以使用:exclusively_dependent来代替:dependent选项。如果这个选项被设置了,子表的记录行就要在一个SQL语句中来执行删除。

最后,你可以覆写Active Record用来获取和计数子表记录行的SQL,这要通过设置:finder_sql:counter_sql两个选项来做到。在我们碰到where子句中使用:condition不够用时,这两个选项是用的上了。例如,你可以为一个特定产品创建所有商品项目的一个集合。

class Order < ActiveRecord::Base

has_many :rails_line_items,

:class_name => "LineItem",

:finder_sql => "select l.* from line_items l, products p " +

" where l.product_id = p.id " +

" and p.title like \'%rails%\'"

end

:counter_sql选项被用来覆盖Active Record中用来计数的查询语句。如果:finder_sql被指定而且:counter_sql没有被指定,Activer Record是使用select count(*)来替换finder SQL语句中的select部分,来合成countersql语句。

: order选项是指定一个SQL片断,它将为从数据库中加载的行定义一个次序后加入到集合中。如果你需要一种特殊顺序来遍历集合,你就要指定: order选项。你给出的SQL片断是简单的文本,它将出现在一个select语句的order by子句之后。它是由包含了一个或多个列名字组成的列表。这个集合将按列表中的第一个列排序。如果两个记录行有相同的列值,决定它们次序的是列表中的第二个列名,以此类推。默认的排序是升序,可以使用DESC来降序排序。

下面代码可以被用于指定一个次序的商品项目,它以数量次序被排序(最小的数量在前面)

class Order < ActiveRecord::Base

has_many :line_items,

:order => "quantity, unit_price DESC"

end

如果两个商品项目有同样的数量,那么单价最高的被放到前面。

回过头想想,我们讨论过的has_one是,我们也提到它也支持一个: order选项。它看起来有点奇怪-如果一个父类只和一个子类关联,那当获取那个子类时,指定的是哪个order呢?

Active Record 可以不在现在的基础数据库上创建has_one关系。例如,一个customer可能有很多orders:这是一个has_many关系。但是customer将只有一个最近的order。我们可以使用has_one: order的结合来表示这个关系:

class Customer < ActiveRecord::Base

has_many :orders

has_one :most_recent_order,

:class_name => \'Order\',

:order => \'created_at DESC\'

end

这段代码在customermodel中创建一个新的属性,most_recent_order。它将指向最近created_at时间戳的order。我们可以用这个属性来查找customer的最近order

cust = Customer.find_by_name("Dave Thomas")

puts "Dave last ordered on #{cust.most_recent_order.created_at}"

这里的代码能起作用,是因为Active Record实际上是使用这样的SQL语句来获取有has_one关联的数据。

SELECT * FROM orders

WHERE customer_id = ?

ORDER BY created_at DESC

LIMIT 1

limit子句意思是只返回一个记录行,以符合has_one声明。order by子句确保记录行是最近的记录。

分类:

技术点:

相关文章: