Вложенные рельсы объединяются в области видимости

У меня есть 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тогда вы золотой, если он возвращает объект, то он недействителен.

Хороший РоРинг

Другие вопросы по тегам