I have found very little about how one can write scopes for polymorphic associations in rails, let alone how to write queries on polymorphic associations.
In the Rails Documentation, I have looked at the Polymorphic Associations section, the Joining Tables section, and the Scopes section. I have also done my fair share of googling.
Take this setup for example:
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
So a Pet
can be of animal_type
"Dog", "Cat", or "Bird".
To show all the table structures: here is my 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
I then went ahead and made some records:
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
And then I went and made some pet
records:
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")
And that is the setup! Now the tough part: I want to create some scopes on the Pet
model which dig into the polymorphic associations.
Here are some scopes I would like to write:
Pets
of animal_type == "Dog" that can sitPets
of animal_type == "Cat" that have killed at least 10 micePets
that are NOT both animal_type "Dog" and cannot sit. (In other words: Give me all the pets: all of them: except for dogs that cannot sit)So in my Pet
model I would want to put my scopes in there:
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
I am finding it pretty tough to write these scopes.
Some stuff I found online makes it look like you can only write these scopes with raw SQL. I am wondering if it is possible to use the Hash syntax for these scopes instead.
Any tips/help would be greatly appreciated!
Polymorphic relationship in Rails refers to a type of Active Record association. This concept is used to attach a model to another model that can be of a different type by only having to define one association.
A polymorphic association consists on two (or more) associations happening with the same foreign key. For example, consider the models Image , Video and Comment . The first two represent something that a user might post. We want to allow comments to be placed in both of them.
The basic structure of a polymorphic association (PA)sets up 2 columns in the comment table. (This is different from a typical one-to-many association, where we'd only need one column that references the id's of the model it belongs to). For a PA, the first column we need to create is for the selected model.
I'd add these scopes to the relevant individual models eg:
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
if you then need them on the main Pet model, you can just add them as methods eg:
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
Your complicated case is just all pets minus some that are also sitting dogs.
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
I agree of having individual scopes for sitting dogs and killer cats. A scope could be introduced for Pet to filter them by animal_type.
Here's my version:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With