在Active Record中,通过在父对象中的has_many来定义到子对象的关联,在子对象中使用belongs_to来指定父对象。我们已经在上一篇中了解了belongs_to声明,实际上,在一对多的情况下,和一对一是相同的,所以我们来了解has_many声明。
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的line_items列表中追加一个对象,并且和设置了line_item对象外键值为order对象主键值,而且在order对象保存的时候,会同时保存line_item对象。
我们可以像对数组一样对has_many关联进行循环:
order = Order.find(123)
total = 0.0
order.line_items.each do |li|
total += li.quantity * li.unit_price
end
和has_one一样,我们可以改变Active Record的默认形式,我们可以给has_many一组设定,:class_name,:foreign_key,:conditions,:order和:dependent和has_one中的是一样的,has_many还有:exclusively_dependent,:finder_sql,:counter_sql。
has_one和has_many都支持:dependent,这告诉Rails在删除主表中记录的同时删除从表中对应的记录。也就是对所有外键为删除的主记录的id的子对象调用它们destory方法。
无论如何,如果从表仅仅和主表关联,而没有其他表关联,而且没有任何钩子方法在删除的时候执行操作,你可以使用:exclusively_dependent来代替:dependent,在这种情况下,对于所有的子记录会使用一条sql语句来删除,这样执行速度会快一点。
你还可以通过使用:finder_sql和:counter_sql设定来复写Active Record用来对子记录进行查询和计数的sql。在:conditions设定不足够的情况下,这两个设定就很有用了。例如:
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用作计算行数的sql,如果:finder_sql被指定了,但是:counter_sql没有指定,Active Record将会根据查询sql来替换计算行数的sql。
:order设定指定了从数据库中选出的记录的排序所使用的sql,如果你在遍历记录集的时候需要有特定的排序,你就需要指定:order,也就是设定sql语句的order by部分,默认情况下,所有的字段都是升序。例如:
class Order < ActiveRecord::Base
has_many :line_items,
:order => "quantity, unit_price DESC"
end
现在再回到has_one声明,我们前面提到过也支持:order,但是一条父记录最多只有一条子记录,那么为什么要支持:order来允许指定排序条件呢?想象一下这样的场景,一个用户可能有多条订单,但是,如果我们想查看该用户的最新一条订单呢?这时候,我们就可以使用has_one声明了:
class Customer < ActiveRecord::Base
has_many :orders
has_one :most_recent_order,
:class_name => 'Order',
:order => 'created_at DESC'
end
上面的代码创建了一个新属性:most_recent_order,将会引用到该用户的最新一条订单,我们可以这样使用它:
cust = Customer.find_by_name("Dave Thomas")
puts "Dave last ordered on #{cust.most_recent_order.created_at}"
实际上,Active Record执行了下面的sql:
SELECT * FROM orders
WHERE customer_id = ?
ORDER BY created_at DESC
LIMIT 1
根据前面我们学习的find方法,可以看到只取了排序后的第一条记录。
has_many添加的方法
就像belongs_to和has_one,has_many也添加了一组属性相关的方法在它所在的类中,下面我们来看看这些方法,我们的声明是这样
class Customer < ActiveRecord::Base
has_many :orders
end
l orders(force_reload=false)=
返回一个和用户关联的订单的数组,结果集是被缓存的,对于相同的查询,不会再次从数据库提取数据,除非force_reload = true。
l orders <<order
添加订单到指定用户的订单的列表中。
l orders.push(order1, ...)
添加一个或多个订单对象到用户的订单列表中,concat()是该方法的别名。
l orders.delete(order1, ...)
从用户的订单列表中删除一个或多个订单,但是不会删除数据库中对应的记录,只是将它们的customer_id外键设置为null,断开和用户的关联。
l orders.clear
分离用户和订单,就像delete(),但是如果订单被指明为:dependent,数据库中对应的记录就会被删除。
l orders.find(options...)
发出一个find()调用,但是仅返回用户关联的订单。
l orders.build(attributes={})
构造一个新的order对象,用给定的属性初始化,并且关联到customer,该对象没有保存。
l orders.create(attributes={})