Rails 前方有坑,请注意。。。 蛋疼的 first_or_create

zhangjinzhu · 2015年08月16日 · 最后由 flemon 回复于 2015年08月17日 · 3360 次阅读
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 也并没有什么卵用。。。。。。

问题在哪?

同看不出来是什么问题,所以楼主的帖子也是

也并没有什么卵用

?

生成的 SQL 貌似没有什么不妥,你所期望的 SQL 又是什么?

@rei @martin91 @hz_qiuyuanxin

order.items 不应该是

// SELECT `order_items`.* FROM `order_items` WHERE `order_items`.`order_id` = 99

#4 楼 @zhangjinzhu

你后面加了 where(product_id: 888) 不是么?

@hz_qiuyuanxin 再看看加的条件的位置?

#4 楼 @zhangjinzhu

你后面加了 where(product_id: 888) 不是么?

ActiveRecord 查询的时候,会先生成抽象语法树(AST),最后根据你的代码在运行时生成对应的 sql 语句。 比如 order.items 会生成 SELECTorder_items.* FROMorder_itemsWHEREorder_items.order_id= 99 但是如果是 order.items.where(product_id: 888).first_or_create ,则会生成 SELECTorder_items.* FROMorder_itemsWHEREorder_items.product_id= 888 ANDorder_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) 啊!!!!!!!

#11 楼 @zhangjinzhu 好好看文档去,我不想理你了,88

@hz_qiuyuanxin Show me the 文档? 那里有讲?。。。。。。或者说你明白我说的是啥意思么?

这就是不好好说话,浪费双方时间的例子。

顶楼意思是:

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

@rei 👍

我以为我说的挺明白了,结果。。。。。。。

 # 上面有
 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 了一会才发现

醉了,你这种不接受的态度不适合用框架。 自己从零开始写比较好。

还有 lz 对callback的理解也异乎常人

20 楼 已删除

我觉得我是弱智,继续匿了

#21 楼 @martin91 7 楼的理解绝对正确,谦虚了。

问题是这样子的,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, 我也觉得没什么作用最好别用...

补充楼上的, 文档这里这里显示他们的 scope 是联接的

需要 登录 后方可回复, 如果你还没有账号请 注册新账号