具有多态关联的范围和查询

时间:2016-12-13 02:27:16

标签: ruby-on-rails ruby

我几乎没有发现如何为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&#34; Dog&#34;,&#34; Cat&#34;或者#34; Bird&#34;。

显示所有表格结构:这是我的 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模型上创建一些范围,挖掘多态关联。

以下是我想写的一些范围:

  • 给我所有Pets的animal_type ==&#34; Dog&#34;可以坐
  • 给我所有Pets的animal_type ==&#34; Cat&#34;已杀死至少10只小鼠
  • 给我所有Pets不属于animal_type&#34; Dog&#34;而且不能坐。 (换句话说:给我所有的宠物:所有的宠物:除了不能坐的狗)

所以在我的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编写这些范围。我想知道是否可以为这些范围使用Hash语法。

非常感谢任何提示/帮助!

6 个答案:

答案 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

答案 1 :(得分:1)

在回顾以前的答案并玩弄它之后:这就是我的工作。

(注意Pet.remove_dogs_that_cannot_sit返回一个数组。这个类方法是可读的,但由于N + 1而有慢的缺点。任何修复建议都会非常感激。)< / em>的

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

答案 2 :(得分:1)

不是完整的答案,但这是执行返回AR关系的remove_dogs_that_cannot_sit查询并删除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

在多态模型上使用belongs_to是加速某些查询的好方法,特别是如果您的多态模型仅限于少量选项。您也可以在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

答案 3 :(得分: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

如果您在主Pet模型上需要它们,您可以将它们添加为方法,例如:

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

答案 4 :(得分:0)

以下是移除remove_dogs_that_cannot_sit

上的N + 1的另一种方法
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'")

答案 5 :(得分:0)

我所做的就像是在吼叫:

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

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

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

  scope :sitting_dogs, -> do
    joins("INNER JOIN dogs on \
    pets.animal_id = dogs.id and pets.animal_type = \
    'Dog'").merge(Dog.sittable)
  end

  scope :amok_cats, -> do
    joins("INNER JOIN cats on \
    pets.animal_id = cats.id and pets.animal_type = \
    'Cat'").merge(Cat.amok)
  end

   scope :can_sit_dogs, -> do
    joins("INNER JOIN dogs on \
    pets.animal_id = dogs.id and pets.animal_type = \
    'Dog'").merge(Dog.dissittable)
  end
end

此外,scope的名称更倾向于adjective而不是noun。因此,我使用sittable dissitable amok而不是sits killer

如果您熟悉ransack,也可以根据issue

进行搜索。

希望对您有帮助。