I have a Rails model which 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
However Sorbet can't seem to relate to the has_many :items
. When I run Sorbet typechecking I get the following error:
$ 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
The answer to Sorbet's question is yes - I do mean that method. Where is the confusion coming from? Why doesn't the definition of .items
in the RBI file satisfy Sorbet's need to know where this method is defined?
Ok so this turned out to be a misunderstanding of Rails (Ruby?) rather than Sorbet. Skipping ahead this is actually a point to sorbet because it helped spot and solve this problem.
The problem is that when you pass a block to
before_save
, the block gets called on the class (Plan
), not the instance (plan
). Instead, an instance is passed into it.So taking the original code:
This would result in the execution of
Plan.before_save(plan)
. Whereplan
is the instance ofPlan
. So in the example above,items
is being pulled out of thin air and won't work.Two syntaxes which do work
will work. And so will:
I'm not quite sure what makes the second one work, whether it's Ruby magic or Rails magic but sometimes there's just too much magic for my liking.