ActiveModel::Attribute generates non nilable attribute that should be nilable
I have the following class (whole content):
# app/models/vacancy.rb
# typed: true
# frozen_string_literal: true
class Vacancy < ApplicationRecord
attribute :startlatest, :datetime
end
I generates the DSL to have a non nilable startlatest:
# sorbet/rbi/dsl/vacancy.rbi
class Vacancy
include GeneratedAttributeMethods
# [...]
private
module GeneratedAttributeMethods
# [...]
sig { returns(::ActiveSupport::TimeWithZone) }
def startlatest; end
sig { params(value: ::ActiveSupport::TimeWithZone).returns(::ActiveSupport::TimeWithZone) }
def startlatest=(value); end
# [...]
end
end
Why does it assume the attribute is not nilable in this case? Shouldn't it be nilable?
Tapioca v0.16.11
Hi, @doits! Thanks for opening this issue. You said "I generates the DSL to have a non nilable startlatest" -- what does that mean? What command did you use to generate RBI?
Oh sorry, it was a typo. It should have been "It generates ...". So the command is:
bundle exec tapioca dsl Vacancy
It is a little bit more complicated than I thought. To trigger the behaviour, the following must all apply:
- The model must be an
ActiveRecord::Base-model - A database table for the model must exist
- The attribute must not have a column in the database
In my case all of these apply (I have stuff saved in the database for that model, but the attribute in question is a virtual one), that is why I stumbled upon this.
Here's a step by step instruction how you can replicate and see the behaviour:
-
create a new rails app and add required gems
rails new sorbet_test cd sorbet_test bundle add tapioca --version='~> 0.17' -
init sorbet
bundle exec tapioca init -
create the model
cat << EOF > app/models/vacancy.rb class Vacancy < ApplicationRecord attribute :startlatest, :datetime end EOF -
generate the dsl
bundle exec tapioca dsl Vacancy -
👀 notice
sorbet/rbi/dsl/vacancy.rbidoes not have a method forstartlatest❓ --> it should have one that is nilable -
Create the model's table in database
echo 'ActiveRecord::Migration.create_table(:vacancies)' | bundle exec rails c -
regenerate the dsl
bundle exec tapioca dsl Vacancy -
👁️ notice
sorbet/rbi/dsl/vacancy.rbidoes have a method forstartlatestthat is not nilable ❌ --> it should have one that is nilablesig { returns(::ActiveSupport::TimeWithZone) } def startlatest; end -
add a the
startlatestcolumn into db (which is nullable by default)echo 'ActiveRecord::Migration.add_column(:vacancies, :startlatest, :datetime)' | bundle exec rails c -
regenerate the dsl
bundle exec tapioca dsl Vacancy -
👁️ notice
sorbet/rbi/dsl/vacancy.rbidoes have a method forstartlatestthat is nilable ✅sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) } def startlatest; end
I think I found a solution after reading https://github.com/Shopify/tapioca/blob/main/manual/compiler_activerecordcolumns.md, which seems to be the compiler that adds this methods. Setting ActiveRecordColumnTypes to nilable makes all columns nilable by default:
# sorbet/tapioca/config.yml
dsl:
compiler_options:
ActiveRecordColumnTypes: nilable
Now it generates what I think it should:
sig { returns(T.nilable(::ActiveSupport::TimeWithZone)) }
def startlatest; end