class SorbetRails::ModelRbiFormatter

Attributes

available_classes[R]
model_class[R]

Public Class Methods

new(model_class, available_classes) click to toggle source
# File lib/sorbet-rails/model_rbi_formatter.rb, line 24
def initialize(model_class, available_classes)
  @model_class = T.let(model_class, T.class_of(ActiveRecord::Base))
  @available_classes = T.let(available_classes, T::Set[String])
  begin
    # Load all dynamic instance methods of this model by instantiating a fake model
    @model_class.new unless @model_class.abstract_class?
  rescue StandardError => err
    puts "#{err.class}: Note: Unable to create new instance of #{model_class_name}"
  end
end

Public Instance Methods

generate_base_rbi(root) click to toggle source
# File lib/sorbet-rails/model_rbi_formatter.rb, line 70
def generate_base_rbi(root)
  # This is the backbone of the model_rbi_formatter.
  # It could live in a base plugin but I consider it not replacable and better to leave here
  model_relation_rbi = root.create_class(
    self.model_relation_class_name,
    superclass: "ActiveRecord::Relation",
  )
  model_relation_rbi.create_include(self.model_query_methods_returning_relation_module_name)
  model_relation_rbi.create_constant(
    "Elem",
    value: "type_member(fixed: #{model_class_name})",
  )

  model_assoc_relation_rbi = root.create_class(
    self.model_assoc_relation_class_name,
    superclass: "ActiveRecord::AssociationRelation",
  )
  model_assoc_relation_rbi.create_include(self.model_query_methods_returning_assoc_relation_module_name)
  model_assoc_relation_rbi.create_constant(
    "Elem",
    value: "type_member(fixed: #{model_class_name})",
  )

  collection_proxy_rbi = root.create_class(
    self.model_assoc_proxy_class_name,
    superclass: "ActiveRecord::Associations::CollectionProxy",
  )
  collection_proxy_rbi.create_include(self.model_query_methods_returning_assoc_relation_module_name)
  collection_proxy_rbi.create_constant(
    "Elem",
    value: "type_member(fixed: #{self.model_class_name})",
  )

  model_rbi = root.create_class(
    self.model_class_name,
    superclass: T.must(@model_class.superclass).name,
  )
  model_rbi.create_extend(self.model_query_methods_returning_relation_module_name)
  model_rbi.create_type_alias(
    self.model_relation_type_class_name,
    type: self.model_relation_type_alias
  )
end
generate_rbi() click to toggle source
# File lib/sorbet-rails/model_rbi_formatter.rb, line 36
  def generate_rbi
    puts "-- Generate sigs for #{model_class_name} --"

    # Collect the instances of each plugin into an array
    plugin_instances = self.class.get_plugins.map do |plugin_klass|
      plugin_klass.new(model_class, available_classes)
    end

    generator = Parlour::RbiGenerator.new(break_params: 3)
    run_plugins(plugin_instances, generator)
    # Generate the base after the plugins because when ConflictResolver merge the modules,
    # it'll put the modules at the last position merged. Putting the base stuff
    # last will keep the order consistent and minimize changes when new plugins are added.
    generate_base_rbi(generator.root)

    Parlour::ConflictResolver.new.resolve_conflicts(generator.root) do |msg, candidates|
      puts "Conflict: #{msg}. Skip following methods"
      candidates.each do |c|
        puts "- Method `#{c.name}` generated by #{c.generated_by.class.name}"
      end
      nil
    end

    rbi = <<~MESSAGE
      # This is an autogenerated file for dynamic methods in #{self.model_class_name}
      # Please rerun bundle exec rake rails_rbi:models[#{self.model_class_name}] to regenerate.

    MESSAGE

    rbi += generator.rbi
    return rbi
  end
run_plugins(plugins, generator) click to toggle source
# File lib/sorbet-rails/model_rbi_formatter.rb, line 121
def run_plugins(plugins, generator)
  allow_failure = ENV["SBR_DEBUG_MODE"] == "true"
  plugins.each do |plugin|
    begin
      generator.current_plugin = plugin
      plugin.generate(generator.root)
    rescue Exception => e
      raise e unless allow_failure
      puts "!!! Plugin #{plugin.class.name} threw an exception: #{e}"
    end
  end
end