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

zhangjinzhu · August 16, 2015 · Last by flemon replied at August 17, 2015 · 3360 hits
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 Floor has deleted

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

#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 是联接的

You need to Sign in before reply, if you don't have an account, please Sign up first.