class Order < ActiveRecord::Base
has_many :items, :class_name => "OrderItem"
end
class OrderItem < ActiveRecord::Base
after_create :print_order_items
belongs_to :order
def print_order_items
puts order.items.to_sql
end
end
order = Order.find(99)
order.items.where(product_id: 888).first_or_create
大家能猜中生成的 SQL 是什么么?
// 输出结果
// SELECT `order_items`.* FROM `order_items` WHERE `order_items`.`product_id` = 888 AND `order_items`.`order_id` = 99
注意上面的 product_id 的条件
order.reload.items(true).to_sql 仍然无效哦。。。。。
Order.find(order.id).items.to_sql 也并没有什么卵用。。。。。。
order.items 不应该是
// SELECT `order_items`.* FROM `order_items` WHERE `order_items`.`order_id` = 99
么
你后面加了 where(product_id: 888) 不是么?
ActiveRecord 查询的时候,会先生成抽象语法树(AST),最后根据你的代码在运行时生成对应的 sql 语句。
比如 order.items
会生成 SELECT
order_items.* FROM
order_itemsWHERE
order_items.
order_id= 99
但是如果是 order.items.where(product_id: 888).first_or_create
,则会生成 SELECT
order_items.* FROM
order_itemsWHERE
order_items.
product_id= 888 AND
order_items.
order_id= 99
这些都是正确的啊! 这些都是正确的啊! 这些都是正确的啊!
#6 楼 @zhangjinzhu 你还是讲讲你的需求的,你是想要查询 order.order_items 看有没有,如果没有,就创建一条,然后它的 product_id 为 888,还是说,你想查一查是否存在 order_id: 9, product_id: 888 的 order_items,如果没有就创建一条
@martin91 请认真读一遍!请认真读一遍!请认真读一遍!
@hz_qiuyuanxin 问题是这样子的,first_or_create 里面的条件 .where(product_id: 888)
给应用到 callbacks 中了,并且在 callback 中怎么也去不掉这个 where 条件
order.items
order.reload.items(true)
Order.find(order.id).items
也就是说,不管当前的订单有多少 items, 在 callbacks 中始终只能找出一个。。。。。
#9 楼 @zhangjinzhu 回答我的问题,先把需求描述清楚,再来纠结代码怎么写。
你是想要
if order.items.count == 0
order.items.create product_id: 888
end
还是想要
order.items.find_or_create_by product_id: 888
http://apidock.com/rails/ActiveRecord/Relation/first_or_create http://apidock.com/rails/ActiveRecord/Relation/find_or_create_by
@hz_qiuyuanxin 我只是了提一下这个 unexpected behaviour , 我有好多解决方案来解决这个问题,但仍然抵挡不了这个是个 unexpected behaviour (BUG) 啊!!!!!!!
这就是不好好说话,浪费双方时间的例子。
顶楼意思是:
item = order.items.where(product_id: 888).first_or_create
# 在 item 的回调中
order.items
# => SELECT `order_items`.* FROM `order_items` WHERE `order_items`.`product_id` = 888 AND `order_items`.`order_id` = 99
# 多了一个 product_id
我以为我说的挺明白了,结果。。。。。。。
# 上面有
def print_order_items
puts order.items.to_sql
end
# 下面有
order.reload.items(true).to_sql 仍然无效哦。。。。。
Order.find(order.id).items.to_sql 也并没有什么卵用。。。。。。
#16 楼 @zhangjinzhu 我第一时间看不懂,以为 SELECT
语句是对应最后一行代码。
调试了一下,似乎 find_or_create
语法没有这个问题。试试:
order.items.find_or_create_by(product_id: 888)
没有试过。。。不过这个问题蛮变态的。。。。普通的测试还挺难测试出来。。。。应该就是上面的那个 issue,两年前的问题,这几天又在扯蛋了。。。。。。
我刚刚遇到这个问题以为是 rails 的关联缓存,debug 了一会才发现
问题是这样子的,first_or_create 里面的条件 .where(product_id: 888) 给应用到 callbacks 中了,并且在 callback 中怎么也去不掉这个 where 条件
柱子把这句放到主贴里不就完事了.. :trollface:
总结:scope.first_or_create 出来的对象不一致:
如果是 first 的 就不受 scope 影响了 (expected behavior) 如果是 create 的 callback 中就还会受 scope 作用 (expected behavior)
不过这个方法也不提供 upsert 一样的原子性,就相当于 scope.first or scope.create
, 我也觉得没什么作用最好别用...