以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 |