2016-12-13 16 views
11

Tôi đã tìm thấy rất ít về cách người ta có thể viết phạm vi cho các mối liên hệ đa hình trong đường ray, hãy để một mình cách viết truy vấn trên các liên kết đa hình.Phạm vi và truy vấn với các hiệp hội đa hình

Trong tài liệu Rails, tôi đã xem Polymorphic Associations section, Joining Tables sectionScopes section. Tôi cũng đã thực hiện phần googling công bằng của mình.

Hãy thiết lập này ví dụ:

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 

Vì vậy, một Pet có thể được animal_type "Chó", "Cát", hay "Bird".

Để hiển thị tất cả các cấu trúc bảng: ở đây là tôi 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 

sau đó tôi đã đi trước và thực hiện một số hồ sơ:

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 

Và sau đó tôi đã đi và thực hiện một số pet hồ sơ:

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") 

Và điều đó là thiết lập! Bây giờ phần khó khăn: Tôi muốn tạo ra một số phạm vi trên mô hình Pet mà đào sâu vào các hiệp hội đa hình.

Dưới đây là một số phạm vi tôi muốn viết:

  • Hãy cho tôi tất cả các Pets của animal_type == "Dog" có thể ngồi
  • Hãy cho tôi tất cả các Pets của animal_type == "Cát" đã giết chết ít nhất 10 con chuột
  • Cho tôi tất cả Pets KHÔNG phải là cả hai "animal" và không thể ngồi được. (Nói cách khác: Hãy cho tôi tất cả các vật nuôi: tất cả trong số họ: trừ cho chó mà không thể ngồi)

Vì vậy, trong mô hình Pet tôi, tôi sẽ muốn đặt phạm vi của tôi trong đó:

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 

Tôi thấy nó khá khó khăn để viết những phạm vi này.

Một số nội dung tôi tìm thấy trực tuyến làm cho nó trông giống như bạn chỉ có thể viết các phạm vi này với SQL thô. Tôi tự hỏi nếu có thể sử dụng cú pháp Hash cho các phạm vi này thay thế.

Mọi mẹo/trợ giúp sẽ được đánh giá cao!

Trả lời

1

Sau khi xem xét câu trả lời trước và chơi đùa với nó: đây là những gì tôi đã làm.

(Lưu ý rằng Pet.remove_dogs_that_cannot_sit trả về một mảng. Phương pháp lớp học này là có thể đọc được nhưng có nhược điểm của việc chậm do N + 1. Mọi góp ý cho sửa chữa đó sẽ được đánh giá rất nhiều.)

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' có thể được thực hiện nhanh hơn bằng cách sử dụng một bao gồm trên' động vật'. Hãy thử 'all.includes (: animal) .reject {| pet | pet.animal_type == "Dog" &&! pet.animal.sits} ' – chrismanderson

+0

lưu ý tinh tế của nó nhưng khi bạn nhấn phần" .reject "bạn đang thực sự rơi vào ruby ​​và để lại SQL đằng sau, do đó tại sao nó chậm. Bạn có thể chuỗi các phạm vi AREL và ở lại trong SQL, ví dụ:by_dogs.where (ngồi: false) nên tương đương, giả sử bạn có NOT NULL và mặc định là false set trên cột boolean trong db. IIRC, tôi có thể sai khi tôi rời khỏi đỉnh đầu của tôi, nhưng ".all" được ngụ ý và không cần thiết khi bạn đang có chuỗi AREL miễn là bạn có một phạm vi hiện diện. – engineerDave

0

Tôi muốn thêm các phạm vi các mô hình cá nhân có liên quan ví dụ:

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 

nếu sau đó bạn cần đến chúng trên mô hình vật nuôi chính, bạn có thể chỉ cần thêm chúng như phương pháp ví dụ:

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 

etc

Trường hợp phức tạp của bạn chỉ là tất cả vật nuôi trừ đi một số cũng là chó ngồi.

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

Cảm ơn bạn đã trả lời của bạn. Phần khó khăn là những gì tôi thực sự quan tâm là các bản ghi 'Pet'. Tôi muốn lấy một số bộ sưu tập hồ sơ thú cưng và lọc chúng dựa trên các phạm vi. – Neil

+0

Vâng - đó là những gì tôi đã làm nó bây giờ ... bạn đang lọc các vật nuôi dựa trên phạm vi. –

+0

Lưu ý: không được thử nghiệm trên các ví dụ thực tế ... bạn có thể cần phải fiddle với họ để có được hem làm việc. Lưu ý: bạn cũng có thể sử dụng phạm vi hợp nhất. –

1

Tôi đồng ý có phạm vi riêng cho chó ngồi và mèo sát thủ. Phạm vi có thể được giới thiệu cho Thú cưng để lọc chúng theo animal_type.

Dưới đây là phiên bản của tôi:

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

cảm ơn bạn đã trả lời! Nó trông tuyệt vời, mặc dù tiếc là nó không làm việc cho tôi. Nó đi kèm với lỗi sau khi sử dụng 'remove_dogs_that_cannot_sit' phạm vi:' NameError: undefined local variable hoặc method 'reject'' – Neil

+0

Có vẻ như một trong những nhu cầu để xác định trong phạm vi 'all.reject ...' để cho nó để làm việc, mặc dù tôi không chắc chắn tại sao. – Neil

+1

Tôi cũng nhận thấy rằng phạm vi 'remove_dogs_that_cannot_sit' của bạn trả về một mảng trái với đối tượng ActiveRecord :: Relation. Vì vậy: nó có lẽ không nên là một phạm vi vì nó không phải là chainable. Nó có lẽ thay vì phải là một phương pháp lớp. – Neil

1

Không phải là câu trả lời hoàn chỉnh, nhưng đây là một cách để thực hiện truy vấn remove_dogs_that_cannot_sit mà trả về một mối quan hệ AR và loại bỏ 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 

Sử dụng một belongs_to trên một mô hình đa hình là một cách tuyệt vời để tăng tốc các truy vấn nhất định, đặc biệt nếu mô hình đa hình của bạn bị giới hạn ở một số lượng nhỏ các tùy chọn. Bạn cũng có thể làm sạch một số phương pháp phạm vi của mình trên Pet.

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

Nhanh hơn nữa.

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

Đây là một cách khác để loại bỏ N + 1 trên 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'") 
Các vấn đề liên quan