Rails Rails 中 polymorphic 的使用, 以及获取 unscoped 对象属性

chenyunli · 2017年10月25日 · 最后由 chenyunli 回复于 2017年10月30日 · 818 次阅读

如果有什么错误之处,还请大神们指点。

注意:本文小写的变量都为实例变量,如:employee

使用 polymorphic

场景: EmployeeProducthas_many pictures, 但是又不希望创建两个类似的 pictures 表。也就是我们希望EmployeeProduct 可以同时关联上 Picture, 最后能从 pictures 表中各取所需,也可以在 Picture 的每个实例对象中获得所属对象。最终结果:

  • rails 中
employee.pictures
product.pictures
picture.imageable # 是一个具体的 employee 或者 product 对象
  • 存在表里的是:
id name imageable_id imageable_type
1 sun 1 Product
2 moon 1 Employee
  • 示例代码
class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true
end

class Employee < ApplicationRecord
  has_many :pictures, as: :imageable
end

class Product < ApplicationRecord
  has_many :pictures, as: :imageable
  default_scope { where(published: true) }
end

class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end
    add_index :pictures, [:imageable_type, :imageable_id]
  end
end
  • 注意事项

model 中的 imageable 需要与表中的 imageable_idimageable_type 对应,如果你的是 object_idobject_type,那么 model 中应该是 belongs_to :object, polymorphic: true

覆盖 Picture 中的 imageable

上述代码中,Productscope,当你用 picture.imageable 的时候,会把 scope 也带上,有可能你需要的是所有的 imageable ,而不是 scope 下的,这个时候可以通过覆盖掉 imageable 这个方法来解决。

比如:

class Picture < ApplicationRecord
  belongs_to :imageable, polymorphic: true

  def imageable
    @imageable ||= imageable_type.constantize.unscoped.find_by_id imageable_id
  end
end

这里面通过 imageable_type 获取类名,constantize 把字符串的类名转化为对象,再调用 unscoped 方法,最后通过 imageable_id 找到具体的 imageable 对象。定义 @imageable 是为了一次请求中不多次查询imageable,因为 rails 会缓存。

根据反馈做出调整

  1. 一开始文中写的是 scope,默认是不会被关联查出来的 ,现在改正过来了。
  2. 最好不使用 default_scope
  3. 如果非要使用 default_scope 可以用比文中更好一点的解决方法(文中的方法会导致 includes 无效等问题): belongs_to :imageable, (-> { unscoped }), polymorphic: true
共收到 19 条回复

scope是关联上的scope?看不出你写的scope和关联有什么关系,一开始我以为是default_scope,后来发现关联是会忽视default_scope的。。

你那样改写关联,会导致includes无效等杂七杂八的问题,用||=缓存的话,也要有相应失效的机制

mizuhashi 回复

用 picture.imageable 会带上 product 的 scope ,也就是查出 published 的 product,所以这边覆盖掉是想查出 unscoped 的 product,你说的 includes 会有问题,但是我们这边暂时没涉及。有什么更好的方案么,欢迎指教 😁

chenyunli 回复

说说你的查询语句是怎么写的,Product.published.map{|x| x.picutres.map{|x| x.imageable}} 这样?

mizuhashi 回复

picture.imageable 这样就查出来了啊

chenyunli 回复

问题是你的picture是怎么来的。。Picture.all.map{|x| x.imageable}?这样也是不会带published的啊,还是不清楚要怎么重现

chenyunli 回复

我也很好奇,scope只是定义了一个查询方法,并不会作为默认查询条件,你是怎么查才导致查出 published 的 product。

adamshen 回复

写的是 default_scope , 我改一下,写错了

mizuhashi 回复

default_scope, 不是 scope,文章里面写错了

mizuhashi 回复

default_scope 的话,用 picture.imageable 不会忽略 default_scope 中的条件

chenyunli 回复

不是很推荐这么做,就像楼上说的,会导致includes无效等杂七杂八的问题。

这个本来的imageable方法不是单纯地查找并生成一个关联对象这么简单。而是先通过一个中间对象ActiveRecord::Associations::BelongsToAssociation判断下对象是否已经缓存,没有的话再reset_scope、reload。你通过覆盖这个方法,使原本ar设计好的一些东西失效了,得不偿失。而且你本来只是想使Product的default_scope失效,这样一来,所有imageable找出来的model全部的default_scope都失效了。

adamshen 回复

如果不用覆盖解决,这块还有好的解决方法不?因为不想带着 default_scope。

Products 可以不要 default_scope 吗,让代码少些弯,更好读些?

chenyunli 回复

最好就是不要用default_scope,如果非要用的话,可以试试这样,但也不是很好。

belongs_to :imageable, -> { unscope(:where) }, polymorphic: true
adamshen 回复

嗯嗯,试一试,谢谢😄 😁

doosolar 回复

明白,谢谢啦

16楼 已删除
17楼 已删除
18楼 已删除
adamshen 回复

试了,你说的这个可以实现

最好不要用 default_scope, 尽量不要用 default_scope, 绝壁不要用 default_scope

除非你的这个模型承载的数据在业务中的职能足够单纯,并且不会被扩展或「花式调用」

IChou 回复

知道啦

adamshen 回复

belongs_to :imageable, -> { unscope(:where) }, polymorphic: true 这个我查出来的不对。imageable_id 和 imageable 不对应。

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