【问题标题】:Referring to instance in has_many (Rails)引用 has_many (Rails) 中的实例
【发布时间】:2012-08-14 09:57:41
【问题描述】:

我有一个游戏模型,它有很多:文本。问题是我必须根据它们所属的游戏对文本进行不同的排序(是的,丑陋,但它是遗留数据)。我创建了一个Text.in_game_order_query(game) 方法,它返回适当的排序。

我最喜欢的解决方案是在 Text 模型中放置一个默认范围,但这需要知道它们属于哪个游戏。我也不想为每个游戏的文本创建单独的类 - 有很多游戏,而且还会有更多游戏,所有较新的游戏都将使用相同的顺序。所以我有了另一个想法:在 has_many 中排序文本,当我知道它们属于哪个游戏时:

has_many :texts, :order => Text.in_game_order_query(self)

但是,self 是这里的类,所以这不起作用。

除了每次都调用@game.texts.in_game_order(@game),真的没有别的办法了吗??

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 activerecord has-many


    【解决方案1】:

    我最近遇到了一个非常相似的问题,我确信这在 Rails 中是不可能的,但我学到了一些非常有趣的东西。

    可以为作用域声明一个参数,然后不传入,默认传入父对象!

    所以,你可以这样做:

    class Game < ActiveRecord
      has_many :texts, -> (game) { Text.in_game_order_query(game) }
    

    信不信由你,你不必通过游戏。 Rails 会为你神奇地做到这一点。你可以这样做:

    game.texts
    

    不过,有一个警告。如果您启用了预加载,这目前在 Rails 中不起作用。如果这样做,您可能会收到以下警告:

    弃用警告:关联范围“文本”取决于实例(范围块接受参数)。预加载发生在创建单个实例之前。这意味着没有实例被传递到关联范围。这很可能会导致损坏或不正确的行为。这些关联的加入、预加载和预先加载已被弃用,将来将被删除。

    【讨论】:

    • Hey Mike :) 感谢您的回答,您有没有机会找到避免使用此方法进行 N+1 查询的方法?
    【解决方案2】:

    使用 PradeepKumar 的想法跟进,我发现以下解决方案可行

    假设一个类Block 有一个属性block_type 和一个容器类(比如Page),你可以有这样的东西:

    class Page
      ...
    
      has_many :blocks do
        def ordered_by_type
          # self is the array of blocks
          self.sort_by(&:block_type)
        end
      end
    
      ...
    end
    

    然后当你打电话时

    page.blocks.ordered_by_type
    

    你得到你想要的 - 由 Proc 定义。 显然,Proc 可能要复杂得多,并且在 SQL 调用中不工作,但在编译结果集之后。

    更新:
    一段时间后,我重新阅读了这篇文章和我的答案,我想知道你是否可以做一些简单的事情,就像你在帖子中基本上建议自己的另一种方法一样。

    如果您向Game 添加了一个名为ordered_texts 的方法

      def ordered_texts
        texts.in_game_order(self)
      end
    

    这能解决问题吗?或者此方法是否需要与其他 Game 关系方法链接?

    【讨论】:

      【解决方案3】:

      Association extension 有可能吗?

      看来你可以做到这一点:

      module Legacy
        def legacy_game_order
          order(proxy_association.owner.custom_texts_order)
        end
      end
      
      class Game << ActiveRecord::Base
        includes Legacy
        has_many :texts, :extend => Legacy
      
        def custom_texts_order
          # your custom query logic goes here
        end
      end
      

      这样,给定一个游戏实例,您应该能够访问实例的自定义查询,而无需传入 self:

      g = Game.find(123)
      g.texts.legacy_game_order
      

      【讨论】:

        【解决方案4】:

        这是一种你可以做到的方法,

           has_many :texts, :order => lambda { Text.in_game_order_query(self) }
        

        这是我通常不会推荐的另一种方式(但会起作用),

        has_many :texts do
          def game_order(game)
            find(:all, :order => Text.in_game_order_query(game))
          end
        end
        

        你可以打电话给他们,

        game.texts.game_order(game)
        

        【讨论】:

        • 试试,has_many :texts, lambda { {:order =&gt; Text.in_game_order_query(self)} }
        • “参数数量错误(1 代表 0)” - 我开始认为这是不可能的 :-( 我也尝试将此功能移至游戏模型,以便我可以做 self .in_game_order_query 无需传递参数,但没有运气。
        【解决方案5】:

        我不确定你的订单/查询在 in_game_order_query 类方法中是什么样的,但我相信你可以做到这一点

        has_many :texts, :finder_sql => proc{Text.in_game_order_query(self)}
        

        只是让您知道我以前从未使用过它,但如果您让我知道这是否适合您,我将不胜感激。

        查看http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many 以获取有关 :finder_sql 的更多文档

        【讨论】:

          【解决方案6】:

          我认为如果你想处理运行时信息,你应该这样做:

          has_many :texts, :order => proc{ {Text.in_game_order_query(self)} }
          

          【讨论】:

          • 双花括号给我一个语法错误,并且只有一组 {} 表示“无法访问 proc”。
          • 抱歉错字。只有一套牙套。但我不确定这是否适用于 :order 。我将它与 :conditions 一起使用。也许如果 proc 不起作用,请尝试 lambda 或 Proc.new
          猜你喜欢
          • 1970-01-01
          • 2011-08-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-04-06
          相关资源
          最近更新 更多