我几乎没有发现如何为rails中的多态关联编写范围,更不用说如何在多态关联上编写查询。
在Rails文档中,我查看了Polymorphic Associations section,Joining Tables section和Scopes 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语法。
非常感谢任何提示/帮助!
答案 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
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
。
希望对您有帮助。