【问题标题】:Overwriting/Adding an ActiveRecord association dynamically using a singleton class使用单例类动态覆盖/添加 ActiveRecord 关联
【发布时间】:2010-10-27 23:29:46
【问题描述】:

业务逻辑是这样的:用户通过一个连接表在一条船上,我想我们称该模型为 Ticket。但是当一个用户实例想要检查船上还有谁时,有一个条件会询问该用户是否有权看到船上的每个人,或者只是船上的某些人。如果用户可以看到所有人,则正常交易很好: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 返回所有用户。

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

【问题讨论】:

    标签: ruby-on-rails ruby activerecord


    【解决方案1】:

    您的方法是问题所在。我知道目前在您不必重构现有呼叫站点的地方破解某些东西似乎是权宜之计,但我相信随着时间的推移,这将再次困扰您,成为错误和复杂性的根源。

    根据我的经验,躺着的睡狗会回来狠狠地咬你。通常以未来开发人员的形式出现,他们不知道您的关联是“魔术”并假设它只是桶轨来使用它。他/她甚至可能没有理由编写一个会暴露该行为的测试用例,这增加了您只有在该错误处于生产状态并且客户不满意时才发现该错误的可能性。你现在节省的时间真的值得吗?

    来自波士顿的奥斯汀正在指明方向。不同的语义?不同的名字。第一条规则是始终编写尽可能清楚地说明其功能的代码。其他任何事情都是疯狂的道路。

    【讨论】:

      【解决方案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
      

      通过返回一个空的哈希值,你可以有效地取消作用域的效果。

      【讨论】:

      • 你说得对:我只是要实现一些与此非常相似的东西,编辑现有代码,避免将我的整个胸部置于无限力量的恶习中。另外,我在一个单例类上追踪了下划线问题:ActiveRecord 2.3 reflect.rb 中的第 241 行。如果该行是:active_record.class_name.foreign_key 而不是 active_record.name.foreign_key 一切都会正常工作。嘿伙计,感谢您的所有帮助和建议。我真的很感激。
      【解决方案3】:

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

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

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

      用户 门票 船 门票 用户

      ...对吗?

      因此,您需要将两组工单过滤到带有 current_user 标记的工单。

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

      Blech,我一点也不觉得我在帮助你。对不起。

      【讨论】:

      • 我使用的是 activerecord 2.1,但它也发生在 2.3.2 上。不过,感谢您抽出宝贵时间。
      【解决方案4】:

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

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

      【讨论】:

      • 这是我尝试使用alias_method_chain 的方法,我编辑了示例代码以澄清这一点。我不能这样做的原因是现有代码一团糟,假设所有用户总是想“看到”船上的每个人,我正试图在上游抓住它,让睡狗躺着,也有所有其他关联都“正常工作”。
      猜你喜欢
      • 1970-01-01
      • 2011-02-22
      • 1970-01-01
      • 2017-03-02
      • 1970-01-01
      • 2020-12-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多