Вложенные рельсы объединяются в области видимости
У меня есть 3 класса. Страна, Город и Деятельность. В стране есть много городов, а в городе есть много видов деятельности.
Некоторые действия еще не готовы, поэтому они имеют статус одобрения. Я хотел бы настроить его, чтобы город без утвержденных действий был недействительным, а страна без действительных городов также недействительна.
class ApprovalStatus < ActiveRecord::Base
def self.approved_status_id
ApprovalStatus.find_or_create_by(name: 'Approved').id
end
class Activity < ActiveRecord::Base
# Named scopes
scope :that_is_approved, -> { where approval_status_id: ApprovalStatus.approved_status_id }
class City < ActiveRecord::Base
# Named scopes
scope :that_has_valid_activities, -> { joins(:activities).merge(Activity.that_is_approved).uniq }
class Country < ActiveRecord::Base
# Named scopes
scope :that_has_valid_cities, -> { joins(:cities).uniq }
# Associations
has_many :cities, -> { that_has_valid_activities }, dependent: :destroy
Я хочу, чтобы область действия и связь были такими, какие я хочу, чтобы Country.that_has_valid_cities возвращал только действительные страны и только действительные города, которые будут отображаться в этих допустимых странах, когда сериализатор активной модели захватывает города.
В консоли rails: City.that_has_valid _activities работает нормально.
ApprovalStatus Load (13.4ms) SELECT "approval_statuses".* FROM "approval_statuses" WHERE "approval_statuses"."name" = $1 LIMIT 1 [["name", "Approved"]]
City Load (21.6ms) SELECT DISTINCT "cities".* FROM "cities" INNER JOIN "activities" ON "activities"."city_id" = "cities"."id" WHERE "activities"."approval_status_id" = $1 [["approval_status_id", 2]]
Country.that_has_valid_cities перерывы.
ApprovalStatus Load (0.4ms) SELECT "approval_statuses".* FROM "approval_statuses" WHERE "approval_statuses"."name" = $1 LIMIT 1 [["name", "Approved"]]
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "activities" LINE 1: ..." ON "cities"."country_id" = "countries"."id" AND "activitie...
^
: SELECT DISTINCT "countries".* FROM "countries" INNER JOIN "cities" ON "cities"."country_id" = "countries"."id" AND "activities"."approval_status_id" = $1
Похоже, что он пытается сделать И, когда я хочу, чтобы он сделал второе ВНУТРЕННЕЕ СОЕДИНЕНИЕ.
class Country < ActiveRecord::Base
# Named scopes
scope :that_has_valid_cities, -> { joins(:cities).merge(City.that_has_valid_activities).uniq }
# Associations
has_many :cities, dependent: :destroy
Работает и производит запросы:
ApprovalStatus Load (0.6ms) SELECT "approval_statuses".* FROM "approval_statuses" WHERE "approval_statuses"."name" = $1 LIMIT 1 [["name", "Approved"]]
Country Load (1.2ms) SELECT DISTINCT "countries".* FROM "countries" INNER JOIN "cities" ON "cities"."country_id" = "countries"."id" LEFT OUTER JOIN "activities" ON "activities"."city_id" = "cities"."id" WHERE "activities"."approval_status_id" = $1 ORDER BY "countries"."id" ASC LIMIT 1 [["approval_status_id", 2]]
Однако возвращенные страны по-прежнему перечисляют свои недопустимые города, поскольку недопустимые города не фильтруются из допустимых стран.
PS Дайте мне знать, если весь стиль здесь не так, как я изучаю рельсы самостоятельно.
2 ответа
# app/models/approval_status.rb
class ApprovalStatus < ActiveRecord::Base
has_many :activities
def self.approved_status
# I added some caching
return @approved_status if @approved_status
@approved_status = ApprovalStatus.find_or_create_by(name: 'Approved')
end
end
# app/models/activity.rb
class Activity < ActiveRecord::Base
belongs_to :city
belongs_to :approval_status
scope :that_is_approved, -> { where approval_status: ApprovalStatus.approved_status }
end
# app/models/city.rb
class City < ActiveRecord::Base
belongs_to :country
has_many :activities
scope :that_has_valid_activities, -> { joins(:activities).where(activities: {id: Activity.that_is_approved}) }
end
# app/models/country.rb
class Country < ActiveRecord::Base
has_many :cities, -> { that_has_valid_activities }, dependent: :destroy
# I added this (probably would help you along the way in case you do not know yet about :through)
has_many :activities, through: :cities
scope :that_has_cities_having_valid_activities, -> { joins(cities: :activities) }
end
использование
Country.that_has_cities_having_valid_activities
City.that_has_valid_activities
Попался
Обратите внимание, что
Country.joins(:cities)
больше не будет работать, а сгенерирует и ошибку. Вы будете нуждаться в этом всегда вместе с:activities
вот так:Country.joins(cities: :activities)
- Это потому что ваш
has_many :cities, -> { that_has_valid_activities }
запрашивает связанный:activities
, Не удалось найти способ автоматически выполнить эту работу.
- Это потому что ваш
Рекомендация
Советую не использовать
has_many :cities, -> { that_has_valid_activities } scope :that_has_cities_having_valid_activities, -> { joins(cities: :activities) }
но вместо этого используйте:
has_many :cities scope :that_has_cities_having_valid_activities, -> { joins(:cities).where(cities: {id: City.that_has_valid_activities} ) }
так что вы все еще можете запросить "недействительные" города. Иначе,
@country.cities
всегда будет отфильтрован по "действительным" городам, и возможный будущий сценарий запроса "недействительных" городов будет сложным и может включать в себя некрасивый код.Это также исправит ошибку выше
Проверенная работа
Я думаю, что вы могли бы проверить обратное. Проверьте, является ли какая-либо деятельность, город или страна недействительной. Вам просто нужно запустить find_by
, Если он вернется nil
тогда вы золотой, если он возвращает объект, то он недействителен.
Хороший РоРинг