Treten Sie unter Bedingungen zweimal an den gleichen Tisch

Es gibt Situationen, in denen ActiveRecord den Alias-Tabellennamen festlegt, wenn mehrere Joins mit derselben Tabelle vorhanden sind. Ich stecke in einer Situation fest, in der diese Joins Gültigkeitsbereiche enthalten (mit "Zusammenführen").

Ich habe eine Beziehung von vielen zu vielen:

Modelle table_name:users

Zweite Modelle table_name:posts

Join-Tabellenname:access_levels

Ein Beitrag hat viele Benutzer über access_levels und umgekehrt.

Sowohl das Benutzermodell als auch das Post-Modell haben dieselbe Beziehung:

has_many :access_levels, -> { merge(AccessLevel.valid) }

Der Gültigkeitsbereich innerhalb des AccessLevel-Modells sieht folgendermaßen aus:

  # v1
  scope :valid, -> {
    where("(valid_from IS NULL OR valid_from < :now) AND (valid_until IS NULL OR valid_until > :now)", :now => Time.zone.now)
  }

  # v2
  # scope :valid, -> {
  #   where("(#{table_name}.valid_from IS NULL OR #{table_name}.valid_from < :now) AND (#{table_name}.valid_until IS NULL OR #{table_name}.valid_until > :now)", :now => Time.zone.now)
  # }

Ich würde etw gerne so nennen:

Post.joins(:access_levels).joins(:users).where (...)

ActiveRecord erstellt einen Alias für den zweiten Join ('access_levels_users'). Ich möchte diesen Tabellennamen innerhalb des Gültigkeitsbereichs des AccessLevel-Modells referenzieren.

V1 erzeugt offensichtlich einePG::AmbiguousColumn-Error. Mit V2 werden beide Bedingungen mit vorangestelltaccess_levels., was semantisch falsch ist.

So generiere ich die Abfrage: (vereinfacht)

# inside of a policy
scope = Post.
  joins(:access_levels).
  where("access_levels.level" => 1, "access_levels.user_id" => current_user.id)

# inside of my controller
scope.joins(:users).select([
        Post.arel_table[Arel.star],
        "hstore(array_agg(users.id::text), array_agg(users.email::text)) user_names"
      ]).distinct.group("posts.id")

Die generierte Abfrage sieht folgendermaßen aus (mit der Tastevalid Anwendungsbereich v2 von oben):

SELECT "posts".*, hstore(array_agg(users.id::text), array_agg(users.email::text)) user_names

  FROM "posts"
  INNER JOIN "access_levels" ON "access_levels"."post_id" = "posts"."id" AND (("access_levels"."valid_from" IS NULL OR "access_levels"."valid_from" < '2014-07-24 05:38:09.274104') AND ("access_levels"."valid_until" IS NULL OR "access_levels"."valid_until" > '2014-07-24 05:38:09.274132'))
  INNER JOIN "users" ON "users"."id" = "access_levels"."user_id"
  INNER JOIN "access_levels" "access_levels_posts" ON "access_levels_posts"."post_id" = "posts"."id" AND (("access_levels"."valid_from" IS NULL OR "access_levels"."valid_from" < '2014-07-24 05:38:09.274675') AND ("access_levels"."valid_until" IS NULL OR "access_levels"."valid_until" > '2014-07-24 05:38:09.274688'))

  WHERE "posts"."deleted_at" IS NULL AND "access_levels"."level" = 4 AND "access_levels"."user_id" = 1 GROUP BY posts.id

ActiveRecord legt einen entsprechenden Alias 'access_levels_posts' für den zweiten Join der Tabelle access_levels fest. Das Problem ist, dass das zusammengeführt wirdvalid-scope stellt der Spalte 'access_levels' anstelle von 'access_levels_posts' voran. Ich habe auch versucht, mit arel den Gültigkeitsbereich zu generieren:

# v3
scope :valid, -> {
  where arel_table[:valid_from].eq(nil).or(arel_table[:valid_from].lt(Time.zone.now)).and(
    arel_table[:valid_until].eq(nil).or(arel_table[:valid_until].gt(Time.zone.now))
  )
}

Die resultierende Abfrage bleibt gleich.

Antworten auf die Frage(3)

Ihre Antwort auf die Frage