identity_cache 0.4.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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