identity_cache 0.4.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +5 -5
  2. data/.github/probots.yml +2 -0
  3. data/.github/workflows/ci.yml +92 -0
  4. data/.gitignore +2 -0
  5. data/.rubocop.yml +5 -0
  6. data/CAVEATS.md +25 -0
  7. data/CHANGELOG.md +73 -19
  8. data/Gemfile +5 -1
  9. data/LICENSE +1 -1
  10. data/README.md +49 -27
  11. data/Rakefile +14 -5
  12. data/dev.yml +12 -16
  13. data/gemfiles/Gemfile.latest-release +8 -0
  14. data/gemfiles/Gemfile.min-supported +7 -0
  15. data/gemfiles/Gemfile.rails-edge +7 -0
  16. data/identity_cache.gemspec +29 -10
  17. data/lib/identity_cache.rb +78 -51
  18. data/lib/identity_cache/belongs_to_caching.rb +12 -40
  19. data/lib/identity_cache/cache_fetcher.rb +6 -5
  20. data/lib/identity_cache/cache_hash.rb +2 -2
  21. data/lib/identity_cache/cache_invalidation.rb +4 -11
  22. data/lib/identity_cache/cache_key_generation.rb +17 -65
  23. data/lib/identity_cache/cache_key_loader.rb +128 -0
  24. data/lib/identity_cache/cached.rb +7 -0
  25. data/lib/identity_cache/cached/association.rb +87 -0
  26. data/lib/identity_cache/cached/attribute.rb +123 -0
  27. data/lib/identity_cache/cached/attribute_by_multi.rb +37 -0
  28. data/lib/identity_cache/cached/attribute_by_one.rb +88 -0
  29. data/lib/identity_cache/cached/belongs_to.rb +100 -0
  30. data/lib/identity_cache/cached/embedded_fetching.rb +41 -0
  31. data/lib/identity_cache/cached/prefetcher.rb +61 -0
  32. data/lib/identity_cache/cached/primary_index.rb +96 -0
  33. data/lib/identity_cache/cached/recursive/association.rb +109 -0
  34. data/lib/identity_cache/cached/recursive/has_many.rb +9 -0
  35. data/lib/identity_cache/cached/recursive/has_one.rb +9 -0
  36. data/lib/identity_cache/cached/reference/association.rb +16 -0
  37. data/lib/identity_cache/cached/reference/has_many.rb +105 -0
  38. data/lib/identity_cache/cached/reference/has_one.rb +100 -0
  39. data/lib/identity_cache/configuration_dsl.rb +53 -215
  40. data/lib/identity_cache/encoder.rb +95 -0
  41. data/lib/identity_cache/expiry_hook.rb +36 -0
  42. data/lib/identity_cache/fallback_fetcher.rb +2 -1
  43. data/lib/identity_cache/load_strategy/eager.rb +28 -0
  44. data/lib/identity_cache/load_strategy/lazy.rb +71 -0
  45. data/lib/identity_cache/load_strategy/load_request.rb +20 -0
  46. data/lib/identity_cache/load_strategy/multi_load_request.rb +27 -0
  47. data/lib/identity_cache/mem_cache_store_cas.rb +53 -0
  48. data/lib/identity_cache/memoized_cache_proxy.rb +137 -58
  49. data/lib/identity_cache/parent_model_expiration.rb +46 -11
  50. data/lib/identity_cache/query_api.rb +102 -408
  51. data/lib/identity_cache/railtie.rb +8 -0
  52. data/lib/identity_cache/record_not_found.rb +6 -0
  53. data/lib/identity_cache/should_use_cache.rb +1 -0
  54. data/lib/identity_cache/version.rb +3 -2
  55. data/lib/identity_cache/with_primary_index.rb +136 -0
  56. data/lib/identity_cache/without_primary_index.rb +24 -3
  57. data/performance/cache_runner.rb +25 -73
  58. data/performance/cpu.rb +4 -3
  59. data/performance/externals.rb +4 -3
  60. data/performance/profile.rb +6 -5
  61. data/railgun.yml +16 -0
  62. metadata +60 -73
  63. data/.travis.yml +0 -30
  64. data/Gemfile.rails42 +0 -6
  65. data/Gemfile.rails50 +0 -6
  66. data/test/attribute_cache_test.rb +0 -110
  67. data/test/cache_fetch_includes_test.rb +0 -46
  68. data/test/cache_hash_test.rb +0 -14
  69. data/test/cache_invalidation_test.rb +0 -139
  70. data/test/deeply_nested_associated_record_test.rb +0 -19
  71. data/test/denormalized_has_many_test.rb +0 -211
  72. data/test/denormalized_has_one_test.rb +0 -160
  73. data/test/fetch_multi_test.rb +0 -308
  74. data/test/fetch_test.rb +0 -258
  75. data/test/fixtures/serialized_record.mysql2 +0 -0
  76. data/test/fixtures/serialized_record.postgresql +0 -0
  77. data/test/helpers/active_record_objects.rb +0 -106
  78. data/test/helpers/database_connection.rb +0 -72
  79. data/test/helpers/serialization_format.rb +0 -42
  80. data/test/helpers/update_serialization_format.rb +0 -24
  81. data/test/identity_cache_test.rb +0 -29
  82. data/test/index_cache_test.rb +0 -161
  83. data/test/memoized_attributes_test.rb +0 -49
  84. data/test/memoized_cache_proxy_test.rb +0 -107
  85. data/test/normalized_belongs_to_test.rb +0 -107
  86. data/test/normalized_has_many_test.rb +0 -231
  87. data/test/normalized_has_one_test.rb +0 -9
  88. data/test/prefetch_associations_test.rb +0 -364
  89. data/test/readonly_test.rb +0 -109
  90. data/test/recursive_denormalized_has_many_test.rb +0 -131
  91. data/test/save_test.rb +0 -82
  92. data/test/schema_change_test.rb +0 -112
  93. data/test/serialization_format_change_test.rb +0 -16
  94. data/test/test_helper.rb +0 -140
data/Rakefile CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env rake
2
+ # frozen_string_literal: true
2
3
  require 'bundler/gem_tasks'
3
4
 
4
5
  require 'rake/testtask'
5
6
  require 'rdoc/task'
6
7
 
7
- desc 'Default: run unit tests.'
8
- task :default => :test
8
+ desc('Default: run tests and style checks.')
9
+ task(default: [:test, :rubocop])
9
10
 
10
- desc 'Test the identity_cache plugin.'
11
+ desc('Test the identity_cache plugin.')
11
12
  Rake::TestTask.new(:test) do |t|
12
13
  t.libs << 'lib'
13
14
  t.libs << 'test'
@@ -15,9 +16,17 @@ Rake::TestTask.new(:test) do |t|
15
16
  t.verbose = true
16
17
  end
17
18
 
18
- desc 'Update serialization format test fixture.'
19
+ task :rubocop do
20
+ require 'rubocop/rake_task'
21
+ RuboCop::RakeTask.new
22
+ end
23
+
24
+ desc('Update serialization format test fixture.')
19
25
  task :update_serialization_format do
20
- ruby './test/helpers/update_serialization_format.rb'
26
+ %w(mysql2 postgresql).each do |db|
27
+ ENV["DB"] = db
28
+ ruby './test/helpers/update_serialization_format.rb'
29
+ end
21
30
  end
22
31
 
23
32
  namespace :benchmark do
data/dev.yml CHANGED
@@ -3,7 +3,10 @@ name: identity-cache
3
3
  up:
4
4
  - homebrew:
5
5
  - postgresql
6
- - ruby: 2.3.3
6
+ - mysql-client@5.7:
7
+ or: [mysql@5.7]
8
+ conflicts: [mysql-connector-c, mysql, mysql-client]
9
+ - ruby: 2.6.5
7
10
  - railgun
8
11
  - bundler
9
12
 
@@ -27,6 +30,14 @@ commands:
27
30
  bundle exec ruby -I test "$@"
28
31
  fi
29
32
 
33
+ style:
34
+ desc: 'Run rubocop checks'
35
+ run: bundle exec rubocop "$@"
36
+
37
+ check:
38
+ desc: 'Run tests and style checks'
39
+ run: bundle exec rake test && bundle exec rubocop
40
+
30
41
  benchmark-cpu:
31
42
  desc: 'Run the identity cache CPU benchmark'
32
43
  run: bundle exec rake benchmark:cpu
@@ -38,18 +49,3 @@ commands:
38
49
  update-serialization-format:
39
50
  desc: 'Update serialization format test fixture'
40
51
  run: bundle exec rake update_serialization_format
41
-
42
- railgun:
43
- image: dev:railgun-common-services-0.2.x
44
- ip_address: 192.168.64.98
45
- memory: 1G
46
- cores: 1
47
- disk: 1G
48
- services:
49
- mysql: 3306
50
- postgresql: 5432
51
- memcached: 11211
52
-
53
- packages:
54
- - git@github.com:Shopify/dev-shopify.git
55
-
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+ gemspec path: '..'
3
+
4
+ gem 'activerecord'
5
+ gem 'activesupport'
6
+ gem "mysql2", ">= 0.4.4"
7
+ gem "pg", ">= 0.18", "< 2.0"
8
+ gem 'rubocop', '~> 1.5'
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+ gemspec path: '..'
3
+
4
+ gem 'ar_transaction_changes', '~> 1.1.0'
5
+ gem 'activerecord', '~> 5.2.0'
6
+ gem 'mysql2', '~> 0.4.4'
7
+ gem 'pg', '~> 0.18.0'
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+ gemspec path: '..'
3
+
4
+ gem 'activerecord', github: 'rails/rails'
5
+ gem 'activesupport', github: 'rails/rails'
6
+ gem "mysql2", ">= 0.4.4"
7
+ gem "pg", "~> 1.1"
@@ -1,27 +1,46 @@
1
1
  # -*- encoding: utf-8 -*-
2
+ # frozen_string_literal: true
2
3
  require File.expand_path('../lib/identity_cache/version', __FILE__)
3
4
 
4
5
  Gem::Specification.new do |gem|
5
- gem.authors = ["Camilo Lopez", "Tom Burns", "Harry Brundage", "Dylan Thacker-Smith", "Tobias Lutke", "Arthur Neves", "Francis Bogsanyi"]
6
+ gem.authors = [
7
+ "Camilo Lopez",
8
+ "Tom Burns",
9
+ "Harry Brundage",
10
+ "Dylan Thacker-Smith",
11
+ "Tobias Lutke",
12
+ "Arthur Neves",
13
+ "Francis Bogsanyi",
14
+ ]
6
15
  gem.email = ["gems@shopify.com"]
7
- gem.description = %q{Opt in read through ActiveRecord caching.}
8
- gem.summary = %q{IdentityCache lets you specify how you want to cache your model objects, at the model level, and adds a number of convenience methods for accessing those objects through the cache. Memcached is used as the backend cache store, and the database is only hit when a copy of the object cannot be found in Memcached.}
16
+ gem.description = "Opt-in read through Active Record caching."
17
+ gem.summary = "IdentityCache lets you specify how you want to cache your " \
18
+ "model objects, at the model level, and adds a number of " \
19
+ "convenience methods for accessing those objects through " \
20
+ "the cache. Memcached is used as the backend cache store, " \
21
+ "and the database is only hit when a copy of the object " \
22
+ "cannot be found in Memcached."
9
23
  gem.homepage = "https://github.com/Shopify/identity_cache"
10
24
 
11
- gem.files = `git ls-files`.split($\)
12
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
25
+ gem.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^test/}) }
27
+ end
28
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
29
+ gem.test_files = gem.files.grep(%r{^test/})
14
30
  gem.name = "identity_cache"
15
31
  gem.require_paths = ["lib"]
16
32
  gem.version = IdentityCache::VERSION
17
33
 
18
- gem.required_ruby_version = '>= 2.2.0'
34
+ gem.required_ruby_version = '>= 2.5.0'
35
+
36
+ gem.metadata['allowed_push_host'] = 'https://rubygems.org'
19
37
 
20
- gem.add_dependency('ar_transaction_changes', '~> 1.0')
21
- gem.add_dependency('activerecord', '>= 4.2.0')
38
+ gem.add_dependency('ar_transaction_changes', '~> 1.1')
39
+ gem.add_dependency('activerecord', '>= 5.2')
22
40
 
23
41
  gem.add_development_dependency('memcached', '~> 1.8.0')
24
42
  gem.add_development_dependency('memcached_store', '~> 1.0.0')
43
+ gem.add_development_dependency('dalli')
25
44
  gem.add_development_dependency('rake')
26
45
  gem.add_development_dependency('mocha', '0.14.0')
27
46
  gem.add_development_dependency('spy')
@@ -33,6 +52,6 @@ Gem::Specification.new do |gem|
33
52
  gem.add_development_dependency('cityhash', '0.6.0')
34
53
  gem.add_development_dependency('mysql2')
35
54
  gem.add_development_dependency('pg')
36
- gem.add_development_dependency('stackprof') if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new("2.1.0")
55
+ gem.add_development_dependency('stackprof')
37
56
  end
38
57
  end
@@ -1,8 +1,32 @@
1
+ # frozen_string_literal: true
1
2
  require 'active_record'
2
3
  require 'active_support/core_ext/module/attribute_accessors'
3
4
  require 'ar_transaction_changes'
4
5
 
5
6
  require "identity_cache/version"
7
+ require "identity_cache/record_not_found"
8
+ require "identity_cache/encoder"
9
+ require "identity_cache/cache_key_loader"
10
+ require "identity_cache/load_strategy/load_request"
11
+ require "identity_cache/load_strategy/multi_load_request"
12
+ require "identity_cache/load_strategy/eager"
13
+ require "identity_cache/load_strategy/lazy"
14
+ require "identity_cache/cached"
15
+ require "identity_cache/cached/prefetcher"
16
+ require "identity_cache/cached/embedded_fetching"
17
+ require "identity_cache/cached/association"
18
+ require "identity_cache/cached/attribute"
19
+ require "identity_cache/cached/attribute_by_one"
20
+ require "identity_cache/cached/attribute_by_multi"
21
+ require "identity_cache/cached/belongs_to"
22
+ require "identity_cache/cached/primary_index"
23
+ require "identity_cache/cached/recursive/association"
24
+ require "identity_cache/cached/recursive/has_one"
25
+ require "identity_cache/cached/recursive/has_many"
26
+ require "identity_cache/cached/reference/association"
27
+ require "identity_cache/cached/reference/has_one"
28
+ require "identity_cache/cached/reference/has_many"
29
+ require "identity_cache/expiry_hook"
6
30
  require 'identity_cache/memoized_cache_proxy'
7
31
  require 'identity_cache/belongs_to_caching'
8
32
  require 'identity_cache/cache_key_generation'
@@ -15,18 +39,14 @@ require "identity_cache/cache_invalidation"
15
39
  require "identity_cache/cache_fetcher"
16
40
  require "identity_cache/fallback_fetcher"
17
41
  require 'identity_cache/without_primary_index'
42
+ require 'identity_cache/with_primary_index'
18
43
 
19
44
  module IdentityCache
20
45
  extend ActiveSupport::Concern
21
46
 
22
- include ArTransactionChanges
23
- include IdentityCache::BelongsToCaching
24
- include IdentityCache::CacheKeyGeneration
25
- include IdentityCache::ConfigurationDSL
26
- include IdentityCache::QueryAPI
27
- include IdentityCache::CacheInvalidation
28
- include IdentityCache::ShouldUseCache
29
- include IdentityCache::ParentModelExpiration
47
+ autoload :MemCacheStoreCAS, 'identity_cache/mem_cache_store_cas'
48
+
49
+ include WithPrimaryIndex
30
50
 
31
51
  CACHED_NIL = :idc_cached_nil
32
52
  BATCH_SIZE = 1000
@@ -34,43 +54,34 @@ module IdentityCache
34
54
  DELETED_TTL = 1000
35
55
 
36
56
  class AlreadyIncludedError < StandardError; end
57
+
37
58
  class AssociationError < StandardError; end
38
- class InverseAssociationError < StandardError
39
- def initialize
40
- super "Inverse name for association could not be determined. Please use the :inverse_name option to specify the inverse association name for this cache."
41
- end
42
- end
59
+
60
+ class InverseAssociationError < StandardError; end
61
+
43
62
  class UnsupportedScopeError < StandardError; end
63
+
44
64
  class UnsupportedAssociationError < StandardError; end
65
+
45
66
  class DerivedModelError < StandardError; end
46
67
 
68
+ mattr_accessor :cache_namespace
69
+ self.cache_namespace = "IDC:#{CACHE_VERSION}:"
70
+
71
+ # Fetched records are not read-only and this could sometimes prevent IDC from
72
+ # reflecting what's truly in the database when fetch_read_only_records is false.
73
+ # When set to true, it will only return read-only records when cache is used.
74
+ mattr_accessor :fetch_read_only_records
75
+ self.fetch_read_only_records = true
76
+
47
77
  class << self
48
78
  include IdentityCache::CacheHash
49
79
 
50
80
  attr_accessor :readonly
51
81
  attr_writer :logger
52
82
 
53
- mattr_accessor :cache_namespace
54
- self.cache_namespace = "IDC:#{CACHE_VERSION}:".freeze
55
-
56
- version = Gem::Version.new(IdentityCache::VERSION)
57
-
58
- # Inverse active record associations are set when loading embedded
59
- # cache_has_many associations from the cache when never_set_inverse_association
60
- # is false. When set to true, it will only set the inverse cached association.
61
- mattr_accessor :never_set_inverse_association
62
- self.never_set_inverse_association = version >= Gem::Version.new("0.5")
63
-
64
- # Fetched records are not read-only and this could sometimes prevent IDC from
65
- # reflecting what's truly in the database when fetch_read_only_records is false.
66
- # When set to true, it will only return read-only records when cache is used.
67
- mattr_accessor :fetch_read_only_records
68
- self.fetch_read_only_records = version >= Gem::Version.new("0.5")
69
-
70
- def included(base) #:nodoc:
71
- raise AlreadyIncludedError if base.respond_to?(:cached_model)
72
- base.class_attribute :cached_model
73
- base.cached_model = base
83
+ def append_features(base) #:nodoc:
84
+ raise AlreadyIncludedError if base.include?(IdentityCache)
74
85
  super
75
86
  end
76
87
 
@@ -101,8 +112,27 @@ module IdentityCache
101
112
  end
102
113
 
103
114
  def should_use_cache? # :nodoc:
104
- pool = ActiveRecord::Base.connection_pool
105
- !pool.active_connection? || pool.connection.open_transactions == 0
115
+ ActiveRecord::Base.connection_handler.connection_pool_list.none? do |pool|
116
+ pool.active_connection? &&
117
+ # Rails wraps each of your tests in a transaction, so that any changes
118
+ # made to the database during the test can be rolled back afterwards.
119
+ # These transactions are flagged as "unjoinable", which tries to make
120
+ # your application behave as if they weren't there. In particular:
121
+ #
122
+ # - Opening another transaction during the test creates a savepoint,
123
+ # which can be rolled back independently of the main transaction.
124
+ # - When those nested transactions complete, any `after_commit`
125
+ # callbacks for records modified during the transaction will run,
126
+ # even though the changes haven't actually been committed yet.
127
+ #
128
+ # By ignoring unjoinable transactions, IdentityCache's behaviour
129
+ # during your test suite will more closely match production.
130
+ #
131
+ # When there are no open transactions, `current_transaction` returns a
132
+ # special `NullTransaction` object that is unjoinable, meaning we will
133
+ # use the cache.
134
+ pool.connection.current_transaction.joinable?
135
+ end
106
136
  end
107
137
 
108
138
  # Cache retrieval and miss resolver primitive; given a key it will try to
@@ -114,7 +144,7 @@ module IdentityCache
114
144
  #
115
145
  def fetch(key)
116
146
  if should_use_cache?
117
- unmap_cached_nil_for(cache.fetch(key) { map_cached_nil_for yield })
147
+ unmap_cached_nil_for(cache.fetch(key) { map_cached_nil_for(yield) })
118
148
  else
119
149
  yield
120
150
  end
@@ -135,12 +165,12 @@ module IdentityCache
135
165
  # +keys+ A collection or array of key strings
136
166
  def fetch_multi(*keys)
137
167
  keys.flatten!(1)
138
- return {} if keys.size == 0
168
+ return {} if keys.empty?
139
169
 
140
170
  result = if should_use_cache?
141
171
  fetch_in_batches(keys.uniq) do |missed_keys|
142
172
  results = yield missed_keys
143
- results.map {|e| map_cached_nil_for e }
173
+ results.map { |e| map_cached_nil_for(e) }
144
174
  end
145
175
  else
146
176
  results = yield keys
@@ -154,29 +184,26 @@ module IdentityCache
154
184
  result
155
185
  end
156
186
 
157
- def with_never_set_inverse_association(value = true)
158
- old_value = self.never_set_inverse_association
159
- self.never_set_inverse_association = value
160
- yield
161
- ensure
162
- self.never_set_inverse_association = old_value
163
- end
164
-
165
-
166
187
  def with_fetch_read_only_records(value = true)
167
- old_value = self.fetch_read_only_records
188
+ old_value = fetch_read_only_records
168
189
  self.fetch_read_only_records = value
169
190
  yield
170
191
  ensure
171
192
  self.fetch_read_only_records = old_value
172
193
  end
173
194
 
195
+ def eager_load!
196
+ ParentModelExpiration.install_all_pending_parent_expiry_hooks
197
+ end
198
+
174
199
  private
175
200
 
176
201
  def fetch_in_batches(keys)
177
- keys.each_slice(BATCH_SIZE).each_with_object Hash.new do |slice, result|
178
- result.merge! cache.fetch_multi(*slice) {|missed_keys| yield missed_keys }
202
+ keys.each_slice(BATCH_SIZE).each_with_object({}) do |slice, result|
203
+ result.merge!(cache.fetch_multi(*slice) { |missed_keys| yield missed_keys })
179
204
  end
180
205
  end
181
206
  end
182
207
  end
208
+
209
+ require 'identity_cache/railtie' if defined?(Rails)
@@ -1,59 +1,31 @@
1
+ # frozen_string_literal: true
1
2
  module IdentityCache
2
3
  module BelongsToCaching
3
4
  extend ActiveSupport::Concern
4
5
 
5
6
  included do |base|
6
- base.class_attribute :cached_belongs_tos
7
+ base.class_attribute(:cached_belongs_tos)
7
8
  base.cached_belongs_tos = {}
8
9
  end
9
10
 
10
11
  module ClassMethods
11
- def cache_belongs_to(association, options = {})
12
+ def cache_belongs_to(association)
12
13
  ensure_base_model
13
- raise NotImplementedError if options[:embed]
14
14
 
15
- unless association_reflection = reflect_on_association(association)
15
+ unless (reflection = reflect_on_association(association))
16
16
  raise AssociationError, "Association named '#{association}' was not found on #{self.class}"
17
17
  end
18
18
 
19
- options = {}
20
- self.cached_belongs_tos[association] = options
21
-
22
- options[:embed] = false
23
- options[:cached_accessor_name] = "fetch_#{association}"
24
- options[:records_variable_name] = "cached_#{association}"
25
- options[:association_reflection] = association_reflection
26
- options[:prepopulate_method_name] = "prepopulate_fetched_#{association}"
27
-
28
- build_normalized_belongs_to_cache(association, options)
29
- end
30
-
31
- private
19
+ if reflection.scope
20
+ raise(
21
+ UnsupportedAssociationError,
22
+ "caching association #{self}.#{association} is scoped which isn't supported"
23
+ )
24
+ end
32
25
 
33
- def build_normalized_belongs_to_cache(association, options)
34
- foreign_key = options[:association_reflection].foreign_key
35
- self.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
36
- def #{options[:cached_accessor_name]}
37
- association_klass = association(:#{association}).klass
38
- if association_klass.should_use_cache? && #{foreign_key}.present? && !association(:#{association}).loaded?
39
- if instance_variable_defined?(:@#{options[:records_variable_name]})
40
- @#{options[:records_variable_name]}
41
- else
42
- @#{options[:records_variable_name]} = association_klass.fetch_by_id(#{foreign_key})
43
- end
44
- else
45
- if IdentityCache.fetch_read_only_records && association_klass.should_use_cache?
46
- readonly_copy(association(:#{association}).load_target)
47
- else
48
- #{association}
49
- end
50
- end
51
- end
26
+ cached_belongs_to = Cached::BelongsTo.new(association, reflection: reflection)
52
27
 
53
- def #{options[:prepopulate_method_name]}(record)
54
- @#{options[:records_variable_name]} = record
55
- end
56
- CODE
28
+ cached_belongs_tos[association] = cached_belongs_to.tap(&:build)
57
29
  end
58
30
  end
59
31
  end