Rails关联块会影响查询范围。为什么?

时间:2017-11-02 13:30:16

标签: ruby-on-rails

我在Rails 5.1应用程序中有两个模型。 Master是主要模型,Item属于它。对于特殊用例,我需要覆盖master.items.build的#build方法。在这个新的构建方法中,我必须进行查询以检查其他项目中的某些条件。

执行此操作时,我会遇到一些意外(由我)行为,这样关联块似乎会改变查询的范围。

master.rb

class Master < ApplicationRecord

  has_many :items,  inverse_of: :master do
    def build att=nil
      item = self.new(master: proxy_association.owner)      
      item.description1 = self.where(master_id: nil).to_sql
      item.description2 = self.unscoped.where(master_id: nil).to_sql
      item
    end
  end

end

item.rb的

class Item < ApplicationRecord
  belongs_to :master

  before_create -> {
    self.description1 = self.class.where(master_id: nil).to_sql
    self.description2 = self.class.unscoped.where(master_id: nil).to_sql
  }

end

master_test.rb

require 'test_helper'

class MasterTest < ActiveSupport::TestCase

  test "master build outside association works as expected" do

    m = Master.create! name: 'Bob'
    item = Item.create master: m, name: 'Wall'

    puts "Master: #{m}"
    puts "self.where(master_id: nil).to_sql"
    puts item.description1
    puts "self.unscoped.where(master_id: nil).to_sql"
    puts item.description2

    # Flawed understanding would expect the queries to be the same
    assert item.description1 == item.description2
  end

  test "master association blocks add an unexpected scope" do

    m = Master.create! name: 'Bob'
    item = m.items.build name: 'Wall'

    puts "Master: #{m}"
    puts "self.where(master_id: nil).to_sql"
    puts item.description1
    puts "self.unscoped.where(master_id: nil).to_sql"
    puts item.description2

    # Flawed understanding would expect the queries to be the same
    assert item.description1 == item.description2
  end
end

简而言之,第一次测试的结果符合预期。无范围和标准查询都按预期运行。第二个测试的结果,在关联块内调用相同的查询导致:

Master: #<Master:0x0055c3ee11b770>
self.where(master_id: nil).to_sql
SELECT "items".* FROM "items" WHERE "items"."master_id" = 980190963 AND "items"."master_id" IS NULL
self.unscoped.where(master_id: nil).to_sql
SELECT "items".* FROM "items" WHERE "items"."master_id" IS NULL

我没有在任何地方看到这个记录,并希望有人能指出我在关联块内部进行自动范围界定的原因,以及解释的地方。我会毫无疑问地接受它,但这种行为似乎在某些时候发生了变化(基于已经开始失败的测试的应用程序)。我也在旧版本的4.2.9应用程序中体验过它,我知道它在过去没有这个范围的情况下工作。

如果需要重现,这是一个包含测试应用的GitHub仓库:https://github.com/philayres/test_assoc_blocks

应该像rails db:migraterake test一样简单来演示它。

我愿意接受想法和解释。

1 个答案:

答案 0 :(得分:0)

也许我没有正确理解这一点:

m = Master.create! name: 'Bob'
item = m.items.build name: 'Wall'

puts "Master: #{m}"
puts "self.where(master_id: nil).to_sql"
puts item.description1
puts "self.unscoped.where(master_id: nil).to_sql"
puts item.description2

# Flawed understanding would expect the queries to be the same
assert item.description1 == item.description2

在这种情况下,您将范围传递给构建方法:

item = m.items.build name: 'Wall'

此范围是Item.where(master_id:m.id)。

在覆盖的构建方法中,您可以执行以下操作:

item.description1 = self.where(master_id: nil).to_sql

self将成为此类的范围实例 - 将成为

Item.where(master_id: m.id).where(master_id: nil).to_sql

对于第二种方法

self.unscoped.where(master_id: nil).to_sql

将删除第一个范围,然后应用where子句。我不确定你为什么要打电话

m.items

如果您特别不希望与m。

相关联的项目

要验证这一点,请尝试检查

self.to_sql

并且自己实际上在街区内。