one_to_one relationship explain and notice

以Agile Web Development with Rails书中关于has_one关系的order和invoice关系做个例子,其中设了一个total_price字段在invoice表中,不能为空,当做以下操作时:

o = Order.find(1)i = Invoice.newo.invoice = i

这个时候Rails会自动保存i这个Invoice对象,保存过和书里有插图说明,先将原来记录的order_id置为NULL,再插入些记录。而此记录在数据库里是设定了非空,所以不会插入成功。
得到的log如下:

Order Load (0.002154)   SELECT * FROM orders WHERE (orders.`id` = 1)Invoice Load (0.002144)   SELECT * FROM invoices WHERE (invoices.order_id = 1) LIMIT 1SQL (0.000859)   BEGINInvoice Update (0.002496)   UPDATE invoices SET `created_at` = '2007-08-26 17:21:30', `total_price` = '9.0', `order_id` = NULL WHERE `id` = 1SQL (0.003602)   COMMITSQL (0.000605)   BEGINSQL (0.000000)   Mysql::Error: Column 'total_price' cannot be null: INSERT INTO invoices (`order_id`, `total_price`, `created_at`) VALUES(1, NULL, '2007-08-26 18:07:21')SQL (0.000865)   ROLLBACK

这就造成了数据问题,在这里是数据库做限制,所以程序出错还可以知道出错,如果是通过validate或者before_save过滤器过滤的话,就很难查觉数据没有写入库中,所以这种写法书里不推荐,而是先强制保存子对象i再保存父对象o。
或者写入事务中:

Order.transaction do   o.invoice = iend

程序还是会报错,因为数据库写不进去的。可以再begin..end捕获错误。

>> o = Order.new do |x|?> x.name = 'a'>> x.email = 'a@test.com'>> x.address = 'Shanghai'>> end=> #"a", "updated_at"=>nil, "pay_type"=>nil, "address"=>"Shanghai", "created_at"=>nil, "email"=>"a@test.com"}>>> i = Invoice.new=> #nil, "total_price"=>nil, "created_at"=>nil}>>> i.order = o=> #"a", "updated_at"=>nil, "pay_type"=>nil, "address"=>"Shanghai", "created_at"=>nil, "email"=>"a@test.com"}>>> i.saveActiveRecord::StatementInvalid: Mysql::Error: Column 'total_price' cannot be null: INSERT INTO invoices (`order_id`, `total_price`, `created_at`) VALUES(5, NULL, '2007-08-26 18:36:24')

one_to_one relationship中保存子对象i时会先保存其父对象o,而保存父对象o则子对象不会一起保存。以上例子产生的log如下:

SQL (0.000750)   BEGINSQL (0.080562)   INSERT INTO orders (`name`, `updated_at`, `pay_type`, `address`, `created_at`, `email`) VALUES('a', '2007-08-26 18:36:23', NULL, 'Shanghai', '2007-08-26 18:36:23', 'a@test.com')SQL (0.000000)   Mysql::Error: Column 'total_price' cannot be null: INSERT INTO invoices (`order_id`, `total_price`, `created_at`) VALUES(5, NULL, '2007-08-26 18:36:24')SQL (0.005971)   ROLLBACK

Rails 是将其包装在一个事务里处理的,所以不会生成一条新的order记录。

BTW:
在has_many关系中与上面has_one是相反的,当保存了父对象之后会遍历父对象中的全部子对象并保存,如果父对象和子对象都是新建对象,不能先保存子对象,因为无法取到父对象的id。如下例子说明:

order = Order.new[1, 2, 3].each do |prd_id|  product = Product.find(prd_id)  order.line_items << LineItem.new(:product =>product, :quantity => 2, :total_price => 3.22)endorder.save

产生Log如下:

Product Load (0.002137)   SELECT * FROM products WHERE (products.`id` = 1)SQL (0.023867)   BEGINSQL (0.002860)   COMMITProduct Load (0.001754)   SELECT * FROM products WHERE (products.`id` = 2)SQL (0.066274)   BEGINSQL (0.001553)   COMMITProduct Load (0.002514)   SELECT * FROM products WHERE (products.`id` = 3)SQL (0.001669)   BEGINSQL (0.000442)   COMMITSQL (0.000661)   BEGINSQL (0.003558)   INSERT INTO orders (`name`, `updated_at`, `pay_type`, `address`, `created_at`, `email`) VALUES(NULL, '2007-08-26 19:30:18', NULL, NULL, '2007-08-26 19:30:18', NULL)SQL (0.002258)   INSERT INTO line_items (`order_id`, `updated_at`, `total_price`, `product_id`, `quantity`, `created_at`) VALUES(9, '2007-08-26 19:30:18', '3.22', 1, 2, '2007-08-26 19:30:18')SQL (0.002302)   INSERT INTO line_items (`order_id`, `updated_at`, `total_price`, `product_id`, `quantity`, `created_at`) VALUES(9, '2007-08-26 19:30:18', '3.22', 2, 2, '2007-08-26 19:30:18')SQL (0.004991)   INSERT INTO line_items (`order_id`, `updated_at`, `total_price`, `product_id`, `quantity`, `created_at`) VALUES(9, '2007-08-26 19:30:18', '3.22', 3, 2, '2007-08-26 19:30:18')SQL (0.069437)   COMMIT
order = Order.newproduct = Product.find(1)li = LineItem.new(:product =>product, :quantity => 2, :total_price => 3.22)order.line_items << lili.save

产生Log如下:

Product Load (0.002695)   SELECT * FROM products WHERE (products.`id` = 1)SQL (0.023173)   BEGINSQL (0.000987)   COMMITSQL (0.000777)   BEGINSQL (0.000000)   Mysql::Error: Column 'order_id' cannot be null: INSERT INTO line_items (`order_id`, `updated_at`, `total_price`, `product_id`, `quantity`, `created_at`) VALUES(NULL, '2007-08-26 19:34:15', '3.22', 1, 2, '2007-08-26 19:34:15')SQL (0.002174)   ROLLBACKSQL (0.003055)   BEGINSQL (0.001734)   COMMIT