Module: Miscellany::ArbitraryPrefetch::ActiveRecordPatches::RelationPatch

Defined in:
lib/miscellany/active_record/arbitrary_prefetch.rb

Instance Method Summary collapse

Instance Method Details

#add_prefetches!(kwargs) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/miscellany/active_record/arbitrary_prefetch.rb', line 142

def add_prefetches!(kwargs)
  return unless kwargs.present?

  if ACTIVE_RECORD_VERSION < ::Gem::Version.new('7.2.0')
    assert_mutability!
  else
    assert_modifiable!
  end

  @values[:prefetches] ||= {}
  kwargs.each do |attr, opts|
    @values[:prefetches][attr] = normalize_prefetch_options(attr, opts)
  end
  self
end

#exec_queriesObject



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/miscellany/active_record/arbitrary_prefetch.rb', line 119

def exec_queries
  return super if loaded?

  records = super
  (@values[:prefetches] || {}).each do |_key, opts|
    pfc = PrefetcherContext.new(model, opts)
    pfc.link_models(records)

    unless defined?(Goldiloader) && Goldiloader.enabled?
      if PRE_RAILS_6_2
        ::ActiveRecord::Associations::Preloader.new.preload(records, [opts[:attribute]])
      else
        ::ActiveRecord::Associations::Preloader.new(records: records, associations: [opts[:attribute]]).call
      end
    end
  end
  records
end

#normalize_prefetch_options(attr, opts) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/miscellany/active_record/arbitrary_prefetch.rb', line 158

def normalize_prefetch_options(attr, opts)
  opts = resolve_prefetch_scope(opts) if opts.is_a?(Proc)

  norm = if opts.is_a?(Array)
      { relation: opts[0], queryset: prefetch_queryset(opts[0], opts[1]) }
    elsif opts.is_a?(ActiveRecord::Relation)
      rel_name = opts.model.name.underscore
      rel = (model.reflections[rel_name] || model.reflections[rel_name.pluralize])&.name
      { relation: rel, queryset: opts }
    else
      opts
  end

  norm[:attribute] = attr
  norm[:type] ||= (attr.to_s.pluralize == attr.to_s) ? :has_many : :has_one

  norm
end

#prefetch(**kwargs) ⇒ Object



138
139
140
# File 'lib/miscellany/active_record/arbitrary_prefetch.rb', line 138

def prefetch(**kwargs)
  spawn.add_prefetches!(kwargs)
end

#prefetch_queryset(relation, queryset) ⇒ Object

[:comments, ->{ where(favorite: true) }] - evaluate the block against the named association, the same way a Rails association scope is evaluated.

Raises:

  • (ArgumentError)


190
191
192
193
194
195
196
197
# File 'lib/miscellany/active_record/arbitrary_prefetch.rb', line 190

def prefetch_queryset(relation, queryset)
  return queryset unless queryset.is_a?(Proc)

  reflection = model.reflections[relation.to_s]
  raise ArgumentError, "#{model} has no association named #{relation}" unless reflection

  reflection.klass.all.instance_exec(&queryset)
end

#resolve_prefetch_scope(block) ⇒ Object

->{ comments.where(favorite: true) } - pull both the base association and the filtered queryset out of the block. Blocks that never name an association (->{ Comment.where(...) }) fall back to inferring it from the Relation's model.

Raises:

  • (ArgumentError)


180
181
182
183
184
185
186
# File 'lib/miscellany/active_record/arbitrary_prefetch.rb', line 180

def resolve_prefetch_scope(block)
  raise ArgumentError, "prefetch scopes do not accept arguments" if block.arity > 0

  fallback = block.binding.receiver rescue nil
  queryset, source_key = ScopeResolver.new(model, fallback).__evaluate__(block)
  source_key ? [source_key, queryset] : queryset
end