Class: SorbetRails::ModelPlugins::ActiveRecordAssoc

Inherits:
Base
  • Object
show all
Defined in:
lib/sorbet-rails/model_plugins/active_record_assoc.rb

Constant Summary

Constants inherited from Base

Base::Parameter

Instance Attribute Summary

Attributes inherited from Base

#available_classes, #model_class

Instance Method Summary collapse

Methods included from SorbetRails::ModelUtils

#add_relation_query_method, #exists_class_method?, #exists_instance_method?, #model_assoc_proxy_class_name, #model_assoc_relation_class_name, #model_class, #model_class_name, #model_module_name, #model_relation_class_name

Constructor Details

#initialize(model_class, available_classes) ⇒ ActiveRecordAssoc

Returns a new instance of ActiveRecordAssoc.



5
6
7
8
# File 'lib/sorbet-rails/model_plugins/active_record_assoc.rb', line 5

def initialize(model_class, available_classes)
  super
  @columns_hash = @model_class.table_exists? ? @model_class.columns_hash : {}
end

Instance Method Details

#assoc_should_be_untyped?(reflection) ⇒ Boolean

Returns:

  • (Boolean)


95
96
97
# File 'lib/sorbet-rails/model_plugins/active_record_assoc.rb', line 95

def assoc_should_be_untyped?(reflection)
  polymorphic_assoc?(reflection) || !@available_classes.include?(reflection.klass.name)
end

#generate(root) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/sorbet-rails/model_plugins/active_record_assoc.rb', line 11

def generate(root)
  return unless @model_class.reflections.length > 0

  assoc_module_name = self.model_module_name("GeneratedAssociationMethods")
  assoc_module_rbi = root.create_module(assoc_module_name)
  assoc_module_rbi.create_extend("T::Sig")

  model_class_rbi = root.create_class(self.model_class_name)
  model_class_rbi.create_include(assoc_module_name)

  @model_class.reflections.sort.each do |assoc_name, reflection|
    reflection.collection? ?
      populate_collection_assoc_getter_setter(assoc_module_rbi, assoc_name, reflection) :
      populate_single_assoc_getter_setter(assoc_module_rbi, assoc_name, reflection)
  end
end

#polymorphic_assoc?(reflection) ⇒ Boolean

Returns:

  • (Boolean)


106
107
108
109
110
# File 'lib/sorbet-rails/model_plugins/active_record_assoc.rb', line 106

def polymorphic_assoc?(reflection)
  reflection.through_reflection ?
    polymorphic_assoc?(reflection.source_reflection) :
    reflection.polymorphic?
end

#populate_collection_assoc_getter_setter(assoc_module_rbi, assoc_name, reflection) ⇒ Object



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/sorbet-rails/model_plugins/active_record_assoc.rb', line 74

def populate_collection_assoc_getter_setter(assoc_module_rbi, assoc_name, reflection)
  # TODO allow people to specify the possible values of polymorphic associations
  assoc_class = assoc_should_be_untyped?(reflection) ? "T.untyped" : "::#{reflection.klass.name}"
  relation_class = relation_should_be_untyped?(reflection) ?
    "ActiveRecord::Associations::CollectionProxy" :
    "#{assoc_class}::ActiveRecord_Associations_CollectionProxy"

  assoc_module_rbi.create_method(
    assoc_name.to_s,
    return_type: relation_class,
  )
  assoc_module_rbi.create_method(
    "#{assoc_name}=",
    parameters: [
      Parameter.new("value", type: "T.any(T::Array[#{assoc_class}], #{relation_class})")
    ],
    return_type: nil,
  )
end

#populate_single_assoc_getter_setter(assoc_module_rbi, assoc_name, reflection) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/sorbet-rails/model_plugins/active_record_assoc.rb', line 35

def populate_single_assoc_getter_setter(assoc_module_rbi, assoc_name, reflection)
  # TODO allow people to specify the possible values of polymorphic associations
  assoc_class = assoc_should_be_untyped?(reflection) ? "T.untyped" : "::#{reflection.klass.name}"
  assoc_type = "T.nilable(#{assoc_class})"
  if reflection.belongs_to?
    column_def = @columns_hash[reflection.foreign_key.to_s]
    if ENV["RAILS_VERSION"] == "4.2"
      # Before Rails 5, belongs_to relations were nilable by default
      # if this is a belongs_to connection, we may be able to detect whether
      # this field is required & use a stronger type
      if column_def
        assoc_type = assoc_class if !column_def.null
      end
    else
      # In Rails 5 and later, belongs_to are required unless specified to be optional
      assoc_type = assoc_class if !reflection.options[:optional]
    end
  end

  assoc_module_rbi.create_method(
    assoc_name.to_s,
    return_type: assoc_type,
  )
  assoc_module_rbi.create_method(
    "#{assoc_name}=",
    parameters: [
      Parameter.new("value", type: assoc_type)
    ],
    return_type: nil,
  )
end

#relation_should_be_untyped?(reflection) ⇒ Boolean

Returns:

  • (Boolean)


100
101
102
103
# File 'lib/sorbet-rails/model_plugins/active_record_assoc.rb', line 100

def relation_should_be_untyped?(reflection)
  # only type the relation we'll generate
  assoc_should_be_untyped?(reflection) || !@available_classes.include?(reflection.klass.name)
end