使用单例类动态覆盖/添加ActiveRecord关联

时间:2009-05-21 16:00:15

标签: ruby-on-rails ruby activerecord

业务逻辑是这样的:用户通过联接表在船中,我想我们称该模型为票证。但是当用户实例想要检查船上还有谁时,有一个条件是询问该用户是否有权查看船上的每个人,或者只是船上的某些人。如果用户可以看到每个人,那么正常交易就可以了:some_user.boats.first.users会返回所有拥有该船票的用户。但是对于一些用户来说,只有人们在船上(就他们而言)是人们,就像餐厅一样。因此,如果用户的票证被“标记”(使用acts_as_taggable样式系统)和“餐厅”,则从some_user.boats.first.users返回的唯一用户应该是带有标记为“餐厅”的票证的用户。

仅仅是为了记录,我不是试图设计一些疯狂的东西 - 我试图将这种任意分组楔入一个(大多数)存在的系统。 所以我们得到了:

class User
  has_many :tickets
  has_many :boats, :through => :tickets
end

class Ticket
  belongs_to :user
  belongs_to :boat
end

class Boat
  has_many :tickets
  has_many :users, :through => :tickets
end

最初,我认为我可以有条件地修改虚拟类,如:

singleton = class << a_user_instance ; self ; end
singleton.class_eval(<<-code
  has_many :tickets, :include => :tags, :conditions => ['tags.id in (?)', [#{tag_ids.to_s(:db)}]]
code
)

这一直到生成SQL,但在生成时,它生成的SQL以:

结尾

LEFT OUTER JOIN "tags" ON ("tags"."id" = "taggings"."tag_id") WHERE ("tickets"._id = 1069416589 AND (tags.id in (5001,4502)))

我试过挖掘ActiveRecord代码,但是我找不到任何能在前面的SQL中使用下划线加上'id'前缀的地方。我知道加载ActiveRecord类时会加载关联,我假设单例类也是如此。 耸肩

我还使用alias_method_chain之类的:

singleton = class << a_user_instance ; self ; end
singleton.class_eval(<<-code
  def tickets_with_tag_filtering
    tags = Tag.find(etc, etc)
    tickets_without_tag_filtering.scoped(:include => :tags, :conditions => {:'tags.id' => tags})
  end
  alias_method_chain :tickets, :tag_filtering
code
)

但是,虽然该方法产生了所需的票证,但这些票证上的任何联接都使用类中的条件,而不是虚拟类。 some_user.boats.first.users会返回所有用户。

任何类型的评论都会受到赞赏,特别是如果我用这种方法咆哮错误的树。谢谢!

4 个答案:

答案 0 :(得分:2)

因此,对于您的下划线问题的猜测是Rails正在根据评估时的上下文生成关联代码。单身一堂课可能会搞砸了,就像这样:

"#{owner.table_name}.#{association.class.name}_id = #{association.id}"

可以进入并在你的单例类中定义一个类名属性,看看是否能修复这个问题。

总的来说,我不推荐这个。它创造的行为令人痛苦地追踪并且不可能有效地延伸。它会在代码库中创建一个地雷,它会在以后伤害你或你所爱的人。

相反,请考虑使用named_scope声明:

class User
   has_many :taggings, :through => :tickets

   named_scope :visible_to, lambda { |looking_user|
      { :include => [ :tickets, :taggings ], 
        :conditions => [ "tickets.boat_id in (?) and taggings.ticket_id = tickets.id and taggings.tag_id in (?)", looking_user.boat_ids, looking_user.tag_ids ] 
      }
    }
end

虽然您可能需要返回并编辑一些代码,但在使用方式方面要灵活得多:

Boat.last.users.visible_to( current_user )

很明显,对查找的限制,以及限制的目的是什么。因为条件是在运行时动态计算的,所以您可以处理客户端打击您的下一个奇怪的修改。假设他们的一些用户有X射线视觉和透视:

class User
   named_scope :visible_to, lambda { |looking_user|
      if looking_user.superhuman?
        {}
      else
        { :include => [ :tickets, :taggings ], 
          :conditions => [ "tickets.boat_id in (?) and taggings.ticket_id = tickets.id and taggings.tag_id in (?)", looking_user.boat_ids, looking_user.tag_ids ] 
        }
      end
    }
end

通过返回空哈希,可以有效地抵消范围的影响。

答案 1 :(得分:0)

为什么不抓住船上的所有用户并添加他们的标签。

然后运行快速过滤器以包含&amp;仅返回与查询用户具有相同标记的用户。

答案 2 :(得分:0)

您使用的是什么版本的Rails?您是否尝试升级以查看下划线问题是否已修复?它就像找不到外键作为“tag_id”或某些东西。

我的ruby-fu是有限的,所以我不确定如何在运行时动态包含正确的方法选项。

只是为了帮助你澄清,你必须担心这两个地方。您希望过滤用户的可查看用户,以便他们只能看到具有相同标记的用户。你的结构是:

用户&lt; - &gt;门票&lt; - &gt;船&lt; - &gt;门票&lt; - &gt;用户

......对吗?

因此,您需要将两组故障单过滤到具有current_user标记的故障单。

也许您只需要一个current_user.viewable_users()方法,然后通过它过滤所有内容?我不确定你需要保留哪些现有功能。

布莱克,我觉得我根本不在帮助你。遗憾。

答案 3 :(得分:0)

你的方法就是问题所在。我知道目前似乎很容易破解你不必重构现有呼叫站点的东西,但我相信,如果时间过去,这将会成为困扰和复杂性的源头。

根据我的经验,睡觉的狗躺着回来咬你。通常以未来开发人员的形式,不知道你的关联是“魔术”,并使用它假设它只是pail ole rails。他/她可能甚至没有理由写一个会暴露行为的测试用例,这会增加你只能在生产中找到bug并且客户不满意的几率。你现在攒钱真的值得吗?

Austinfrombostin指明了方向。不同的语义?不同的名字。规则一是始终编写代码,尽可能清楚地说出它的作用。其他任何事情都是疯狂的道路。