3

我有一个 Rails 模型has_many items

class Plan < ApplicationRecord
  extend T::Sig

  has_many :items, dependent: :destroy

  before_save do
    # hyper simple test method to illustrat problem
    puts items
  end
end

然而,冰糕似乎与has_many :items. 当我运行 Sorbet 类型检查时,我收到以下错误:

$ srb tc
app/models/plan.rb:11: Method items does not exist on T.class_of(Plan) https://srb.help/7003
    11 |    items
            ^^^^^
  Did you mean:
    sorbet/rails-rbi/models/plan.rbi:86: Plan::GeneratedAssociationMethods#items
    86 |  def items; end

Sorbet 的问题的答案是肯定的——我的意思是那种方法。哪里来的混乱?为什么.itemsRBI 文件中的定义不能满足 Sorbet 知道该方法定义在哪里的需要?

4

2 回答 2

4

好的,结果证明这是对 Rails(Ruby?)而不是 Sorbet 的误解。跳过这实际上是冰糕的一个要点,因为它有助于发现和解决这个问题。

问题在于,当您将块传递给 时before_save,该块会在类 ( Plan) 上调用,而不是在实例 ( plan) 上调用。相反,将一个实例传递给它。

所以取原始代码:

class Plan < ApplicationRecord
  extend T::Sig

  has_many :items, dependent: :destroy

  before_save do
    # hyper simple test method to illustrate problem
    puts items
  end
end

这将导致Plan.before_save(plan). plan的实例在哪里Plan。因此,在上面的示例中,items被无中生有地拉出来并且不起作用。

两种有效的语法

class Plan < ApplicationRecord
  extend T::Sig

  has_many :items, dependent: :destroy

  before_save do |plan| # <= notice the argument
    puts plan.items
  end
end

将工作。也将如此:

class Plan < ApplicationRecord
  extend T::Sig

  has_many :items, dependent: :destroy
  before_save :put_items

  def put_items
    puts items
  end
end

我不太确定是什么让第二个起作用,无论是 Ruby 魔法还是 Rails 魔法,但有时我喜欢的魔法太多了。

于 2021-05-26T15:11:04.203 回答
0

这样做的原因是传递给before_save的过程是在类实例的上下文中运行的,而不是在默认情况下 Sorbet 无法理解的类本身。Sorbet 提供T.bind了这种情况,它允许您告诉 Sorbet 将其self视为块内的不同类型

class Plan < ApplicationRecord
  extend T::Sig

  has_many :items, dependent: :destroy

  before_save do
    T.bind(self, Plan) # treats `self` as `Plan` instead of `T.class_of(Plan)`
    
    # hyper simple test method to illustrate problem
    puts items # type-checks fine
  end
end
于 2021-07-09T14:12:05.480 回答