我有多对多的关系:
@H_404_4@Models table_name: users
Second models table_name: posts
Join table name: access_levels
一个帖子通过access_levels有很多用户,反之亦然.
两者,用户模型和Post模型共享相同的关系:
has_many:access_levels,– > {merge(AccessLevel.valid)}
AccessLevel模型的范围如下所示:
# 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) # }
我想这样打电话给sth:
Post.joins(:access_levels).joins(:users).where (...)
ActiveRecord为第二个连接(‘access_levels_users’)创建一个别名.我想在AccessLevel模型的“有效”范围内引用这个表名.
V1显然生成一个PG :: AmbiguousColumn-Error.
V2导致在access_levels两个条件前缀,这在语义上是错误的.
# 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")
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为access_levels表的第二个连接设置一个合适的别名“access_levels_posts”.
问题是合并的有效范围前缀为’access_levels’而不是’access_levels_posts’.我也试图用isl来生成范围:
# 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)) ) }
结果查询保持不变.
解决方法
关键是找到一个方法来访问当前的isl_table对象,如果它们正在被使用,那么它的table_aliases在执行时就在该范围内.使用该表,您将能够知道是否在具有表名称别名(多个连接在同一个表上)的JOIN中使用范围,或者如果另一方面该范围没有表名称的别名.
# based on your v2 scope :valid,-> { where("(#{current_table_from_scope}.valid_from IS NULL OR #{current_table_from_scope}.valid_from < :now) AND (#{current_table_from_scope}.valid_until IS NULL OR #{current_table_from_scope}.valid_until > :now)",:now => Time.zone.now) } def self.current_table_from_scope current_table = current_scope.arel.source.left case current_table when Arel::Table current_table.name when Arel::Nodes::TableAlias current_table.right else fail end end
我使用current_scope
作为基础对象来查找arel表,而不是先前使用self.class.arel_table或甚至relation.arel_table的尝试.我正在调用该对象的源来获取一个Arel::SelectManager
,这反过来将给你#left上的当前表.在这一刻,有两个选项:你有一个Arel :: Table(没有别名,表名在#name上),或者你有一个Arel :: Nodes :: TableAlias与其#right上的别名.
如果你有兴趣,这里有一些参考我在路上使用:
> A similar question on SO,用一大堆代码回答,你可以使用而不是你的美丽和简洁的能力.
>这Rails issue和这other one.