Reputation: 929
There are four models: Car, Person, Country and Religion.
class Car < ActiveRecord::Base
belongs_to :person
has_one :country, through: :person
has_one :religion, through: :person
def self.get_indian_cars_owned_by_hindus
self.joins(:country).joins(:religion).where("countries.name=? and religions.name=?", 'India', 'Hindu').count
end
end
class Person < ActiveRecord::Base
belongs_to :country
belongs_to :religion
has_one :car
end
class Country < ActiveRecord::Base
has_many :persons
end
class Religion < ActiveRecord::Base
has_many :persons
end
The function get_indian_cars_owned_by_hindus inside model Cars applies some condition on tables countries & religions. The query that gets generated when the function is called is this:
SELECT COUNT(*) FROM `cars` INNER JOIN `persons` ON `persons`.`id` = `cars`.`person_id` INNER JOIN `countries` ON `countries`.`id` = `persons`.`country_id` INNER JOIN `persons` `persons_cars_join` ON `persons_cars_join`.`id` = `cars`.`person_id` INNER JOIN `religions` ON `religions`.`id` = `persons_cars_join`.`religion_id` WHERE (`countries`.`name` = 'India') AND (`religions`.`name` = 'Hindu');
I initially expected the query to be this:
SELECT COUNT(*) FROM `cars` INNER JOIN `persons` ON `persons`.`id` = `cars`.`person_id` INNER JOIN `countries` ON `countries`.`id` = `persons`.`country_id` INNER JOIN `religions` ON `religions`.`id` = `persons`.`religion_id` WHERE (`countries`.`name` = 'India') AND (`religions`.`name` = 'Hindu');
ActiveRecords here is not optimizing an extra join between persons and cars tables. While I believe mysql will optimize this at it's end, what's the way to write this correctly in the rails way
.
I obviously can move the method inside model persons, but that's not the solution I am looking for. There can be cases where there are more than one nodes.
EDIT: Chaging model names from A, B, C and D to Car, Person, Country and Religion
Upvotes: 3
Views: 1132
Reputation: 552
The best bet would be changing the join
call to not use the has_many :through
relationships. Instead you can nest the deep relationships in an array, like this:
def self.get_indian_cars_owned_by_hindus
self.joins(:person => [:country, :religion]).
where("countries.name=? and religions.name=?", 'India', 'Hindu').
count
end
Upvotes: 1