Given the following example models:
# rails g model country name
class Country < ApplicationRecord
has_many :states
has_many :cities, through: :states
end
# rails g model country name country:belongs_to
class State < ApplicationRecord
belongs_to :country
has_many :cities
end
# rails g model city name state:belongs_to
class City < ApplicationRecord
belongs_to :state
has_one :country, through: :state
end
We can get records with matches in joined tables by applying an INNER JOIN:
countries_with_states = Country.joins(:states)
countries_with_cities = Country.joins(states: :cities)
# can be shortened since we have an indirect association
countries_with_cities = Country.joins(:cities)
This will just returns rows with at least one match in the joined table.
We can also get records without matches in the joined table by using a LEFT OUTER JOIN with a condition on the joined table:
countries_with_no_states = Country.left_joins(:states)
.where(states: { id: nil })
countries_with_no_cities = Country.left_joins(:cities)
.where(cities: { id: nil })
Using #map
on an association should not be done as its extremely ineffective and can lead to serious performance issues. You instead need to create meaningful associations between your models and use joins to filter the rows in the database.
ActiveSupport's #blank?
and #empty?
methods should really only be used when dealing with user input like strings and arrays of strings which is where its heuristics are actually useful.
For collections of records you have .exists?
which will always create a query and .any?
and .none?
which will use the size if the relation has been loaded.