ar_cache 1.0.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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +68 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +56 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +7 -0
- data/Gemfile.common +17 -0
- data/Gemfile.lock +192 -0
- data/LICENSE.txt +21 -0
- data/README.md +166 -0
- data/Rakefile +28 -0
- data/ar_cache.gemspec +34 -0
- data/bin/activerecord-test +20 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/gemfiles/rails-6-1 +3 -0
- data/gemfiles/rails-edge +3 -0
- data/lib/ar_cache.rb +22 -0
- data/lib/ar_cache/active_record.rb +36 -0
- data/lib/ar_cache/active_record/associations/has_one_through_association.rb +39 -0
- data/lib/ar_cache/active_record/associations/singular_association.rb +18 -0
- data/lib/ar_cache/active_record/connection_adapters/abstract/database_statements.rb +76 -0
- data/lib/ar_cache/active_record/connection_adapters/abstract/transaction.rb +90 -0
- data/lib/ar_cache/active_record/core.rb +21 -0
- data/lib/ar_cache/active_record/insert_all.rb +17 -0
- data/lib/ar_cache/active_record/model_schema.rb +27 -0
- data/lib/ar_cache/active_record/persistence.rb +23 -0
- data/lib/ar_cache/active_record/relation.rb +48 -0
- data/lib/ar_cache/configuration.rb +59 -0
- data/lib/ar_cache/log_subscriber.rb +8 -0
- data/lib/ar_cache/marshal.rb +81 -0
- data/lib/ar_cache/mock_table.rb +55 -0
- data/lib/ar_cache/query.rb +95 -0
- data/lib/ar_cache/record.rb +54 -0
- data/lib/ar_cache/store.rb +38 -0
- data/lib/ar_cache/table.rb +126 -0
- data/lib/ar_cache/version.rb +5 -0
- data/lib/ar_cache/where_clause.rb +151 -0
- data/lib/generators/ar_cache/install_generator.rb +22 -0
- data/lib/generators/ar_cache/templates/configuration.rb +34 -0
- data/lib/generators/ar_cache/templates/migrate/create_ar_cache_records.rb.tt +16 -0
- metadata +107 -0
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 OuYangJinTing
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# ArCache
|
|
2
|
+
|
|
3
|
+
`ArCache` is an modern cacheing library for `ActiveRecord` inspired by cache-money and second_level_cache.
|
|
4
|
+
It works automatically by copied `ActiveRecord` related code.
|
|
5
|
+
When executing standard `ActiveRecord` query, it will first query the cache, and if there is none in the cache,
|
|
6
|
+
then query the database and write the result to the cache.
|
|
7
|
+
|
|
8
|
+
> **! WARNING: Please read these [information](#Warning) before using `ArCache`.**
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- `Low impact`: If your code strictly comply with the activerecord style, you don’t need to modify any code.([see more details](#Warning))
|
|
13
|
+
- `Read cache`: Automatically intercept ActiveRecord queries, then try to fetch data from cache.
|
|
14
|
+
- `Write cache`: If the query is cacheable and the cached data is not exists, it will be automatically written to the cache after the query.
|
|
15
|
+
- `Expire cache`: Automatically expire cache after updated/modified data.
|
|
16
|
+
- `Iterative cache`: The cache version will be updated after table fields, `ArCache` switch or `ArCache` coder changed.
|
|
17
|
+
- `Shared cache`: The cache is not only used with ActiveRecord, you can easily use it in other places.([see examples](examples))
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
Add this line to your application's Gemfile:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
gem 'ar_cache'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
And then execute:
|
|
28
|
+
|
|
29
|
+
```shell
|
|
30
|
+
bundle install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Post Installation
|
|
34
|
+
|
|
35
|
+
If is an `rails` application:
|
|
36
|
+
|
|
37
|
+
```shell
|
|
38
|
+
rails generate ar_cache:install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Otherwise copy [configuration](lib/generators/ar_cache/templates/configuration.rb) and [migration](lib/generators/ar_cache/templates/migrate/create_ar_cache_records.rb.tt) files to your application.
|
|
42
|
+
|
|
43
|
+
After that review the migrations then migrate:
|
|
44
|
+
|
|
45
|
+
```shell
|
|
46
|
+
rake db:migrate
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
`ArCache` works automatically, so you don’t need to care about how to use the cache, just need to know how to skip and delete the cache.
|
|
52
|
+
|
|
53
|
+
Skip cache:
|
|
54
|
+
|
|
55
|
+
- `ActiveRecord::Persistence#reload`, eg:
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
User.find(1).reload
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `ActiveRecord::Relation#skip_ar_cache`, eg:
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
User.skip_ar_cache.find(1)
|
|
65
|
+
User.where(id: [1, 2]).skip_ar_cache.load
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Delete cache:
|
|
69
|
+
|
|
70
|
+
- `ArCache::Table`, eg:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
User.ar_cache_table.delete(id...)
|
|
74
|
+
User.first.ar_cache_table.delete(id...)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
For configuration information, please see [configuration](lib/generators/ar_cache/templates/configuration.rb) file.
|
|
80
|
+
|
|
81
|
+
## Cacheable query
|
|
82
|
+
|
|
83
|
+
If all the following conditions are met, ArCache will try to read the cache:
|
|
84
|
+
|
|
85
|
+
- Use hash as `#where` parameter.
|
|
86
|
+
- Query condition contains unique index.
|
|
87
|
+
- Condition of unique index is only one array or no array.
|
|
88
|
+
- No call `#select` or select value is table column.
|
|
89
|
+
- No call `#order` or order value is table column and only one.
|
|
90
|
+
- No call `#limit` or value of the unique index isn't array.
|
|
91
|
+
- No call `#joins`.
|
|
92
|
+
- No call `#left_joins`.
|
|
93
|
+
- No call `#skip_query_cache!`.
|
|
94
|
+
- No call `#skip_ar_cache`.
|
|
95
|
+
- No call `#explain`.
|
|
96
|
+
- No call `#from`.
|
|
97
|
+
- No call `#group`.
|
|
98
|
+
- No call `value`.
|
|
99
|
+
- ...
|
|
100
|
+
|
|
101
|
+
**Cacheable example:**
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
User.find(1) # primary key cache
|
|
105
|
+
User.where(id: [1, 2]) # array query cache
|
|
106
|
+
User.where(email: 'foobar@gmail.com') # sigle-column unique index cache
|
|
107
|
+
User.where(name: 'foobar', status: :active) # multi-column unique index cache
|
|
108
|
+
User.includes(:account).where(id: [1, 2]) # association cache
|
|
109
|
+
User.first.account # association model cache
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Cache iteration
|
|
113
|
+
|
|
114
|
+
The following cases will cause cache iteration:
|
|
115
|
+
|
|
116
|
+
- Table field changes.
|
|
117
|
+
- Open `ArCache` or close `ArCache`.
|
|
118
|
+
- `ActiveRecord` update/delete condition does not hit the unique index.
|
|
119
|
+
- `ActiveRecord` update/delete join other tables.
|
|
120
|
+
|
|
121
|
+
**Notice: After iteration, all existing caches of the table will be expired!**
|
|
122
|
+
|
|
123
|
+
## How it works(Work in progress)
|
|
124
|
+
|
|
125
|
+
`ArCache` works based on the unique index of the table.
|
|
126
|
+
|
|
127
|
+
## Warning
|
|
128
|
+
|
|
129
|
+
- Prohibit the use of `#execute` update/delete operations!
|
|
130
|
+
- Prohibit use `ActiveRecord` other underlying methods to directly update/delete data! (You is a fake activerecord user if this code appears)
|
|
131
|
+
- Prohibit skip `ActiveRecord` directly update/delete data!
|
|
132
|
+
|
|
133
|
+
## Alternatives
|
|
134
|
+
|
|
135
|
+
There are some other gems implementations for `ActiveRecord` cache such as:
|
|
136
|
+
|
|
137
|
+
- [identity_cache](https://github.com/Shopify/identity_cache)
|
|
138
|
+
- [second_level_cache](https://github.com/hooopo/second_level_cache)
|
|
139
|
+
- [cache-money](https://github.com/ngmoco/cache-money)
|
|
140
|
+
|
|
141
|
+
However, `ArCache` has some differences:
|
|
142
|
+
|
|
143
|
+
- It don’t depend with `ActiveRecord` callbacks, so don’t need to deal with dirty cache manually.
|
|
144
|
+
- It cache real database data, so can use it's cache at other places.
|
|
145
|
+
- It can automatically handle cache iteration, so don't need to update cache version manually.
|
|
146
|
+
- It proxy standard ActiveRecord query, so don't need to modify the code and remember the additional api.
|
|
147
|
+
- The new data need to perform a query before the cache will take effect.
|
|
148
|
+
- The cache will not be updated after the data is updated, but the cache will be expired directly.
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
153
|
+
|
|
154
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
155
|
+
|
|
156
|
+
## Contributing
|
|
157
|
+
|
|
158
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/OuYangJinTing/ar_cache>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/OuYangJinTing/ar_cache/blob/master/CODE_OF_CONDUCT.md).
|
|
159
|
+
|
|
160
|
+
## License
|
|
161
|
+
|
|
162
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
163
|
+
|
|
164
|
+
## Code of Conduct
|
|
165
|
+
|
|
166
|
+
Everyone interacting in the `ArCache` project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/OuYangJinTing/ar_cache/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rake/testtask'
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << 'test'
|
|
8
|
+
t.libs << 'lib'
|
|
9
|
+
t.test_files = FileList['test/**/*_test.rb']
|
|
10
|
+
t.verbose = true
|
|
11
|
+
t.warning = true
|
|
12
|
+
t.options = '--warnings --color'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Rake::TestTask.new(:bench) do |t|
|
|
16
|
+
t.libs << 'test'
|
|
17
|
+
t.libs << 'lib'
|
|
18
|
+
t.test_files = FileList['test/**/*_benchmark.rb']
|
|
19
|
+
t.verbose = true
|
|
20
|
+
t.warning = true
|
|
21
|
+
t.options = '--verbose --warnings --color'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
require 'rubocop/rake_task'
|
|
25
|
+
|
|
26
|
+
RuboCop::RakeTask.new
|
|
27
|
+
|
|
28
|
+
task default: %i[test rubocop]
|
data/ar_cache.gemspec
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/ar_cache/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'ar_cache'
|
|
7
|
+
spec.version = ArCache::VERSION
|
|
8
|
+
spec.authors = ['OuYangJinTing']
|
|
9
|
+
spec.email = ['2729877005qq@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'An modern cacheing library for ActiveRecord inspired by cache-money and second_level_cache.'
|
|
12
|
+
spec.description = spec.summary
|
|
13
|
+
spec.homepage = 'https://github.com/OuYangJinTing/ar_cache'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 3.0.0')
|
|
16
|
+
|
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['source_code_uri'] = 'https://github.com/OuYangJinTing/ar_cache.git'
|
|
19
|
+
spec.metadata['changelog_uri'] = 'https://github.com/OuYangJinTing/ar_cache/CHANGELOG.md'
|
|
20
|
+
|
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
23
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
24
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
|
25
|
+
end
|
|
26
|
+
spec.bindir = 'exe'
|
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
28
|
+
spec.require_paths = ['lib']
|
|
29
|
+
|
|
30
|
+
spec.add_runtime_dependency 'activerecord', '>= 6.1', '< 7'
|
|
31
|
+
|
|
32
|
+
# For more information and examples about making a new gem, checkout our
|
|
33
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
34
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
IFS=$'\n\t'
|
|
4
|
+
set -vx
|
|
5
|
+
|
|
6
|
+
if [ ! -d 'tmp/rails/' ]; then
|
|
7
|
+
git clone --branch=6-1-ar_cache-test https://github.com/OuYangJinTing/rails.git tmp/rails && cd tmp/rails
|
|
8
|
+
else
|
|
9
|
+
cd tmp/rails && git pull origin 6-1-ar_cache-test
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
BUNDLE_GEMFILE=./Gemfile
|
|
13
|
+
bundle install
|
|
14
|
+
|
|
15
|
+
cd activerecord
|
|
16
|
+
BUNDLE_GEMFILE=../Gemfile
|
|
17
|
+
# bundle exec rake rebuild_mysql_databases
|
|
18
|
+
bundle exec rake rebuild_postgresql_databases
|
|
19
|
+
bundle exec rake test:sqlite3
|
|
20
|
+
bundle exec rake test:postgresql
|
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'ar_cache'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require 'irb'
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/gemfiles/rails-6-1
ADDED
data/gemfiles/rails-edge
ADDED
data/lib/ar_cache.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/cache'
|
|
4
|
+
require 'active_record'
|
|
5
|
+
|
|
6
|
+
require 'ar_cache/version'
|
|
7
|
+
require 'ar_cache/configuration'
|
|
8
|
+
require 'ar_cache/record'
|
|
9
|
+
require 'ar_cache/store'
|
|
10
|
+
require 'ar_cache/marshal'
|
|
11
|
+
require 'ar_cache/table'
|
|
12
|
+
require 'ar_cache/mock_table'
|
|
13
|
+
require 'ar_cache/query'
|
|
14
|
+
require 'ar_cache/where_clause'
|
|
15
|
+
require 'ar_cache/log_subscriber'
|
|
16
|
+
require 'ar_cache/active_record'
|
|
17
|
+
|
|
18
|
+
require_relative './generators/ar_cache/install_generator' if defined?(Rails)
|
|
19
|
+
|
|
20
|
+
module ArCache
|
|
21
|
+
singleton_class.delegate :configure, to: Configuration
|
|
22
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ar_cache/active_record/model_schema'
|
|
4
|
+
require 'ar_cache/active_record/relation'
|
|
5
|
+
require 'ar_cache/active_record/core'
|
|
6
|
+
require 'ar_cache/active_record/persistence'
|
|
7
|
+
require 'ar_cache/active_record/insert_all'
|
|
8
|
+
require 'ar_cache/active_record/associations/singular_association'
|
|
9
|
+
require 'ar_cache/active_record/associations/has_one_through_association'
|
|
10
|
+
require 'ar_cache/active_record/connection_adapters/abstract/transaction'
|
|
11
|
+
require 'ar_cache/active_record/connection_adapters/abstract/database_statements'
|
|
12
|
+
|
|
13
|
+
# rubocop:disable Layout/LineLength
|
|
14
|
+
ActiveSupport.on_load(:active_record, run_once: true) do
|
|
15
|
+
ActiveRecord::Core::ClassMethods.prepend(ArCache::ActiveRecord::Core::ClassMethods)
|
|
16
|
+
|
|
17
|
+
ActiveRecord::ModelSchema.prepend(ArCache::ActiveRecord::ModelSchema)
|
|
18
|
+
ActiveRecord::ModelSchema::ClassMethods.prepend(ArCache::ActiveRecord::ModelSchema::ClassMethods)
|
|
19
|
+
|
|
20
|
+
ActiveRecord::Persistence.prepend(ArCache::ActiveRecord::Persistence)
|
|
21
|
+
|
|
22
|
+
ActiveRecord::InsertAll.prepend(ArCache::ActiveRecord::InsertAll)
|
|
23
|
+
|
|
24
|
+
ActiveRecord::Relation.prepend(ArCache::ActiveRecord::Relation)
|
|
25
|
+
|
|
26
|
+
ActiveRecord::Associations::SingularAssociation.prepend(ArCache::ActiveRecord::Associations::SingularAssociation)
|
|
27
|
+
ActiveRecord::Associations::HasOneThroughAssociation.prepend(ArCache::ActiveRecord::Associations::HasOneThroughAssociation)
|
|
28
|
+
|
|
29
|
+
ActiveRecord::ConnectionAdapters::NullTransaction.prepend(ArCache::ActiveRecord::ConnectionAdapters::NullTransaction)
|
|
30
|
+
ActiveRecord::ConnectionAdapters::RealTransaction.prepend(ArCache::ActiveRecord::ConnectionAdapters::Transaction)
|
|
31
|
+
ActiveRecord::ConnectionAdapters::SavepointTransaction.prepend(ArCache::ActiveRecord::ConnectionAdapters::Transaction)
|
|
32
|
+
ActiveRecord::ConnectionAdapters::TransactionManager.prepend(ArCache::ActiveRecord::ConnectionAdapters::TransactionManager)
|
|
33
|
+
|
|
34
|
+
ActiveRecord::ConnectionAdapters::DatabaseStatements.prepend(ArCache::ActiveRecord::ConnectionAdapters::DatabaseStatements)
|
|
35
|
+
end
|
|
36
|
+
# rubocop:enable Layout/LineLength
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Associations
|
|
6
|
+
module HasOneThroughAssociation
|
|
7
|
+
private def find_target # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
|
8
|
+
return super if reflection.klass.ar_cache_table.disabled?
|
|
9
|
+
return super if reflection.through_reflection.klass.ar_cache_table.disabled?
|
|
10
|
+
|
|
11
|
+
if owner.strict_loading? && owner.validation_context.nil?
|
|
12
|
+
Base.strict_loading_violation!(owner: owner.class, association: reflection.klass)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if reflection.strict_loading? && owner.validation_context.nil?
|
|
16
|
+
Base.strict_loading_violation!(owner: owner.class, association: reflection.name)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# TODO: Should not instantiate AR
|
|
20
|
+
through_record = if reflection.scope
|
|
21
|
+
owner.association(reflection.through_reflection.name).scope.merge(reflection.scope).first
|
|
22
|
+
else
|
|
23
|
+
owner.send(reflection.through_reflection.name)
|
|
24
|
+
end
|
|
25
|
+
return super if through_record.is_a?(::ActiveRecord::Associations::CollectionProxy)
|
|
26
|
+
return nil if !through_record || through_record.destroyed?
|
|
27
|
+
|
|
28
|
+
record = through_record.send(reflection.source_reflection.name)
|
|
29
|
+
record = record.first if record.is_a?(::ActiveRecord::Associations::CollectionProxy)
|
|
30
|
+
return nil unless record
|
|
31
|
+
|
|
32
|
+
record.tap { |r| set_inverse_instance(r) }
|
|
33
|
+
rescue StandardError # If scope depend on other table, will raise exception
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Associations
|
|
6
|
+
module SingularAssociation
|
|
7
|
+
private def skip_statement_cache?(...)
|
|
8
|
+
# Polymorphic associations do not support computing the class, so can't judge ArCache status.
|
|
9
|
+
# But SingularAssociation query usually can hit the unique index, so here return true directly.
|
|
10
|
+
return true if is_a?(::ActiveRecord::Associations::BelongsToPolymorphicAssociation)
|
|
11
|
+
return true if reflection.klass.ar_cache_table.enabled?
|
|
12
|
+
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ArCache
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module ConnectionAdapters
|
|
6
|
+
module DatabaseStatements
|
|
7
|
+
def insert(arel, ...)
|
|
8
|
+
super.tap do
|
|
9
|
+
if arel.is_a?(String)
|
|
10
|
+
sql = arel.downcase
|
|
11
|
+
ArCache::Table.all.each do |table|
|
|
12
|
+
current_transaction.add_changed_table(table.name) if sql.include?(table.name)
|
|
13
|
+
end
|
|
14
|
+
else # is Arel::InsertManager
|
|
15
|
+
klass = arel.ast.relation.instance_variable_get(:@klass)
|
|
16
|
+
current_transaction.add_changed_table(klass.table_name)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
alias create insert
|
|
21
|
+
|
|
22
|
+
def update(arel, ...)
|
|
23
|
+
super.tap { |num| update_ar_cache_version(arel) unless num.zero? }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def delete(arel, ...)
|
|
27
|
+
super.tap { |num| update_ar_cache_version(arel) unless num.zero? }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def truncate(table_name, ...)
|
|
31
|
+
super.tap { update_ar_cache_version_by_table(table_name) }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def truncate_tables(*table_names)
|
|
35
|
+
super.tap do
|
|
36
|
+
table_names.each { |table_name| update_ar_cache_version_by_table(table_name) }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private def update_ar_cache_version(arel_or_sql_string)
|
|
41
|
+
if arel_or_sql_string.is_a?(String)
|
|
42
|
+
update_ar_cache_version_by_sql(arel_or_sql_string)
|
|
43
|
+
else # is Arel::TreeManager
|
|
44
|
+
update_ar_cache_version_by_arel(arel_or_sql_string)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private def update_ar_cache_version_by_arel(arel)
|
|
49
|
+
# arel.ast.relation may be of the following types:
|
|
50
|
+
# - Arel::Nodes::JoinSource
|
|
51
|
+
# - Arel::Table
|
|
52
|
+
arel_table = arel.ast.relation.is_a?(Arel::Table) ? arel.ast.relation : arel.ast.relation.left
|
|
53
|
+
klass = arel_table.instance_variable_get(:@klass)
|
|
54
|
+
return if klass.ar_cache_table.disabled?
|
|
55
|
+
|
|
56
|
+
where_clause = ArCache::WhereClause.new(klass, arel.ast.wheres)
|
|
57
|
+
if where_clause.cacheable?
|
|
58
|
+
current_transaction.add_changed_table(klass.table_name)
|
|
59
|
+
current_transaction.add_ar_cache_keys(where_clause.cache_keys)
|
|
60
|
+
else
|
|
61
|
+
current_transaction.add_ar_cache_table(klass.ar_cache_table)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private def update_ar_cache_version_by_sql(sql)
|
|
66
|
+
sql = sql.downcase
|
|
67
|
+
ArCache::Table.all.each { |table| current_transaction.add_ar_cache_table(table) if sql.include?(table.name) }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private def update_ar_cache_version_by_table(table_name)
|
|
71
|
+
ArCache::Table.all.each { |table| table.update_version if table_name.casecmp?(table.name) }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|