Области и запросы с полиморфными ассоциациями
Я очень мало узнал о том, как можно писать области для полиморфных ассоциаций в рельсах, не говоря уже о том, как писать запросы на полиморфные ассоциации.
В документации Rails я просмотрел раздел Полиморфные ассоциации, раздел "Соединительные таблицы" и раздел "Области" . Я также сделал свою долю в googling.
Возьмите эту настройку, например:
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
, которые выкапывают в полиморфные ассоциации.
Вот несколько областей, которые я бы хотел написать:
- Дайте мне все
Pets
animal_type == "Собака", которая может сидеть
- Дайте мне все
Pets
animal_type == "Cat", которые убили не менее 10 мышей.
- Дайте мне все
Pets
, которые не являются как animal_type "Dog", так и не могут сидеть. (Другими словами: Дайте мне всех домашних животных: все они: кроме собак, которые не могут сидеть).
Итак, в моей модели 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
Мне сложно писать эти области.
Некоторые вещи, которые я нашел в Интернете, показывают, что вы можете только записывать эти области с помощью raw SQL. Мне интересно, можно ли вместо этого использовать синтаксис Hash для этих областей.
Любые советы/помощь будут очень благодарны!
Ответы
Ответ 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
Ответ 2
Я согласен с тем, что у меня есть отдельные области для сидения собак и убийц. Сфера может быть введена для 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
Ответ 3
Не полный ответ, но вот способ выполнения запроса 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
Использование 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
Ответ 4
Я бы добавил эти области к соответствующим отдельным моделям, например:
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 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
Ответ 5
Вот еще один способ удаления N + 1 на 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'")
Ответ 6
То, что я сделал, это как ниже:
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
.
Если вы знакомы с вымогателем, вы также можете использовать его для поиска по проблеме
Желание помогло тебе.