【问题标题】:Relation on scope on ActiveRecordActiveRecord 范围的关系
【发布时间】:2012-05-21 16:50:52
【问题描述】:

这是我使用 Rails 3.2 的 ActiveRecord 模型:

class User < ActiveRecord::Base
    has_one :criterion
    has_many :user_offer_choices
end

class Offer < ActiveRecord::Base
    has_many :user_offer_choices

    def seen
        user_offer_choices.where(seen: true)
    end

    def accepted
        user_offer_choices.where(accepted: true)
    end
end

class Criterion < ActiveRecord::Base
    belongs_to :user
end

class UserOfferChoice < ActiveRecord::Base
    belongs_to :user
    belongs_to :offer
end

我想获取看到报价的用户的所有标准。类似的东西:

Offer.find(11).seen.users.criterions

但我不知道如何使用 ActiveRecord

我知道我可以这样做:

Criterion.joins(user: { user_offer_choices: :offer }).where(user: { user_offer_choices: {accepted: true, offer_id: 11}  } )

但我希望能够在优惠中使用我的范围(已查看并已接受)。那我该怎么做呢?

编辑: 我找到了我要找的,Arel 的合并方法:http://benhoskin.gs/2012/07/04/arel-merge-a-hidden-gem

【问题讨论】:

    标签: ruby-on-rails activerecord ruby-on-rails-3.2


    【解决方案1】:

    首先,您真正想要的是为您的选择定义一个范围。

    class UserOfferChoice < ActiveRecord::Base
      belongs_to :user
      belongs_to :offer
    
      scope :seen, where(seen: true)
      scope :accepted, where(accepted: true)
    end
    

    这允许你这样做

    Offer.find(11).user_offer_choices.seen
    

    并获得标准:

    Offer.find(1).user_offer_choices.seen.map{|choice| choice.user}.map{|user| user.criterion}
    

    现在,这可以通过 Offer 类中的 has many through 来清理。

    class Offer < ActiveRecord::Base
      has_many :user_offer_choices
      has_many :users, :through => :user_offer_choices
    end
    

    但这将我们带到用户,但跳过了范围。

    Offer.find(1).users
    

    现在,您可以使用 Rails 3 范围执行一个技巧,而使用 Rails 2.3.5 named_scopes 则无法做到。 named_scopes 将散列作为参数,但返回一个关系。 Rails 3 范围采用关系,例如来自 where 等查询方法。所以你可以在用户中定义一个范围,使用你的选择类中定义的范围

    class User < ActiveRecord::Base
      has_one :criterion
      has_many :user_offer_choices
      has_many :offers, :through => :user_offer_choices
    
      scope :seen, UserOfferChoice.seen
      scope :accepted, UserOfferChoice.accepted
    end
    

    这允许我们这样做:

    Offer.find(1).users.seen
    

    地图现在看起来像这样:

    Offer.find(1).users.seen.map{|user| user.criterion}
    

    顺便说一句,criteria 的复数形式是criteria。当我读到它时,我脑海中的听力标准,很痛苦。你可以这样做让 Rails 知道复数:

    config/initializers/inflections.rb
    ActiveSupport::Inflector.inflections do |inflect|
      inflect.plural /^criterion$/i, 'criteria'
    end
    

    【讨论】:

    • 我不想使用 map(或其他 Enum 方法),因为它是普通的 Ruby,ActiveRecord 不会生成 SQL。
    • 为什么使用 SQL 而不是 map 很重要?在我看来,这是一个任意的人为要求。如果您注意到,我演示了如何从嵌套两个 map 调用(这很麻烦且难以阅读)到摆脱一个 map 并使用 SQL 和您的范围的解决方案。您可能会尝试扩展该技术,但我认为该解决方案会很麻烦。 (在下一条评论中更多关于 map 与 SQL 的信息。)
    • 使用 map 确实有一个缺点,它会循环遍历集合,但随后会在循环中调用数据库以取消引用关联。当我将选择映射更改为 has-many-through 时,它对给定的 Offer 对象进行了一次 SQL 调用,并为我们构建了连接。但是,在我们确定整个网络响应时间过长之前,我不会担心性能问题。
    • 我同意你的看法。但是,为什么要在集合上使用映射,因为只使用一个(简单)SQL 语句来获取所有对象非常简单?
    • 我同意。当 SQL 可以得到正确的结果时,只得到结果会更有效率。我发布了很多以 SQL 为中心的答案,其中我展示了获得正确结果的 SQL,然后将它们转换为查询方法(或查找参数)。问题是,我在这方面做了很多工作(因为我想炫耀从另一个类调用范围),从没有得到正确结果的代码开始,并且没有以显示的方式定义范围你查了范围。我认为你可以在剩下的路上做一些努力。 (看看我的其他答案。)
    猜你喜欢
    • 1970-01-01
    • 2016-07-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-23
    • 2012-10-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多