Únete a la misma mesa dos veces con condiciones

Hay situaciones en las que ActiveRecord establece el nombre de la tabla de alias si hay varias combinaciones con la misma tabla. Estoy atrapado en una situación en la que estas uniones contienen ámbitos (usando 'fusionar').

Tengo una relación de muchos a muchos:

Modelos nombre_tabla:users

Segundo modelo nombre_tabla:posts

Unir nombre de tabla:access_levels

Una publicación tiene muchos usuarios a través de access_levels y viceversa.

Tanto el modelo de usuario como el modelo de publicación comparten la misma relación:

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

El alcance dentro del modelo AccessLevel se ve así:

  # 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)
  # }

Me gustaría llamar a algo así:

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

ActiveRecord crea un alias para la segunda unión ('access_levels_users'). Quiero hacer referencia a este nombre de tabla dentro del alcance 'válido' del modelo AccessLevel.

V1 obviamente genera unPG::AmbiguousColumn-Error. V2 da como resultado el prefijo de ambas condiciones conaccess_levels., que es semánticamente incorrecto.

Así es como genero la consulta: (simplificado)

# 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")

La consulta generada se ve así (usando elvalid alcance v2 desde arriba):

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 establece un alias apropiado 'access_levels_posts' para la segunda unión de la tabla access_levels. El problema es que la fusiónvalid-scope prefija la columna con 'access_levels' en lugar de 'access_levels_posts'. También intenté usar arel para generar el alcance:

# 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))
  )
}

La consulta resultante sigue siendo la misma.

Respuestas a la pregunta(3)

Su respuesta a la pregunta