Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ActiveRecordAssociations compiler crashes on association defined with ActiveHash #1286

Closed
ohbarye opened this issue Nov 30, 2022 · 5 comments

Comments

@ohbarye
Copy link

ohbarye commented Nov 30, 2022

I'm quite new to tapioca, so please tell me if it's not the right place to report.

Problem

ActiveRecordAssociations compiler does not work with active_hash.

Reproduce

I'm using active_hash with ActiveRecord like this.

class TapiocaTest < ApplicationRecord
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :prefecture
end

I ran the command below then got the error.

$ bin/tapioca dsl TapiocaTest --only=ActiveRecordAssociations --verbose

Actual Behavior

$ bin/tapioca dsl TapiocaTest --only=ActiveRecordAssociations --verbose
Loading Rails application... Done
Loading DSL compiler classes... Done
Compiling DSL RBI files...

Error: `Tapioca::Dsl::Compilers::ActiveRecordAssociations` failed to generate RBI for `TapiocaTest`
/usr/local/bundle/ruby/3.1.0/gems/activerecord-7.0.4/lib/active_record/reflection.rb:436:in `compute_class': Rails couldn't find a valid model for Prefecture association. Please provide the :class_name option on the association declaration. If :class_name is already provided, make sure it's an ActiveRecord::Base subclass. (ArgumentError)
        from /usr/local/bundle/ruby/3.1.0/gems/activerecord-7.0.4/lib/active_record/reflection.rb:382:in `klass'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/compilers/active_record_associations.rb:292:in `validate_reflection!'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/compilers/active_record_associations.rb:269:in `type_for'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/compilers/active_record_associations.rb:187:in `populate_single_assoc_getter_setter'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/compilers/active_record_associations.rb:166:in `block in populate_associations'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/compilers/active_record_associations.rb:162:in `each'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/compilers/active_record_associations.rb:162:in `populate_associations'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/compilers/active_record_associations.rb:131:in `block (2 levels) in decorate'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/rbi_ext/model.rb:27:in `block in create_module'
        from <internal:kernel>:90:in `tap'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/rbi_ext/model.rb:26:in `create_module'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/compilers/active_record_associations.rb:129:in `block in decorate'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/rbi_ext/model.rb:40:in `block in create_class'
        from <internal:kernel>:90:in `tap'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/rbi_ext/model.rb:39:in `create_class'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/rbi_ext/model.rb:16:in `create_path'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/compilers/active_record_associations.rb:128:in `decorate'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/pipeline.rb:161:in `block in rbi_for_constant'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/pipeline.rb:157:in `each'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/pipeline.rb:157:in `rbi_for_constant'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/pipeline.rb:68:in `block in run'
        from /usr/local/bundle/ruby/3.1.0/gems/parallel-1.22.1/lib/parallel.rb:587:in `call_with_index'
        from /usr/local/bundle/ruby/3.1.0/gems/parallel-1.22.1/lib/parallel.rb:557:in `process_incoming_jobs'
        from /usr/local/bundle/ruby/3.1.0/gems/parallel-1.22.1/lib/parallel.rb:537:in `block in worker'
        from /usr/local/bundle/ruby/3.1.0/gems/parallel-1.22.1/lib/parallel.rb:528:in `fork'
        from /usr/local/bundle/ruby/3.1.0/gems/parallel-1.22.1/lib/parallel.rb:528:in `worker'
        from /usr/local/bundle/ruby/3.1.0/gems/parallel-1.22.1/lib/parallel.rb:519:in `block in create_workers'
        from /usr/local/bundle/ruby/3.1.0/gems/parallel-1.22.1/lib/parallel.rb:518:in `each'
        from /usr/local/bundle/ruby/3.1.0/gems/parallel-1.22.1/lib/parallel.rb:518:in `each_with_index'
        from /usr/local/bundle/ruby/3.1.0/gems/parallel-1.22.1/lib/parallel.rb:518:in `create_workers'
        from /usr/local/bundle/ruby/3.1.0/gems/parallel-1.22.1/lib/parallel.rb:457:in `work_in_processes'
        from /usr/local/bundle/ruby/3.1.0/gems/parallel-1.22.1/lib/parallel.rb:294:in `map'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/executor.rb:31:in `run_in_parallel'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/dsl/pipeline.rb:67:in `run'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/commands/dsl.rb:109:in `execute'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/cli.rb:152:in `block in dsl'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca.rb:23:in `block in silence_warnings'
        from /usr/local/lib/ruby/3.1.0/rubygems/user_interaction.rb:46:in `use_ui'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca.rb:22:in `silence_warnings'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `bind_call'
        from /usr/local/bundle/ruby/3.1.0/gems/sorbet-runtime-0.5.10563/lib/types/private/methods/_methods.rb:272:in `block in _on_method_added'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/lib/tapioca/cli.rb:148:in `dsl'
        from /usr/local/bundle/ruby/3.1.0/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
        from /usr/local/bundle/ruby/3.1.0/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
        from /usr/local/bundle/ruby/3.1.0/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
        from /usr/local/bundle/ruby/3.1.0/gems/thor-1.2.1/lib/thor/base.rb:485:in `start'
        from /usr/local/bundle/ruby/3.1.0/gems/tapioca-0.10.3/exe/tapioca:23:in `<top (required)>'
        from bin/tapioca:27:in `load'
        from bin/tapioca:27:in `<main>'

Expected Behavior

No errors and tapioca outputs RBI (regardless of whether it includes the association defined with active_hash).

Environment

$ bin/tapioca --version
Tapioca v0.10.3

$ ruby -v
ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-linux]

$ bin/rails -v
Rails 7.0.4
  • active_hash (3.1.1)

tapioca config is empty.

gem:
dsl:
@KaanOzkan
Copy link
Contributor

I'm not familiar with active_hash but maybe this helps.

The compiler requires a valid association and utilizes the database state to generate RBIs. I don't know the internals of active_hash but maybe it's not setting up the AR side correctly?

Do you have a model called Prefecture that inherits from ActiveHash::Base? If that's the case there might be an underlying issue with active_hash. A workaround to continue using tapioca would be to exclude this compiler in your tapioca/config.yml under dsl key https://github.com/shopify/tapioca#configuration.

If active_hash doesn't translate anything to AR then I don't think its the ActiveRecordAssociations compiler's responsibility. Maybe it can be done in a different compiler using the state that active_hash exposes.

@ohbarye
Copy link
Author

ohbarye commented Dec 1, 2022

Thanks for your help.

The compiler requires a valid association and utilizes the database state to generate RBIs.
Do you have a model called Prefecture that inherits from ActiveHash::Base? If that's the case there might be an underlying issue with active_hash.

Yes. I have a model like below.

# Example app/models/prefecture.rb
class Prefecture < ActiveHash::Base
  self.data = [
    {:id => 1, :name => "Foo"},
    {:id => 2, :name => "Bar"}
  ]
end

I might be catching on to the cause. active_hash does not use any database as its datastore. Instead, it uses an in-memory Hash or YAML, or JSON. And I think active_hash doesn't translate anything to AR because it's decoupled with AR.

> ActiveHash::Base.ancestors
=> 
[ActiveHash::Base,                                                   
 ActiveModel::Conversion,                                            
 ActiveSupport::Dependencies::RequireDependency,                     
 ActiveSupport::ToJsonWithActiveSupportEncoder,                      
 Object,                                                             
 PP::ObjectMixin,                                                    
 ActiveSupport::Tryable,                                             
 JSON::Ext::Generator::GeneratorMethods::Object,                     
 Kernel,                                                             
 BasicObject]   

Maybe it can be done in a different compiler using the state that active_hash exposes.

I'm still not sure about the approach though, do you mean I need to implement another compiler? Or, can any existing compiler do this?


In the meantime, I'm going to exclude the compiler in the way you taught me. 👍

@KaanOzkan
Copy link
Contributor

Thanks for the context. You'd have to write a custom compiler for ActiveHash. There's detailed documentation here. In the end it'd look similar to the assocations compiler.

In gather_constants you'd select descendants of ActiveHash::Base. decorate is the heart of the compiler and you'd need a way to retrieve the set of associations on a given constant. AR exposes this information with #reflections method:

constant.reflections.each do |association_name, reflection|

Once you have that information you can generate modules and methods for your RBI files using the same API as the other compilers (mod.create_module(), klass.create_method())

@ohbarye
Copy link
Author

ohbarye commented Dec 2, 2022

Thanks for the detailed advice. I'll try to write a custom compiler referring to your guide.

@ohbarye
Copy link
Author

ohbarye commented Dec 3, 2022

I found that active_hash repo has an issue related to this problem: active-hash/active_hash#267. According to that, this problem could happen with only Rails 7.0 or above. And I confirmed ActiveRecordAssociations compiler works even with active_hash once the issue gets resolved.

So I'm closing this issue. Thanks for your kind help!

@ohbarye ohbarye closed this as completed Dec 3, 2022
@ohbarye ohbarye changed the title ActiveRecordAssociations compiler crushes on association defined with ActiveHash ActiveRecordAssociations compiler crashes on association defined with ActiveHash Dec 3, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants