Skip to content

Commit d8a5df1

Browse files
committed
WIP
1 parent 7d17add commit d8a5df1

File tree

7 files changed

+52
-13
lines changed

7 files changed

+52
-13
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)] }
@@ -217,7 +231,7 @@ def ensure_klass_exists!
217231
klass
218232
end
219233

220-
def find_target
234+
def find_target(async: false)
221235
if violates_strict_loading?
222236
Base.strict_loading_violation!(owner: owner.class, reflection: reflection)
223237
end
@@ -232,7 +246,7 @@ def find_target
232246

233247
binds = AssociationScope.get_bind_values(owner, reflection.chain)
234248
klass.with_connection do |c|
235-
sc.execute(binds, c) do |record|
249+
sc.execute(binds, c, async: async) do |record|
236250
set_inverse_instance(record)
237251
if owner.strict_loading_n_plus_one_only? && reflection.macro == :has_many
238252
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: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@ def cached_find_by(keys, values)
431431
}
432432

433433
begin
434-
statement.execute(values.flatten, lease_connection, allow_retry: true).first
434+
statement.execute(values.flatten, lease_connection, allow_retry: true).then(&:first)
435435
rescue TypeError
436436
raise ActiveRecord::StatementInvalid
437437
end

activerecord/lib/active_record/querying.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ def find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
5252
end
5353

5454
# Same as <tt>#find_by_sql</tt> but perform the query asynchronously and returns an ActiveRecord::Promise.
55-
def async_find_by_sql(sql, binds = [], preparable: nil, &block)
56-
_query_by_sql(sql, binds, preparable: preparable, async: true).then do |result|
55+
def async_find_by_sql(sql, binds = [], preparable: nil, allow_retry: false, &block)
56+
_query_by_sql(sql, binds, preparable: preparable, allow_retry: allow_retry, async: true).then do |result|
5757
_load_from_sql(result, &block)
5858
end
5959
end

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, klass)
142142
@klass = klass
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-
klass.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
150+
if async
151+
klass.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
152+
else
153+
klass.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
@@ -1841,3 +1841,21 @@ def test_destroy_linked_models
18411841
assert_not Author.exists?(author.id)
18421842
end
18431843
end
1844+
1845+
class AsyncBelongsToAssociationsTest < ActiveRecord::TestCase
1846+
fixtures :companies
1847+
1848+
self.use_transactional_tests = false
1849+
1850+
def test_temp_async_load_belongs_to
1851+
# TODO: proper test?
1852+
client = Client.find(3)
1853+
first_firm = companies(:first_firm)
1854+
assert_queries_match(/LIMIT|ROWNUM <=|FETCH FIRST/) do
1855+
client.association(:firm).async_load_target
1856+
1857+
assert_equal first_firm, client.firm
1858+
assert_equal first_firm.name, client.firm.name
1859+
end
1860+
end
1861+
end

0 commit comments

Comments
 (0)