Skip to content

Commit a1e9adc

Browse files
byroothcmaATshopify
authored andcommitted
WIP
1 parent 334d93a commit a1e9adc

File tree

7 files changed

+55
-17
lines changed

7 files changed

+55
-17
lines changed

activerecord/lib/active_record/associations/association.rb

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ module Associations
3434
# the <tt>reflection</tt> object represents a <tt>:has_many</tt> macro.
3535
class Association # :nodoc:
3636
attr_accessor :owner
37-
attr_reader :target, :reflection, :disable_joins
37+
attr_reader :reflection, :disable_joins
3838

3939
delegate :options, to: :reflection
4040

@@ -50,6 +50,13 @@ def initialize(owner, reflection)
5050
@skip_strict_loading = nil
5151
end
5252

53+
def target
54+
if @target&.is_a?(Promise)
55+
@target = @target.value
56+
end
57+
@target
58+
end
59+
5360
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
5461
def reset
5562
@loaded = false
@@ -172,14 +179,21 @@ def extensions
172179
# ActiveRecord::RecordNotFound is rescued within the method, and it is
173180
# not reraised. The proxy is \reset and +nil+ is the return value.
174181
def load_target
175-
@target = find_target if (@stale_state && stale_target?) || find_target?
182+
@target = find_target(async: false) if (@stale_state && stale_target?) || find_target?
176183

177184
loaded! unless loaded?
178185
target
179186
rescue ActiveRecord::RecordNotFound
180187
reset
181188
end
182189

190+
def async_load_target
191+
@target = find_target(async: true) if (@stale_state && stale_target?) || find_target?
192+
193+
loaded! unless loaded?
194+
target
195+
end
196+
183197
# We can't dump @reflection and @through_reflection since it contains the scope proc
184198
def marshal_dump
185199
ivars = (instance_variables - [:@reflection, :@through_reflection]).map { |name| [name, instance_variable_get(name)] }
@@ -223,7 +237,7 @@ def ensure_klass_exists!
223237
klass
224238
end
225239

226-
def find_target
240+
def find_target(async: false)
227241
if violates_strict_loading?
228242
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
229243
end
@@ -238,7 +252,7 @@ def find_target
238252

239253
binds = AssociationScope.get_bind_values(owner, reflection.chain)
240254
klass.with_connection do |c|
241-
sc.execute(binds, c) do |record|
255+
sc.execute(binds, c, async: async) do |record|
242256
set_inverse_instance(record)
243257
if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
244258
record.strict_loading!

activerecord/lib/active_record/associations/has_many_through_association.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,8 @@ def delete_through_records(records)
216216
end
217217
end
218218

219-
def find_target
219+
def find_target(async: false)
220+
raise NotImplementedError if async
220221
return [] unless target_reflection_has_associated_record?
221222
return scope.to_a if disable_joins
222223
super

activerecord/lib/active_record/associations/singular_association.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def reader
1818
def reset
1919
super
2020
@target = nil
21+
@future_target = nil
2122
end
2223

2324
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
@@ -43,11 +44,12 @@ def scope_for_create
4344
super.except!(*Array(klass.primary_key))
4445
end
4546

46-
def find_target
47+
def find_target(async: false)
4748
if disable_joins
49+
raise NotImplementedError if async
4850
scope.first
4951
else
50-
super.first
52+
super.then(&:first)
5153
end
5254
end
5355

activerecord/lib/active_record/core.rb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -432,11 +432,10 @@ def cached_find_by(keys, values)
432432
where(wheres).limit(1)
433433
}
434434

435-
begin
436-
statement.execute(values.flatten, connection, allow_retry: true).first
437-
rescue TypeError
438-
raise ActiveRecord::StatementInvalid
439-
end
435+
begin
436+
statement.execute(values.flatten, lease_connection, allow_retry: true).then(&:first)
437+
rescue TypeError
438+
raise ActiveRecord::StatementInvalid
440439
end
441440
end
442441
end

activerecord/lib/active_record/querying.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
5656
end
5757

5858
# Same as <tt>#find_by_sql</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
59-
def async_find_by_sql(sql, binds = [], preparable: nil, &block)
59+
def async_find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
6060
result = with_connection do |c|
61-
_query_by_sql(c, sql, binds, preparable: preparable, async: true)
61+
_query_by_sql(c, sql, binds, preparable: preparable, allow_retry: allow_retry, async: true)
6262
end
6363

6464
result.then do |result|

activerecord/lib/active_record/statement_cache.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,18 @@ def initialize(query_builder, bind_map, model)
142142
@model = model
143143
end
144144

145-
def execute(params, connection, allow_retry: false, &block)
145+
def execute(params, connection, allow_retry: false, async: false, &block)
146146
bind_values = @bind_map.bind params
147147

148148
sql = @query_builder.sql_for bind_values, connection
149149

150-
@model.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
150+
if async
151+
@model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
152+
else
153+
@model.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
154+
end
151155
rescue ::RangeError
152-
[]
156+
async ? Promise.wrap([]) : []
153157
end
154158

155159
def self.unsupported_value?(value)

activerecord/test/cases/associations/belongs_to_associations_test.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,3 +1839,21 @@ def test_destroy_linked_models
18391839
assert_not Author.exists?(author.id)
18401840
end
18411841
end
1842+
1843+
class AsyncBelongsToAssociationsTest < ActiveRecord::TestCase
1844+
fixtures :companies
1845+
1846+
self.use_transactional_tests = false
1847+
1848+
def test_temp_async_load_belongs_to
1849+
# TODO: proper test?
1850+
client = Client.find(3)
1851+
first_firm = companies(:first_firm)
1852+
assert_queries_match(/LIMIT|ROWNUM <=|FETCH FIRST/) do
1853+
client.association(:firm).async_load_target
1854+
1855+
assert_equal first_firm, client.firm
1856+
assert_equal first_firm.name, client.firm.name
1857+
end
1858+
end
1859+
end

0 commit comments

Comments
 (0)