Module: RactorRailsShim::ReflectionAssociationPatch

Includes:
ReflectionMemoPatch
Defined in:
lib/ractor_rails_shim/patches/activerecord_reflection.rb

Instance Method Summary collapse

Methods included from ReflectionMemoPatch

#rrs_refl_cache

Instance Method Details

#active_record_primary_keyObject



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/ractor_rails_shim/patches/activerecord_reflection.rb', line 153

def active_record_primary_key
  custom_primary_key = options[:primary_key]
  rrs_refl_cache[[object_id, :active_record_primary_key]] ||=
    if custom_primary_key
      if custom_primary_key.is_a?(Array)
        custom_primary_key.map { |pk| pk.to_s.freeze }.freeze
      else
        custom_primary_key.to_s.freeze
      end
    elsif active_record.has_query_constraints? || options[:query_constraints]
      active_record.query_constraints_list
    elsif active_record.composite_primary_key?
      primary_key = primary_key(active_record)
      primary_key.include?("id") ? "id" : primary_key.freeze
    else
      primary_key(active_record).freeze
    end
end

#association_foreign_keyObject



149
150
151
# File 'lib/ractor_rails_shim/patches/activerecord_reflection.rb', line 149

def association_foreign_key
  rrs_refl_cache[[object_id, :association_foreign_key]] ||= -(options[:association_foreign_key]&.to_s || class_name.foreign_key)
end

#association_primary_key(klass = nil) ⇒ Object



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/ractor_rails_shim/patches/activerecord_reflection.rb', line 172

def association_primary_key(klass = nil)
  if primary_key = options[:primary_key]
    rrs_refl_cache[[object_id, :association_primary_key, klass]] ||=
      if primary_key.is_a?(Array)
        primary_key.map { |pk| pk.to_s.freeze }.freeze
      else
        -primary_key.to_s
      end
  elsif (klass || self.klass).has_query_constraints? || options[:query_constraints]
    rrs_refl_cache[[object_id, :association_primary_key, klass]] ||= -Array(active_record.query_constraints_list).first.to_s
  elsif active_record.composite_primary_key?
    rrs_refl_cache[[object_id, :association_primary_key, klass]] ||= primary_key(klass || self.klass)
  else
    rrs_refl_cache[[object_id, :association_primary_key, klass]] ||= primary_key(klass || self.klass).to_s
  end
end

#check_validity!Object

check_validity! memoizes @validated by writing it on the (frozen, shared) reflection — which raises FrozenError in a worker Ractor. Reimplement it to store the validated flag in the per-worker cache instead. The body mirrors Rails' (minus the @validated write) and only reads via the other patched (cache-backed) reflection accessors, so it performs no ivar writes on the frozen object.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/ractor_rails_shim/patches/activerecord_reflection.rb', line 93

def check_validity!
  cache = rrs_refl_cache
  return if cache[[object_id, :validated]]

  check_validity_of_inverse!

  if !polymorphic? && (klass.composite_primary_key? || active_record.composite_primary_key?)
    if (has_one? || collection?) && Array(active_record_primary_key).length != Array(foreign_key).length
      raise ::ActiveRecord::Reflection::CompositePrimaryKeyMismatchError.new(self)
    elsif belongs_to? && Array(association_primary_key).length != Array(foreign_key).length
      raise ::ActiveRecord::Reflection::CompositePrimaryKeyMismatchError.new(self)
    end
  end

  cache[[object_id, :validated]] = true
end

#foreign_key(infer_from_inverse_of: true) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/ractor_rails_shim/patches/activerecord_reflection.rb', line 123

def foreign_key(infer_from_inverse_of: true)
  rrs_refl_cache[[object_id, :foreign_key, infer_from_inverse_of]] ||=
    if options[:foreign_key]
      if options[:foreign_key].is_a?(Array)
        options[:foreign_key].map { |fk| -fk.to_s.freeze }.freeze
      else
        options[:foreign_key].to_s.freeze
      end
    elsif options[:query_constraints]
      options[:query_constraints].map { |fk| -fk.to_s.freeze }.freeze
    else
      derived_fk = derive_foreign_key(infer_from_inverse_of: infer_from_inverse_of)

      if active_record.has_query_constraints?
        derived_fk = derive_fk_query_constraints(derived_fk)
      end

      if derived_fk.is_a?(Array)
        derived_fk.map! { |fk| -fk.freeze }
        derived_fk.freeze
      else
        -derived_fk.freeze
      end
    end
end

#inverse_nameObject

inverse_name writes @inverse_name on the frozen, shared reflection in a worker Ractor (Rails only sets it lazily, so it is genuinely undefined on a frozen object -> FrozenError). Cache it per-worker instead. Defined here (not on AbstractReflection) because the original inverse_name lives on AssociationReflection and would shadow an AbstractReflection-level override.



115
116
117
# File 'lib/ractor_rails_shim/patches/activerecord_reflection.rb', line 115

def inverse_name
  rrs_refl_cache[[object_id, :inverse_name]] ||= (options.fetch(:inverse_of) { automatic_inverse_of })
end

#join_tableObject



119
120
121
# File 'lib/ractor_rails_shim/patches/activerecord_reflection.rb', line 119

def join_table
  rrs_refl_cache[[object_id, :join_table]] ||= -(options[:join_table]&.to_s || derive_join_table)
end