Shopify / tapioca

The swiss army knife of RBI generation
MIT License
728 stars 121 forks source link

`NameError: uninitialized constant` when trying to generate DSL for ERB fixture that references env constants #1938

Closed eapache-opslevel closed 3 months ago

eapache-opslevel commented 3 months ago

We have an erb fixture file (with a bunch of fields/fixtures elided) that does the following:

<% FIXTURE_DELETED_TIME ||= FIXTURE_CURRENT_TIME - 1.day %>

fixture_name:
  deleted_at: <%= FIXTURE_DELETED_TIME %>

FIXTURE_CURRENT_TIME is defined in config/environments/test.rb.

When trying to run tapioca dsl on this, with tapioca 0.14.4, it fails with:

/Users/eapache/src/gitlab.com/OpsLevel/test/fixtures/deleted_records.yml:1:in `get_binding': NameError: uninitialized constant ActiveRecord::FixtureSet::RenderContext::FIXTURE_CURRENT_TIME (Parallel::UndumpableException)
    from /Users/eapache/.rvm/rubies/ruby-3.2.2/lib/ruby/3.2.0/erb.rb:429:in `eval'
    from /Users/eapache/.rvm/rubies/ruby-3.2.2/lib/ruby/3.2.0/erb.rb:429:in `result'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/activesupport-7.1.3.4/lib/active_support/configuration_file.rb:48:in `render'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/activesupport-7.1.3.4/lib/active_support/configuration_file.rb:22:in `parse'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/activesupport-7.1.3.4/lib/active_support/configuration_file.rb:18:in `parse'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/activerecord-7.1.3.4/lib/active_record/fixture_set/file.rb:53:in `raw_rows'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/activerecord-7.1.3.4/lib/active_record/fixture_set/file.rb:42:in `config_row'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/activerecord-7.1.3.4/lib/active_record/fixture_set/file.rb:28:in `model_class'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/compilers/active_record_fixtures.rb:195:in `block (3 levels) in fixture_class_mapping_from_fixture_files'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/activerecord-7.1.3.4/lib/active_record/fixture_set/file.rb:16:in `open'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/compilers/active_record_fixtures.rb:193:in `block (2 levels) in fixture_class_mapping_from_fixture_files'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/compilers/active_record_fixtures.rb:190:in `select'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/compilers/active_record_fixtures.rb:190:in `block in fixture_class_mapping_from_fixture_files'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/compilers/active_record_fixtures.rb:189:in `each'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/compilers/active_record_fixtures.rb:189:in `each_with_object'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/compilers/active_record_fixtures.rb:189:in `fixture_class_mapping_from_fixture_files'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `bind_call'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `block in _on_method_added'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/compilers/active_record_fixtures.rb:50:in `block in decorate'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/compilers/active_record_fixtures.rb:50:in `select!'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/compilers/active_record_fixtures.rb:50:in `decorate'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `bind_call'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `block in _on_method_added'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/pipeline.rb:204:in `block in rbi_for_constant'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/pipeline.rb:200:in `each'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/pipeline.rb:200:in `rbi_for_constant'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/pipeline.rb:88:in `block in run'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/parallel-1.24.0/lib/parallel.rb:627:in `call_with_index'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/parallel-1.24.0/lib/parallel.rb:597:in `process_incoming_jobs'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/parallel-1.24.0/lib/parallel.rb:577:in `block in worker'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/parallel-1.24.0/lib/parallel.rb:568:in `fork'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/parallel-1.24.0/lib/parallel.rb:568:in `worker'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/parallel-1.24.0/lib/parallel.rb:559:in `block in create_workers'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/parallel-1.24.0/lib/parallel.rb:558:in `each'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/parallel-1.24.0/lib/parallel.rb:558:in `each_with_index'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/parallel-1.24.0/lib/parallel.rb:558:in `create_workers'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/parallel-1.24.0/lib/parallel.rb:497:in `work_in_processes'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/parallel-1.24.0/lib/parallel.rb:291:in `map'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/executor.rb:31:in `run_in_parallel'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `bind_call'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `block in _on_method_added'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/dsl/pipeline.rb:87:in `run'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `bind_call'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `block in _on_method_added'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/commands/abstract_dsl.rb:79:in `generate_dsl_rbi_files'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `bind_call'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `block in _on_method_added'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/commands/dsl_generate.rb:16:in `execute'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `bind_call'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `block in _on_method_added'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/commands/command.rb:27:in `block in run'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca.rb:23:in `block in silence_warnings'
    from /Users/eapache/.rvm/rubies/ruby-3.2.2/lib/ruby/site_ruby/3.2.0/rubygems/user_interaction.rb:46:in `use_ui'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca.rb:22:in `silence_warnings'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `bind_call'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `block in _on_method_added'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/commands/command.rb:26:in `run'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `bind_call'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/sorbet-runtime-0.5.11178/lib/types/private/methods/_methods.rb:274:in `block in _on_method_added'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/lib/tapioca/cli.rb:179:in `dsl'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/thor-1.3.1/lib/thor/command.rb:28:in `run'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/thor-1.3.1/lib/thor/invocation.rb:127:in `invoke_command'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/thor-1.3.1/lib/thor.rb:527:in `dispatch'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/thor-1.3.1/lib/thor/base.rb:584:in `start'
    from /Users/eapache/.rvm/gems/ruby-3.2.2@opslevel/gems/tapioca-0.14.4/exe/tapioca:25:in `<top (required)>'
    from bin/tapioca:27:in `load'
    from bin/tapioca:27:in `<main>'

It used to work on older tapioca versions. I haven't fully bisected, but at a guess the culprit is https://github.com/Shopify/tapioca/pull/1871 cc @TobiasBales @vinistock

KaanOzkan commented 3 months ago

FIXTURE_CURRENT_TIME is defined in config/environments/test.rb

By default tapioca boots the application in development mode: https://github.com/Shopify/tapioca/blob/5b99119da1fc85c09603551d2ead4aaf94694377/lib/tapioca.rb#L56 I suggest making this available in development.

On the tapioca end we could add a rescue for this case but then the compiler will never work for this fixture. I think it's better for the application to be aware of this error and resolve it.

eapache-opslevel commented 3 months ago

By default tapioca boots the application in development mode:

Ah, ok, I didn't know that. It's a bit awkward for test code to be evaluated in development mode, but file-dependent env booting would be a lot of complication for tapioca, for sure. Now that I know, I think we can work around this pretty easily.

Thanks for the feedback!