Verbinden die gleiche Tabelle zweimal mit Bedingungen
Gibt es Situationen, wo ActiveRecord stellt die alias-Tabelle name, wenn es gibt mehrere Verknüpfungen mit der gleichen Tabelle. Ich bin in einer situation stecken, wo diese Verknüpfungen enthalten die Bereiche (mittels 'merge').
Habe ich eine viele-zu-viele-Beziehung:
Modelle table_name:
users
Zweiten Modelle table_name:
posts
Join-Tabelle name:
access_levels
Post hat viele Nutzer durch access_levels und Umgekehrt.
Sowohl die User-Modell und die Post-Modell teilen sich die gleiche relation:
has_many :access_levels, -> { merge(AccessLevel.valid) }
Rahmen innerhalb der AccessLevel Modell sieht wie folgt 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 gerne call sth wie diese:
Post.joins(:access_levels).joins(:users).where (...)
ActiveRecord erzeugt einen alias für den zweiten join ('access_levels_users'). Ich möchte diese Tabelle referenzieren Namen in der 'gültigen' Bereich-der AccessLevel Modell.
V1 offensichtlich erzeugt eine PG::AmbiguousColumn
-Fehler.
V2 Ergebnisse, indem die beiden Bedingungen mit access_levels.
, das ist semantisch falsch.
Dies ist, wie ich das generieren der 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")
Den generierten query sieht wie folgt aus (mit der valid
Umfang 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 stellt eine propriate alias 'access_levels_posts" für die zweite Verknüpfung die access_levels Tabelle.
Das problem ist, dass die zusammengeführten valid
-Präfixe für den Gültigkeitsbereich der Spalte mit 'access_levels' statt 'access_levels_posts'. Ich habe auch versucht, aus arel zu generieren Umfang:
# 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 gleich bleibt.
- deine Frage ist ein wenig verwirrend, aber ich denke, ich weiß, was Sie tun möchten. ändern Sie die
valid
Umfang zujoins(:user).where("(valid_from IS NULL OR valid_from < :now) AND (valid_until IS NULL OR valid_until > :now)", now: Time.zone.now).where(users: { active: true, or: something })
Du musst angemeldet sein, um einen Kommentar abzugeben.
Nach genauerer Betrachtung dieses Problems auf einem ähnliche Frage hier, kam ich auf eine einfachere und sauberere (in meinen Augen) Lösung auf diese Frage. Ich bin einfügen hier die relevanten bits meiner Antwort auf die andere Frage, für die Vollständigkeit, die zusammen mit Ihrem Rahmen.
Dem Punkt war, einen Weg zu finden, um Zugriff auf die aktuelle
arel_table
Objekt, mit seinertable_aliases
wenn Sie verwendet werden, innerhalb des Bereichs, in dem moment seiner Ausführung. Mit dieser Tabelle, Sie werden in der Lage sein zu wissen, ob der Rahmen verwendet wird, innerhalb einesJOIN
hat, dass der name der Tabelle alias (mehrere joins auf die gleiche Tabelle), oder wenn auf der anderen Seite der Umfang hat keinen alias für den Tabellennamen.Ich bin mit
current_scope
als Basis-Objekt zu suchen, das arel-Tabelle, anstatt der vorherigen versuche mitself.class.arel_table
oder sogarrelation.arel_table
. Ich rufesource
auf das Objekt zu beziehenArel::SelectManager
, die wiederum geben Sie die aktuelle Tabelle auf der#left
. In diesem moment gibt es zwei Optionen: man habe dort einArel::Table
(keine alias-Tabellenname ist auf#name
), oder dass Sie eineArel::Nodes::TableAlias
mit dem alias der auf seine#right
.Wenn Sie interessiert sind, hier sind einige Referenzen, die ich verwendet, die Straße hinunter:
Kam ich über diese Frage bei der Suche für Dinge wie diese. Ich weiß, es ist eine späte Antwort, aber, wenn jemand stolpert hier rein vielleicht kann dies einigen helfen. Dies funktioniert in Rails 4.2.2, vielleicht konnte dies nicht getan werden, wenn die Frage gestellt wurde.
Dieser Antwort wurde inspiriert durch die Antwort von @dgilperez, aber ein bisschen vereinfacht. Auch mit richtigen Rahmen. So, hier ist es.
Und keine Notwendigkeit, um es in zwei joins
Ich sah, dass Sie auch geändert, um mit OUTER-JOINs, bekommen Sie mit:
Aber bewusst sein, mit
includes
ist nicht immer mit einer SQL-Anfrage.(current_scope ? current_scope.table : arel_table).name
Habe ich in der Lage, meine eigenen zu lösen problem in der Zwischenzeit. Ich poste meine Lösung zu helfen, andere, die mit ähnlichen Problemen.
Präambel: Es ist ein langer Weg, um in das versprochene land 😉
Werde ich das setup so kurz wie möglich:
Das ursprüngliche Ziel war es zu nennen, so etwas wie dies (in der Reihenfolge an weitere Bedingungen hinzufügen etc.):
Dass die Ergebnisse in einem semantisch falschen Abfrage:
Den zweiten join verwendet ein alias - aber der Zustand ist nicht mit diesem alias.
Arel ist die Rettung!
Ich habe bauen alle folgenden Verknüpfungen mit bloßen arel anstelle von Vertrauen ActiveRecord. Leider scheint es, dass die Kombination von beiden ist nicht immer wie erwartet funktioniert.
Aber wenigstens funktioniert es überhaupt, dass Art und Weise. Ich bin mit outer-joins in diesem Beispiel also würde ich haben, Sie zu bauen, indem ich mich sowieso. Darüber hinaus sind alle diese Abfragen befinden sich in der Politik (mit Experte). So sind Sie leicht überprüfbar und es gibt keine Fett-controller oder jegliche Redundanz. Also ich bin fein mit einigen zusätzlichen code.
Nachdem all das Blut, Schweiß und Tränen, das hier ist unsere daraus resultierende Abfrage:
Alle Bedingungen sind nun mit einem geeigneten alias!