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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1fa182ed254be40433e84f2bdef8faf341581e5f
4
- data.tar.gz: b795a7e03b4cf94a9d5ff3fd77e04186cde84d5c
2
+ SHA256:
3
+ metadata.gz: 5196973719eb77354c40188d33d291b31ac94b23311bd78ba263e1e9c973e1f1
4
+ data.tar.gz: ce1be3c774f20c56b288272b9d30ce4abcb93d60536dedb1a232b566de92e3df
5
5
  SHA512:
6
- metadata.gz: 51c4645eb6a548c720444d6964412576bf9c84a399ad1a100251953d24f7e114654170eb4ae3111c906fedd1d9f0555c1c3c2734904675383f84f9ee62fe3362
7
- data.tar.gz: fabf846eb3ac2d9ccd9eda7f11dc3e42ed64ac5b678863fd1755e066bc04fc77cc46c4cf9eade1149da4a7fa244c28a45a62fdd4c798501a0b3764ded56d697f
6
+ metadata.gz: 4e1bfc8ec934bedf3ea3c451b87b903b8c373c033ab062535cd1e2abcb75cf0da508887440e1d5024a492ea9cb6f34e26aac5b18c53a9a21af96c072bbbaf7fe
7
+ data.tar.gz: e3105dbebe6f2da6f25e13f05a45edd53182bbe8058ac8dbdfe800a6981d765bf7cbefb7a27caa4c21966c23a6630df0994f7f308dac70f61a35f43c801a7430
@@ -0,0 +1,2 @@
1
+ enabled:
2
+ - cla
@@ -0,0 +1,92 @@
1
+ name: CI
2
+
3
+ on:
4
+ push: {}
5
+ pull_request:
6
+ types: [opened, synchronize]
7
+
8
+ jobs:
9
+ build:
10
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.owner.login != 'Shopify'
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ strategy:
15
+ matrix:
16
+ entry:
17
+ - name: 'Minimum supported'
18
+ ruby: 2.5
19
+ gemfile: "Gemfile.min-supported"
20
+ - name: 'Latest released & run rubocop'
21
+ ruby: 2.7
22
+ gemfile: "Gemfile.latest-release"
23
+ rubocop: true
24
+ - name: 'Rails edge'
25
+ ruby: 2.7
26
+ gemfile: "Gemfile.rails-edge"
27
+ edge: true
28
+
29
+ name: ${{ matrix.entry.name }}
30
+
31
+ continue-on-error: ${{ matrix.entry.edge || false }}
32
+
33
+ env:
34
+ BUNDLE_GEMFILE: gemfiles/${{ matrix.entry.gemfile }}
35
+
36
+ services:
37
+ memcached:
38
+ image: memcached
39
+ ports:
40
+ - 11211:11211
41
+ mysql:
42
+ image: mysql:5.7
43
+ env:
44
+ MYSQL_ALLOW_EMPTY_PASSWORD: yes
45
+ MYSQL_DATABASE: identity_cache_test
46
+ options: >-
47
+ --health-cmd="mysqladmin ping"
48
+ --health-interval=10s
49
+ --health-timeout=5s
50
+ --health-retries=5
51
+ ports:
52
+ - 3306:3306
53
+ postgres:
54
+ image: postgres
55
+ env:
56
+ POSTGRES_PASSWORD: postgres
57
+ POSTGRES_DB: identity_cache_test
58
+ options: >-
59
+ --health-cmd pg_isready
60
+ --health-interval 10s
61
+ --health-timeout 5s
62
+ --health-retries 5
63
+ ports:
64
+ - 5432:5432
65
+
66
+ steps:
67
+ - name: Install required packages
68
+ run: |
69
+ sudo apt-get update
70
+ sudo apt-get -y install libmemcached-dev libmysqlclient-dev libpq-dev libsasl2-dev
71
+ - uses: actions/checkout@v2
72
+ - name: Set up Ruby
73
+ uses: actions/setup-ruby@v1
74
+ with:
75
+ ruby-version: ${{ matrix.entry.ruby }}
76
+ - name: Install bundler and gems
77
+ run: |
78
+ gem install bundler
79
+ bundle install --jobs 4 --retry 3
80
+ - name: Test with mysql
81
+ env:
82
+ DB: mysql2
83
+ run: bundle exec rake test
84
+ - name: Test with postgres and memcached_store
85
+ env:
86
+ DB: postgresql
87
+ POSTGRES_PASSWORD: postgres
88
+ ADAPTER: memcached
89
+ run: bundle exec rake test
90
+ - name: Run rubocop
91
+ if: matrix.entry.rubocop
92
+ run: bundle exec rubocop
data/.gitignore CHANGED
@@ -15,3 +15,5 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .rubocop-http*
19
+ .byebug_history
@@ -0,0 +1,5 @@
1
+ inherit_from:
2
+ - https://shopify.github.io/ruby-style-guide/rubocop.yml
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 2.4
@@ -0,0 +1,25 @@
1
+ While IdentityCache allows to massively scale access to ActiveRecord objects, it's important to note what problems might arise with it over time and what are things that it's not solving in the current design.
2
+
3
+ ## Large blobs
4
+
5
+ For [god objects](https://en.wikipedia.org/wiki/God_object) (like the Shop model at Shopify), it’s typical to have many associations. It’s appealing for developers to embed many of those associations into the god model’s IDC blob, such as `cache_has_many :images, embed: true`. As a side effect, over time, the god model’s IDC blob may grow very large. It gets even worse when some of the embedded records get larger themselves, and the total IDC blob gets into as much as a megabyte in size. This puts pressure on both the application, memcached, and the network fibre because this large blob needs to be serialized and unserialized each time and transferred over the network.
6
+
7
+ If the cache blob gets larger than the maximum cache value in memcached, then the cache fill would always fail, causing it to have to load all that data from the database each time.
8
+
9
+ ### Overfetching
10
+
11
+ There's currently no way to retrieve/fill the cache with a subset of a model's associations. So, for models with many embedded associations, every `Model.fetch` would result in fetching all of the blob over the network and unserializing all embedded records, even if the client has only accessed one of those associations. That's a lot of useless CPU cycles and IO operations that happen on each access. It will also increase the network and memory bandwidth used on both the client and the server.
12
+
13
+ Another possible issue is that cache invalidations for a model can be amplified by the number of models that embed them. This can further aggravate the large cache blob problem by embedding an association that gets invalidated more frequently.
14
+
15
+ ## Hot keys
16
+
17
+ To scale out memcache, it’s typical to arrange memcached instances in rings. Most memcache clients/proxies use [consistent hashing](https://github.com/facebook/mcrouter/wiki/Pools#hash-functions) to route the operation to one of the instances in the ring. This means that an IDC entry will always end up in the same memcached instance.
18
+
19
+ If the key associated with a record is on a hot codepath, that memcached instance will possibly experience saturation on the network and the number of bytes that it can send out. In cloud environments, this is a limit that is easy to hit. This especially aggravates the server-side large blob problem.
20
+
21
+ ## Thundering herd
22
+
23
+ When a record gets updated, its IDC entry in memcache is expired, and it only gets populated the next time it is accessed. When many clients request the same key concurrently (e.g. on a hot codepath), they all find out that the IDC entry is missing at the same, and hence they will all go to the database to fill the IDC key at the same time. The massive number of clients hitting the DB will create a [thundering herd problem](https://en.wikipedia.org/wiki/Thundering_herd_problem) and overload the DB.
24
+
25
+ Check out https://github.com/Shopify/identity_cache/pull/373 for more context and for the proposed solution for this problem.
@@ -1,6 +1,60 @@
1
- # IdentityCache changelog
1
+ # Identity Cache Changelog
2
2
 
3
- #### 0.4.1
3
+ ## 1.1.0
4
+
5
+ ### Fixes
6
+ - Fix double debug logging of cache hits and misses (#474)
7
+ - Fix a Rails 6.1 deprecation warning for Rails 7.0 compatibility (#482)
8
+ - Recursively install parent expiry hooks when expiring parent caches (#476)
9
+ - Expire caches before other `after_commit` callbacks (#471)
10
+ - Avoid unnecessary record cache expiry on save with no DB update (#464)
11
+ - Fix an Active Record deprecation warning by not using `Connection#type_cast` (#459)
12
+ - Fix broken `prefetch_associations` of a polymorphic `cache_belongs_to` (#461)
13
+ - Fix `should_use_cache?` check to avoid calling it on the wrong class (#454)
14
+ - Fix fetch `has_many` embedded association on record after adding to it (#449)
15
+
16
+ ### Features
17
+ - Support multiple databases and transactional tests in `IdentityCache.should_use_cache?` (#293)
18
+ - Add support for the default `MemCacheStore` from `ActiveSupport` (#465)
19
+
20
+ ### Breaking Changes
21
+ - Drop ruby 2.4 support, since it is no longer supported upstream (#468)
22
+
23
+ ## 1.0.1
24
+
25
+ - Fix expiry of cache_has_one association with scope and `embed: :id` (#442)
26
+
27
+ ## 1.0.0
28
+
29
+ - Remove inverse_name option. Specify inverse_of on the Active Record association instead. (#439)
30
+ - Bump the minimum Active Record version to 5.2 (#438)
31
+ - Remove the default embed option value from cache_has_one (#437)
32
+ - Lazily evaluate nested includes to fetch blobs in batches (#427)
33
+ - Only cache embedded association IDs when present (#397)
34
+ - Add support for ID embedded `has_one` cached associations (#393)
35
+ - Add support for polymorphic `belongs_to` cached associations (#387)
36
+ - Add `fetch_multi_by_*` support for cache_index with a single field (#368)
37
+ - Remove support for rails 4.2 (#355)
38
+ - Type cast values using attribute types before using in cache key (#354)
39
+ - Set inverse cached association for cache_has_one on cache hit (#345)
40
+ - Use `ActiveSupport:Notifications` to notify subscribers of hydration events (#341)
41
+ - Remove disable_primary_cache_index (#335)
42
+ - Remove deprecated `embed: false` cache_has_many option (#335)
43
+ - Fix column name in the preload association query when using custom primary keys (#338)
44
+ - Raise when trying to cache a belong_to association with a scope. Previously the scope was ignored on a cache hit (#323)
45
+ - Remove deprecated `never_set_inverse_association` option (#319)
46
+ - Lazy load associated classes (#306)
47
+
48
+ ## 0.5.1
49
+
50
+ - Fix bug in prefetch_associations for cache_has_one associations that may be nil
51
+
52
+ ## 0.5.0
53
+
54
+ - `never_set_inverse_association` and `fetch_read_only_records` are now `true` by default (#315)
55
+ - Store the class name instead of the class itself (#311)
56
+
57
+ ## 0.4.1
4
58
 
5
59
  - Deprecated embedded associations on models that don't use IDC (#305)
6
60
  - Remove a respond_to? check that hides mistakes in includes hash (#307)
@@ -11,11 +65,11 @@
11
65
  - Clone instead of dup record when readonlyifying fetched records (#292)
12
66
  - Consistently store the array for cached has many associations (#288)
13
67
 
14
- #### 0.4.0
68
+ ## 0.4.0
15
69
 
16
70
  - Return an array from fetched association to prevent chaining. Up to now, a relation was returned by default. (#287)
17
71
 
18
- #### 0.3.2
72
+ ## 0.3.2
19
73
 
20
74
  - Deprecate returning non read-only records when cache is used. Set IdentityCache.fetch_readonly_records to true to avoid this. (#282)
21
75
  - Use loaded association first when fetching a cache_has_many id embedded association (#280)
@@ -23,11 +77,11 @@
23
77
  - Fetch association returns relation or array depending on the configuration. It was only returning a relation for cache_has_many fetch association methods. (#276)
24
78
  - Stop sharing the same attributes hash between the fetched record and the memoized cache, which could interfere with dirty tracking (#267)
25
79
 
26
- #### 0.3.1
80
+ ## 0.3.1
27
81
 
28
82
  - Fix cache_index for non-id primary key
29
83
 
30
- #### 0.3.0
84
+ ## 0.3.0
31
85
 
32
86
  - Add support for includes option on cache_index and fetch_by_id
33
87
  - Use ActiveRecord instantiate
@@ -40,37 +94,37 @@
40
94
  - Fix cache_belongs_to on polymorphic assocations.
41
95
  - Fetching a cache_belongs_to association no longer loads the belongs_to association
42
96
 
43
- #### 0.2.5
97
+ ## 0.2.5
44
98
 
45
99
  - Fixed support for namespaced model classes
46
100
  - Added some deduplication for parent cache expiry
47
101
  - Fixed some deprecation warnings in rails 4.2
48
102
 
49
- #### 0.2.4
103
+ ## 0.2.4
50
104
 
51
105
  - Refactoring, documentation and test changes
52
106
 
53
- #### 0.2.3
107
+ ## 0.2.3
54
108
 
55
109
  - PostgreSQL support
56
110
  - Rails 4.2 compatibility
57
111
  - Fix: Don't connect to database when calling `IdentityCache.should_use_cache?`
58
112
  - Fix: Fix invalid parent cache invalidation if object is embedded in different parents
59
113
 
60
- #### 0.2.2
114
+ ## 0.2.2
61
115
 
62
116
  - Change: memcached is no longer a runtime dependency
63
117
  - Use cache for read-only models.
64
118
 
65
- #### 0.2.1
119
+ ## 0.2.1
66
120
 
67
121
  - Add a fallback backend using local memory.
68
122
 
69
- #### 0.2.0
123
+ ## 0.2.0
70
124
 
71
125
  - Memcache CAS support
72
126
 
73
- #### 0.1.0
127
+ ## 0.1.0
74
128
 
75
129
  - Backwards incompatible change: Stop expiring cache on after_touch callback.
76
130
  - Change: fetch_multi accepts an array of keys as argument
@@ -81,29 +135,29 @@
81
135
  - Fix: Avoid unused preload on fetch_multi with :includes option for cache miss
82
136
  - Fix: reload will invalidate the local instance cache
83
137
 
84
- #### 0.0.7
138
+ ## 0.0.7
85
139
 
86
140
  - Add support for non-integer primary keys
87
141
  - Fix: Not implemented error for cache_has_one with embed: false
88
142
  - Fix: cache key to change when adding a cache_has_many association with :embed => false
89
143
  - Fix: Compatibility rails 4.1 for `quote_value`, which needs default column.
90
144
 
91
- #### 0.0.6
145
+ ## 0.0.6
92
146
 
93
147
  - Fix: bug where previously nil-cached attribute caches weren't expired on record creation
94
148
  - Fix: cache key to not change when adding a non-embedded association.
95
149
  - Perf: Rails 4 Only create `CollectionProxy` when using it
96
150
 
97
- #### 0.0.5
151
+ ## 0.0.5
98
152
 
99
153
 
100
- #### 0.0.4
154
+ ## 0.0.4
101
155
 
102
156
  - Fix: only marshal attributes, embedded associations and normalized association IDs
103
157
  - Add cache version number to cache keys
104
158
  - Add test case to ensure version number is updated when the marshalled format changes
105
159
 
106
- #### 0.0.3
160
+ ## 0.0.3
107
161
 
108
162
  - Fix: memoization for multi hits actually work
109
163
  - Fix: quotes `SELECT` projection elements on cache misses
@@ -111,7 +165,7 @@
111
165
  - Fix: table names are not hardcoded anymore
112
166
  - Logger now differentiates memoized vs non memoized hits
113
167
 
114
- #### 0.0.2
168
+ ## 0.0.2
115
169
 
116
170
  - Fix: Existent embedded entries will no longer raise when `ActiveModel::MissingAttributeError` when accessing a newly created attribute.
117
171
  - Fix: Do not marshal raw ActiveRecord associations
data/Gemfile CHANGED
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
1
2
  source 'https://rubygems.org'
2
3
  gemspec
3
4
 
4
- gem 'mysql2', '~> 0.3.13'
5
+ gem 'mysql2', '~> 0.5.3'
6
+ gem 'pg', ">= 0.18", "< 2.0"
7
+ gem 'rubocop', '~> 1.5'
8
+ gem 'byebug', platform: :mri
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 Shopify
1
+ Copyright (c) 2013-2021 Shopify
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -12,6 +12,10 @@ Add this line to your application's Gemfile:
12
12
  ```ruby
13
13
  gem 'identity_cache'
14
14
  gem 'cityhash' # optional, for faster hashing (C-Ruby only)
15
+
16
+ gem 'dalli' # To use :mem_cache_store
17
+ # alternatively
18
+ gem 'memcached_store' # to use the old libmemcached based client
15
19
  ```
16
20
 
17
21
  And then execute:
@@ -21,8 +25,30 @@ And then execute:
21
25
 
22
26
  Add the following to all your environment/*.rb files (production/development/test):
23
27
 
28
+ ### If you use Dalli (recommended)
29
+
30
+ ```ruby
31
+ config.identity_cache_store = :mem_cache_store, "mem1.server.com", "mem2.server.com", {
32
+ expires_in: 6.hours.to_i, # in case of network errors when sending a delete
33
+ failover: false, # avoids more cache consistency issues
34
+ }
35
+ ```
36
+
37
+ Add an initializer with this code:
38
+
39
+ ```ruby
40
+ IdentityCache.cache_backend = ActiveSupport::Cache.lookup_store(*Rails.configuration.identity_cache_store)
41
+ ```
42
+
43
+
44
+ ### If you use Memcached (old client)
45
+
24
46
  ```ruby
25
- config.identity_cache_store = :mem_cache_store, Memcached::Rails.new(:servers => ["mem1.server.com"])
47
+ config.identity_cache_store = :memcached_store,
48
+ Memcached.new(["mem1.server.com"],
49
+ support_cas: true,
50
+ auto_eject_hosts: false, # avoids more cache consistency issues
51
+ ), { expires_in: 6.hours.to_i } # in case of network errors when sending a delete
26
52
  ```
27
53
 
28
54
  Add an initializer with this code:
@@ -45,10 +71,10 @@ class Product < ActiveRecord::Base
45
71
 
46
72
  has_many :images
47
73
 
48
- cache_has_many :images, :embed => true
74
+ cache_has_many :images, embed: true
49
75
  end
50
76
 
51
- # Fetch the product by its id using the primary primary index as well as the embedded images association.
77
+ # Fetch the product by its id using the primary index as well as the embedded images association.
52
78
  @product = Product.fetch(id)
53
79
 
54
80
  # Access the loaded images for the Product.
@@ -64,7 +90,7 @@ IdentityCache lets you lookup records by fields other than `id`. You can have mu
64
90
  ``` ruby
65
91
  class Product < ActiveRecord::Base
66
92
  include IdentityCache
67
- cache_index :handle, :unique => true
93
+ cache_index :handle, unique: true
68
94
  cache_index :vendor, :product_type
69
95
  end
70
96
 
@@ -72,6 +98,9 @@ end
72
98
  # If the object isn't in the cache it is pulled from the db and stored in the cache.
73
99
  product = Product.fetch_by_handle(handle)
74
100
 
101
+ # Fetch multiple products by providing an array of index values.
102
+ products = Product.fetch_multi_by_handle(handles)
103
+
75
104
  products = Product.fetch_by_vendor_and_product_type(vendor, product_type)
76
105
  ```
77
106
 
@@ -98,7 +127,7 @@ class Product < ActiveRecord::Base
98
127
  has_one :featured_image
99
128
 
100
129
  cache_has_many :images
101
- cache_has_one :featured_image
130
+ cache_has_one :featured_image, embed: :id
102
131
  end
103
132
 
104
133
  @product.fetch_featured_image
@@ -112,7 +141,7 @@ class Product < ActiveRecord::Base
112
141
  include IdentityCache
113
142
  end
114
143
 
115
- @product.fetch_multi([1, 2])
144
+ Product.fetch_multi([1, 2])
116
145
  ```
117
146
 
118
147
  ### Embedding Associations
@@ -124,7 +153,7 @@ class Product < ActiveRecord::Base
124
153
  include IdentityCache
125
154
 
126
155
  has_many :images
127
- cache_has_many :images, :embed => true
156
+ cache_has_many :images, embed: true
128
157
  end
129
158
 
130
159
  @product = Product.fetch(id)
@@ -135,32 +164,29 @@ With this code, on cache miss, the product and its associated images will be loa
135
164
 
136
165
  ### Caching Polymorphic Associations
137
166
 
138
- IdentityCache tries to figure out both sides of an association whenever it can so it can set those up when rebuilding the object from the cache. In some cases this is hard to determine so you can tell IdentityCache what the association should be. This is most often the case when embedding polymorphic associations. The `inverse_name` option on `cache_has_many` and `cache_has_one` lets you specify the inverse name of the association.
167
+ IdentityCache tries to figure out both sides of an association whenever it can so it can set those up when rebuilding the object from the cache. In some cases this is hard to determine so you can tell IdentityCache what the association should be. This is most often the case when embedding polymorphic associations.
139
168
 
140
169
  ``` ruby
141
170
  class Metafield < ActiveRecord::Base
142
171
  include IdentityCache
143
- belongs_to :owner, :polymorphic => true
172
+ belongs_to :owner, polymorphic: true
144
173
  cache_belongs_to :owner
145
174
  end
146
175
 
147
176
  class Product < ActiveRecord::Base
148
177
  include IdentityCache
149
- has_many :metafields, :as => 'owner'
150
- cache_has_many :metafields, :inverse_name => :owner
178
+ has_many :metafields, as: :owner
179
+ cache_has_many :metafields
151
180
  end
152
181
  ```
153
182
 
154
- The `:inverse_name => :owner` option tells IdentityCache what the association on the other side is named so that it can correctly set the assocation when loading the metafields from the cache.
155
-
156
-
157
183
  ### Caching Attributes
158
184
 
159
185
  For cases where you may not need the entire object to be cached, just an attribute from record, `cache_attribute` can be used. This will cache the single attribute by the key specified.
160
186
 
161
187
  ``` ruby
162
188
  class Redirect < ActiveRecord::Base
163
- cache_attribute :target, :by => [:shop_id, :path]
189
+ cache_attribute :target, by: [:shop_id, :path]
164
190
  end
165
191
 
166
192
  Redirect.fetch_target_by_shop_id_and_path(shop_id, path)
@@ -182,22 +208,18 @@ Example:
182
208
  #### cache_has_many
183
209
 
184
210
  Options:
185
- _[:embed]_ When true, specifies that the association should be included with the parent when caching. This means the associated objects will be loaded already when the parent is loaded from the cache and will not need to be fetched on their own. When :ids, only the id of the associated records will be included with the parent when caching.
186
-
187
- _[:inverse_name]_ Specifies the name of parent object used by the association. This is useful for polymorphic associations when the association is often named something different between the parent and child objects.
211
+ _[:embed]_ When true, specifies that the association should be included with the parent when caching. This means the associated objects will be loaded already when the parent is loaded from the cache and will not need to be fetched on their own. When :ids, only the id of the associated records will be included with the parent when caching. Defaults to `:ids`.
188
212
 
189
213
  Example:
190
- `cache_has_many :metafields, :inverse_name => :owner, :embed => true`
214
+ `cache_has_many :metafields, embed: true`
191
215
 
192
216
  #### cache_has_one
193
217
 
194
218
  Options:
195
- _[:embed]_ When true, specifies that the association should be included with the parent when caching. This means the associated objects will be loaded already when the parent is loaded from the cache and will not need to be fetched on their own. No other values are currently implemented.
196
-
197
- _[:inverse_name]_ Specifies the name of parent object used by the association. This is useful for polymorphic associations when the association is often named something different between the parent and child objects.
219
+ _[:embed]_ When true, specifies that the association should be included with the parent when caching. This means the associated objects will be loaded already when the parent is loaded from the cache and will not need to be fetched on their own. No other values are currently implemented. When :id, only the id of the associated record will be included with the parent when caching.
198
220
 
199
221
  Example:
200
- `cache_has_one :configuration, :embed => true`
222
+ `cache_has_one :configuration, embed: :id`
201
223
 
202
224
  #### cache_belongs_to
203
225
 
@@ -210,7 +232,7 @@ Options:
210
232
  _[:by]_ Specifies what key(s) you want the attribute cached by. Defaults to :id.
211
233
 
212
234
  Example:
213
- `cache_attribute :target, :by => [:shop_id, :path]`
235
+ `cache_attribute :target, by: [:shop_id, :path]`
214
236
 
215
237
  ## Memoized Cache Proxy
216
238
 
@@ -220,8 +242,8 @@ Cache reads and writes can be memoized for a block of code to serve duplicate id
220
242
  class ApplicationController < ActionController::Base
221
243
  around_filter :identity_cache_memoization
222
244
 
223
- def identity_cache_memoization
224
- IdentityCache.cache.with_memoization{ yield }
245
+ def identity_cache_memoization(&block)
246
+ IdentityCache.cache.with_memoization(&block)
225
247
  end
226
248
  end
227
249
  ```
@@ -236,7 +258,7 @@ A word of warning. Some versions of rails will silently rescue all exceptions in
236
258
 
237
259
  Since everything is being marshalled and unmarshalled from Memcached changing Ruby or Rails versions could mean your objects cannot be unmarshalled from Memcached. There are a number of ways to get around this such as namespacing keys when you upgrade or rescuing marshal load errors and treating it as a cache miss. Just something to be aware of if you are using IdentityCache and upgrade Ruby or Rails.
238
260
 
239
- IdentityCache is also very much _opt-in_ by deliberate design. This means IdentityCache does not mess with the way normal Rails associations work, and including it in a model won't change any clients of that model until you switch them to use `fetch` instead of `find`. This is because there is no way IdentityCache is ever going to be 100% consistent. Processes die, execeptions happen, and network blips occur, which means there is a chance that some database transaction might commit but the corresponding memcached DEL operation does not make it. This means that you need to think carefully about when you use `fetch` and when you use `find`. For example, at Shopify, we never use any `fetch`ers on the path which moves money around, because IdentityCache could simply be wrong, and we want to charge people the right amount of money. We do however use the fetchers on performance critical paths where absolute correctness isn't the most important thing, and this is what IdentityCache is intended for.
261
+ IdentityCache is also very much _opt-in_ by deliberate design. This means IdentityCache does not mess with the way normal Rails associations work, and including it in a model won't change any clients of that model until you switch them to use `fetch` instead of `find`. This is because there is no way IdentityCache is ever going to be 100% consistent. Processes die, exceptions happen, and network blips occur, which means there is a chance that some database transaction might commit but the corresponding memcached DEL operation does not make it. This means that you need to think carefully about when you use `fetch` and when you use `find`. For example, at Shopify, we never use any `fetch`ers on the path which moves money around, because IdentityCache could simply be wrong, and we want to charge people the right amount of money. We do however use the fetchers on performance critical paths where absolute correctness isn't the most important thing, and this is what IdentityCache is intended for.
240
262
 
241
263
  ## Notes
242
264