2016-12-13 40 views
11

我很少发现如何在rails中为多态关联编写范围,更不用说如何编写关于多态关联的查询。具有多态关联的范围和查询

在Rails文档中,我查看了Polymorphic Associations sectionJoining Tables sectionScopes section。我也做了我的公平份额谷歌搜索。

采取这种设置,例如:

class Pet < ActiveRecord::Base 
    belongs_to :animal, polymorphic: true 
end 

class Dog < ActiveRecord::Base 
    has_many :pets, as: :animal 
end 

class Cat < ActiveRecord::Base 
    has_many :pets, as: :animal 
end 

class Bird < ActiveRecord::Base 
    has_many :pets, as: :animal 
end 

所以一个Pet可以是animal_type “狗”, “猫”,或 “鸟”。

来显示所有的表结构:这里是我的schema.rb

create_table "birds", force: :cascade do |t| 
    t.datetime "created_at", null: false 
    t.datetime "updated_at", null: false 
end 

create_table "cats", force: :cascade do |t| 
    t.integer "killed_mice" 
    t.datetime "created_at", null: false 
    t.datetime "updated_at", null: false 
end 

create_table "dogs", force: :cascade do |t| 
    t.boolean "sits" 
    t.datetime "created_at", null: false 
    t.datetime "updated_at", null: false 
end 

create_table "pets", force: :cascade do |t| 
    t.string "name" 
    t.integer "animal_id" 
    t.string "animal_type" 
    t.datetime "created_at", null: false 
    t.datetime "updated_at", null: false 
end 

然后我继续做了一些记录:

Dog.create(sits: false) 
Dog.create(sits: true) 
Dog.create(sits: true) #Dog record that will not be tied to a pet 
Cat.create(killed_mice: 2) 
Cat.create(killed_mice: 15) 
Cat.create(killed_mice: 15) #Cat record that will not be tied to a pet 
Bird.create 

然后我去了,做了一些pet记录:

Pet.create(name: 'dog1', animal_id: 1, animal_type: "Dog") 
Pet.create(name: 'dog2', animal_id: 2, animal_type: "Dog") 
Pet.create(name: 'cat1', animal_id: 1, animal_type: "Cat") 
Pet.create(name: 'cat2', animal_id: 2, animal_type: "Cat") 
Pet.create(name: 'bird1', animal_id: 1, animal_type: "Bird") 

而且,是设置!现在最难的部分是:我想在Pet模型上创建一些范围,以挖掘多态关联。

这里有一些领域,我想这样写:

  • 给我你所有animal_type ==“狗”,可以坐
  • 给我animal_type的所有PetsPets ==“猫”已杀死至少10只老鼠
  • 给我所有的Pets不是既animal_type“狗”,也不能坐。 (换句话说:给我所有的宠物:所有的人:除了狗,不能坐)

所以在我Pet模式,我希望把我的范围在那里:

class Pet < ActiveRecord::Base 
    belongs_to :animal, polymorphic: true 

    scope :sitting_dogs, -> {#query goes here} 
    scope :killer_cats, -> {#query goes here} 
    scope :remove_dogs_that_cannot_sit, -> {#query goes here} #only removes pet records of dogs that cannot sit. All other pet records are returned 
end 

我发现编写这些示波器非常困难。

我在网上找到的一些东西使它看起来像只能用原始SQL写这些范围。我想知道是否可以使用这些范围的哈希语法来代替。

任何提示/帮助将不胜感激!

回答

1

检讨以前的回答并玩了之后:这里是我得到了什么工作。

(注意Pet.remove_dogs_that_cannot_sit返回一个数组。此类方法是可读的但是具有慢由于N + 1的缺点。用于固定任何建议,将不胜感激。)

class Dog < ActiveRecord::Base 
    has_many :pets, as: :animal 
    scope :sits, -> {where(sits: true)} 
end 

class Cat < ActiveRecord::Base 
    has_many :pets, as: :animal 
    scope :killer, ->{ where("killed_mice >= ?", 10) } 
end 

class Pet < ActiveRecord::Base 
    belongs_to :animal, polymorphic: true 

    scope :by_type, ->(type) {where(animal_type: type)} 
    scope :by_dogs, -> {by_type("Dog") } 
    scope :by_cats, -> {by_type("Cat") } 

    def self.sitting_dogs 
    all.by_dogs 
     .joins("INNER JOIN dogs on animal_type = 'Dog' and animal_id = dogs.id") 
     .merge(Dog.sits) 
    end 

    def self.killer_cats 
    all.by_cats 
     .joins("INNER JOIN cats on animal_type = 'Cat' and animal_id = cats.id") 
     .merge(Cat.killer) 
    end 

    # returns an Array not Pet::ActiveRecord_Relation 
    # slow due to N + 1 
    def self.remove_dogs_that_cannot_sit 
    all.reject{|pet| pet.animal_type == "Dog" && !pet.animal.sits} 
    end 
end 
+0

'self.remove_dogs_that_cannot_sit'可以通过在'animal'上使用include来加快速度。尝试'all.includes(:animal).reject {| pet | pet.animal_type ==“Dog”&&!pet.animal.sits}' – chrismanderson

+0

请注意它的细微之处,但是当您点击“.reject”部分时,您实际上正在抛弃ruby并将SQL留在后面,因此它的速度很慢。您可以链接AREL范围并保留在SQL中,例如by_dogs.where(sits:false)应该是等效的,假设你在数据库中的布尔列上有NOT NULL和默认假设置。 IIRC,我可能是错的,因为我要离开我的头顶,但只要你有一个范围存在,当你连接AREL范围时,“.all”就是暗示和不必要的。 – engineerDave

0

我想这些示波器添加到相关个别车型如:

class Dog < ActiveRecord::Base 
    has_many :pets, as: :animal 
    scope :sits, ->{ where(sits: true) } 
end 
class Cat < ActiveRecord::Base 
    has_many :pets, as: :animal 
    scope :natural_born_killer, ->{ where("killed_mice >= ?", 10) } 
end 

如果再需要他们在主宠物的模型,你可以将它们添加为方法,例如:

class Pet < ActiveRecord::Base 
    belongs_to :animal, polymorphic: true 

    def sitting_dogs 
    where(:animal => Dog.sits.all) 
    end 
    def killer_cats 
    where(:animal => Cat.natural_born_killer.all) 
    end 
end 

你复杂的情况是所有的宠物减去一些也坐狗。

class Pet < ActiveRecord::Base 
    belongs_to :animal, polymorphic: true 
    scope :sits, ->{ where(sits: true) } 

    def sitting_dogs 
    where(:animal => Dog.sits.all) 
    end 

    # There's probably a nicer way than this - but it'll be functional 
    def remove_dogs_that_cannot_sit 
    where.not(:id => sitting_dogs.pluck(:id)).all 
    end 
end 
+0

谢谢您的答复除去N + 1的另一种方式。困难的部分是我真正关心的是“宠物”记录。我想收集一些宠物记录并根据范围对它们进行过滤。 – Neil

+0

是的 - 这就是我现在正在做的事情......你正在根据示波器过滤宠物。 –

+0

注意:未经实际测试......您可能需要摆弄它们才能使下摆工作。 注意:您也可以使用作用域合并。 –

1

我同意具有坐在狗和猫杀手个人作用域。 Pet可以引入一个范围,通过animal_type来过滤它们。

这里是我的版本:

class Dog < ActiveRecord::Base 
    has_many :pets, as: :animal 
    scope :sits, ->{ where(sits: true) } 
end 

class Cat < ActiveRecord::Base 
    has_many :pets, as: :animal 
    scope :killer, ->{ where("killed_mice >= ?", 10) } 
end 

class Pet < ActiveRecord::Base 
    belongs_to :animal, polymorphic: true 
    scope :by_type, -> { |type| where(animal_type: type) } 
    scope :sitting_dogs, -> { by_type("Dog").sits } 
    scope :killer_cats, -> { by_type("Cat").killer } 
    scope :remove_dogs_that_cannot_sit, -> { reject{|pet| pet.animal_type == "Dog" && !pet.animal.sits} } 
end 
+0

谢谢你的回答!它看起来不错,但不幸的是它不适合我。使用'remove_dogs_that_cannot_sit'作用域时出现以下错误:'NameError:未定义的局部变量或方法'reject'' – Neil

+0

它看起来像一个需要在范围内指定'all.reject ...'为了它工作,但我不知道为什么。 – Neil

+1

我也注意到你的'remove_dogs_that_cannot_sit'范围返回一个数组,而不是一个ActiveRecord :: Relation对象。因此:它可能不应该是一个范围,因为它不可链接。它也许应该是一个类方法。 – Neil

1

不是完整的答案,但这里的执行remove_dogs_that_cannot_sit查询,返回的AR关系,消除了N + 1

class Pet < ActiveRecord::Base 
    belongs_to :animal, polymorphic: true 
    belongs_to :dog, -> { where(pets: { animal_type: 'Dog' }) }, foreign_key: :animal_id 

    def self.remove_dogs_that_cannot_sit 
    includes(:dog).where.not("pets.animal_type = 'Dog' AND dogs.sits = false").references(:dogs) 
    end 

    def self.old_remove_dogs_that_cannot_sit 
    all.reject{|pet| pet.animal_type == "Dog" && !pet.animal.sits} 
    end 
end 

使用的方式多态模型上的是加速某些查询的好方法,特别是如果您的多态模型仅限于少量选项。您也可以在Pet上清理一些范围内的方法。

def self.sitting_dogs 
    includes(:dog).merge(Dog.sits).references(:dogs) 
    end 

也更快。

irb(main):085:0> puts Benchmark.measure { 1000.times { Pet.remove_dogs_that_cannot_sit } } 
    0.040000 0.000000 0.040000 ( 0.032890) 
=> nil 

irb(main):087:0> puts Benchmark.measure { 1000.times { Pet.old_remove_dogs_that_cannot_sit } } 
    1.610000 0.090000 1.700000 ( 1.923665) 
=> nil 
0

这里是remove_dogs_that_cannot_sit

scope :joins_all -> { 
    joins("left join cats on animal_type = 'Cat' and animal_id = cats.id") 
    .joins("left join dogs on animal_type = 'Dog' and animal_id = dogs.id") 
    .joins("left join birds on animal_type = 'Bird' and animal_id = birds.id") 
} 

Pet.join_all.where.not("animal_type = 'Dog' and sits = 'f'")