db_memoize 0.1.6 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -0
- data/README.md +12 -34
- data/Rakefile +6 -1
- data/db_memoize.gemspec +1 -1
- data/lib/db_memoize.rb +2 -0
- data/lib/db_memoize/helpers.rb +15 -1
- data/lib/db_memoize/metal.rb +102 -0
- data/lib/db_memoize/migrations.rb +16 -4
- data/lib/db_memoize/model.rb +21 -23
- data/lib/db_memoize/value.rb +2 -0
- data/lib/db_memoize/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 254ec2be767020e1ff61a4b5ddbfc561f894b716
|
4
|
+
data.tar.gz: b27ccaf67e4a4dc32901fe802ab6b7da5872d9ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e430b58340a10f27910e558068cca7ac75ab8e07ec9d0aa5898d340aeeaa142dc3241779d3f1c02d16755d8390ddaee44518a704c563e9c5b7a5aa2ac670ea36
|
7
|
+
data.tar.gz: 82949ec2ac4094966459edbeadb5f34c91f03d9f20cda7e98ee9c1978b3b6fb12a671623473fdb7db88554a01f0ec9fcfb28c8fe8da873f5f218adbd1d07b430
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -35,10 +35,8 @@ will call the original method only once. Consecutive calls will return a cached
|
|
35
35
|
|
36
36
|
If the method takes arguments..
|
37
37
|
|
38
|
-
|
39
|
-
record.hello('
|
40
|
-
record.hello('John')
|
41
|
-
```
|
38
|
+
record.hello('Maria')
|
39
|
+
record.hello('John')
|
42
40
|
|
43
41
|
a cached value will be created for every set of arguments.
|
44
42
|
|
@@ -46,67 +44,47 @@ a cached value will be created for every set of arguments.
|
|
46
44
|
|
47
45
|
To clear cached values for a single method
|
48
46
|
|
49
|
-
|
50
|
-
record.unmemoize(:hello)
|
51
|
-
```
|
47
|
+
record.unmemoize(:hello)
|
52
48
|
|
53
49
|
To clear all cached values of one record
|
54
50
|
|
55
|
-
|
56
|
-
record.unmemoize
|
57
|
-
```
|
51
|
+
record.unmemoize
|
58
52
|
|
59
53
|
To clear cached values of given records for a single method
|
60
54
|
|
61
|
-
|
62
|
-
Letter.unmemoize([letter1, letter2], :hello)
|
63
|
-
```
|
55
|
+
Letter.unmemoize([letter1, letter2], :hello)
|
64
56
|
|
65
57
|
To clear all cached values of given records
|
66
58
|
|
67
|
-
|
68
|
-
Letter.unmemoize([letter1, letter2])
|
69
|
-
```
|
59
|
+
Letter.unmemoize([letter1, letter2])
|
70
60
|
|
71
61
|
Instead of ActiveRecord instances it's sufficient to pass in the ids of the records, too
|
72
62
|
|
73
|
-
|
74
|
-
Letter.unmemoize([23,24])
|
75
|
-
```
|
63
|
+
Letter.unmemoize([23,24])
|
76
64
|
|
77
65
|
### Gotchas
|
78
66
|
|
79
67
|
As the cached values themselves are writtten to the database, are ActiveRecord records (of type `DbMemoize::Value`) and are reqistered as an association you can access all of the cached values of an object like this:
|
80
68
|
|
81
|
-
|
82
|
-
record.memoized_values
|
83
|
-
```
|
69
|
+
record.memoized_values
|
84
70
|
|
85
71
|
This means you can also very easily perform eager loading on them:
|
86
72
|
|
87
|
-
|
88
|
-
Letter.includes(:memoized_values).all
|
89
|
-
```
|
73
|
+
Letter.includes(:memoized_values).all
|
90
74
|
|
91
75
|
DbMemoize by default will write log output to STDOUT. You can change this by setting another logger like so:
|
92
76
|
|
93
|
-
|
94
|
-
DbMemoize.logger = your_logger
|
95
|
-
```
|
77
|
+
DbMemoize.logger = your_logger
|
96
78
|
|
97
79
|
### Rake Tasks
|
98
80
|
|
99
81
|
To _warmup_ your cache you can pre-generate cached values via a rake task like this (only works for methods not depending on arguments)
|
100
82
|
|
101
|
-
|
102
|
-
bundle exec rake db_memoize:warmup class=Letter methods=hello,bye
|
103
|
-
```
|
83
|
+
bundle exec rake db_memoize:warmup class=Letter methods=hello,bye
|
104
84
|
|
105
85
|
Similarly you can wipe all cached values for a given class
|
106
86
|
|
107
|
-
|
108
|
-
bundle exec rake db_memoize:clear class=Letter
|
109
|
-
```
|
87
|
+
bundle exec rake db_memoize:clear class=Letter
|
110
88
|
|
111
89
|
### Setup
|
112
90
|
|
data/Rakefile
CHANGED
data/db_memoize.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_development_dependency 'activerecord', '~> 4.2'
|
26
26
|
|
27
27
|
spec.add_development_dependency 'rake', '~> 10.5'
|
28
|
-
spec.add_development_dependency '
|
28
|
+
spec.add_development_dependency 'pg'
|
29
29
|
spec.add_development_dependency 'rspec-rails', '~> 3.4'
|
30
30
|
spec.add_development_dependency 'pry', '~> 0.10'
|
31
31
|
spec.add_development_dependency 'pry-byebug', '~> 2.0'
|
data/lib/db_memoize.rb
CHANGED
data/lib/db_memoize/helpers.rb
CHANGED
@@ -12,7 +12,21 @@ module DbMemoize
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def log(model, method_name, msg)
|
15
|
-
DbMemoize.logger.send(DbMemoize.log_level
|
15
|
+
DbMemoize.logger.send(DbMemoize.log_level) do
|
16
|
+
"DbMemoize <#{model.class.name}##{model.id}>##{method_name} - #{msg}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def calculate_arguments_hash(arguments)
|
21
|
+
arguments.empty? ? nil : ::Digest::MD5.hexdigest(Marshal.dump(arguments))
|
22
|
+
end
|
23
|
+
|
24
|
+
def marshal(value)
|
25
|
+
Marshal.dump(value)
|
26
|
+
end
|
27
|
+
|
28
|
+
def unmarshal(value)
|
29
|
+
Marshal.load(value)
|
16
30
|
end
|
17
31
|
end
|
18
32
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module DbMemoize
|
2
|
+
module Metal
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
# base.metal # initialize the metal adapter
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def metal
|
10
|
+
@metal ||= Adapter.new(self)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Adapter
|
15
|
+
class PkInfo
|
16
|
+
attr_reader :column, :type
|
17
|
+
|
18
|
+
def initialize(base_klass)
|
19
|
+
@column = base_klass.primary_key
|
20
|
+
@type = @column && base_klass.columns_hash.fetch(@column).type
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(base_klass)
|
25
|
+
@base_klass = base_klass
|
26
|
+
@query_cache = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# setup primary key information. This is necessary to allow the create! method
|
32
|
+
# to return the primary key of a newly created entry.
|
33
|
+
def primary_key
|
34
|
+
@primary_key ||= PkInfo.new(@base_klass)
|
35
|
+
end
|
36
|
+
|
37
|
+
def table_name
|
38
|
+
@base_klass.table_name
|
39
|
+
end
|
40
|
+
|
41
|
+
def raw_connection
|
42
|
+
@base_klass.connection.raw_connection # do not memoize me!
|
43
|
+
end
|
44
|
+
|
45
|
+
def column?(column_name)
|
46
|
+
@base_klass.columns_hash.key?(column_name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def insert_sql(field_names)
|
50
|
+
@query_cache[field_names] ||= _insert_sql(field_names)
|
51
|
+
end
|
52
|
+
|
53
|
+
DATABASE_IDENTIFIER_REGEX = /\A\w+\z/
|
54
|
+
|
55
|
+
def check_database_identifiers!(*strings)
|
56
|
+
strings.each do |s|
|
57
|
+
next if DATABASE_IDENTIFIER_REGEX =~ s.to_s
|
58
|
+
raise ArgumentError, "Invalid database identifier: #{s.inspect}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def _insert_sql(field_names)
|
63
|
+
check_database_identifiers! table_name, *field_names
|
64
|
+
|
65
|
+
placeholders = 0.upto(field_names.count - 1).map { |idx| "$#{idx + 1}" }
|
66
|
+
|
67
|
+
if column?('created_at')
|
68
|
+
field_names << 'created_at'
|
69
|
+
placeholders << 'current_timestamp'
|
70
|
+
end
|
71
|
+
|
72
|
+
if column?('updated_at')
|
73
|
+
field_names << 'updated_at'
|
74
|
+
placeholders << 'current_timestamp'
|
75
|
+
end
|
76
|
+
|
77
|
+
sql = "INSERT INTO #{table_name} (#{field_names.join(',')}) VALUES(#{placeholders.join(',')})"
|
78
|
+
sql += " RETURNING #{primary_key.column}" if primary_key.column
|
79
|
+
sql
|
80
|
+
end
|
81
|
+
|
82
|
+
public
|
83
|
+
|
84
|
+
def create!(record)
|
85
|
+
keys, values = record.to_a.transpose
|
86
|
+
|
87
|
+
sql = insert_sql(keys)
|
88
|
+
result = raw_connection.exec_params(sql, values)
|
89
|
+
|
90
|
+
# if we don't have an ID column then the sql does not return any value. The result
|
91
|
+
# object would be this: #<PG::Result status=PGRES_COMMAND_OK ntuples=0 nfields=0 cmd_tuples=1>
|
92
|
+
# we just return nil in that case; otherwise we return the first entry of the first result row
|
93
|
+
# which would be the stringified id.
|
94
|
+
first_row = result.each_row.first
|
95
|
+
return nil unless first_row
|
96
|
+
|
97
|
+
id = first_row.first
|
98
|
+
primary_key.type == :integer ? Integer(id) : id
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -3,15 +3,27 @@ module DbMemoize
|
|
3
3
|
class << self
|
4
4
|
def create_tables(migration)
|
5
5
|
migration.create_table :memoized_values, id: false do |t|
|
6
|
-
t.string :entity_table_name
|
7
|
-
t.integer :entity_id
|
8
|
-
t.string :method_name
|
6
|
+
t.string :entity_table_name, null: false
|
7
|
+
t.integer :entity_id, null: false
|
8
|
+
t.string :method_name, null: false
|
9
9
|
t.string :arguments_hash
|
10
10
|
t.binary :value
|
11
|
-
t.datetime :created_at
|
11
|
+
t.datetime :created_at, null: false
|
12
12
|
end
|
13
13
|
|
14
14
|
migration.add_index :memoized_values, [:entity_table_name, :entity_id]
|
15
|
+
migrate_empty_arguments_support(migration)
|
16
|
+
end
|
17
|
+
|
18
|
+
def migrate_empty_arguments_support(migration)
|
19
|
+
# entity_id/entity_table_name should have a better chance to be useful, since
|
20
|
+
# there is more variance in entity_ids than there is in entity_table_names.
|
21
|
+
migration.remove_index :memoized_values, [:entity_table_name, :entity_id]
|
22
|
+
migration.add_index :memoized_values, [:entity_id, :entity_table_name]
|
23
|
+
|
24
|
+
# add an index to be useful to look up entries where arguments_hash is NULL.
|
25
|
+
# (which is the case for plain attributes of an object)
|
26
|
+
migration.execute 'CREATE INDEX memoized_attributes_idx ON memoized_values((arguments_hash IS NULL))'
|
15
27
|
end
|
16
28
|
end
|
17
29
|
end
|
data/lib/db_memoize/model.rb
CHANGED
@@ -8,11 +8,11 @@ module DbMemoize
|
|
8
8
|
end
|
9
9
|
|
10
10
|
value = nil
|
11
|
-
args_hash =
|
11
|
+
args_hash = Helpers.calculate_arguments_hash(args)
|
12
12
|
cached_value = find_memoized_value(method_name, args_hash)
|
13
13
|
|
14
14
|
if cached_value
|
15
|
-
value =
|
15
|
+
value = Helpers.unmarshal(cached_value.value)
|
16
16
|
Helpers.log(self, method_name, 'cache hit')
|
17
17
|
else
|
18
18
|
time = ::Benchmark.realtime do
|
@@ -44,7 +44,8 @@ module DbMemoize
|
|
44
44
|
# autocomplete_info: "my autocomplete_info"
|
45
45
|
#
|
46
46
|
def memoize_values(values, *args)
|
47
|
-
|
47
|
+
# [TODO] - when creating many memoized values: should we even support arguments here?
|
48
|
+
args_hash = Helpers.calculate_arguments_hash(args)
|
48
49
|
|
49
50
|
values.each do |name, value|
|
50
51
|
create_memoized_value(name, args_hash, value)
|
@@ -53,16 +54,14 @@ module DbMemoize
|
|
53
54
|
|
54
55
|
private
|
55
56
|
|
56
|
-
def create_memoized_value(method_name,
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
value: Marshal.dump(value)
|
65
|
-
)
|
57
|
+
def create_memoized_value(method_name, arguments_hash, value)
|
58
|
+
::DbMemoize::Value.metal.create! entity_table_name: self.class.table_name,
|
59
|
+
entity_id: id,
|
60
|
+
method_name: method_name.to_s,
|
61
|
+
arguments_hash: arguments_hash,
|
62
|
+
value: Helpers.marshal(value)
|
63
|
+
|
64
|
+
@association_cache.delete :memoized_values
|
66
65
|
end
|
67
66
|
|
68
67
|
def find_memoized_value(method_name, args_hash)
|
@@ -101,19 +100,18 @@ module DbMemoize
|
|
101
100
|
end
|
102
101
|
|
103
102
|
def memoize_values(records_or_ids, values, *args)
|
103
|
+
# [TODO] - when creating many memoized values: should we even support arguments here?
|
104
104
|
transaction do
|
105
|
-
ids
|
106
|
-
|
105
|
+
ids = Helpers.find_ids(records_or_ids)
|
106
|
+
arguments_hash = Helpers.calculate_arguments_hash(args)
|
107
107
|
|
108
108
|
ids.each do |id|
|
109
|
-
values.each do |
|
110
|
-
DbMemoize::Value.create!
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
value: Marshal.dump(value)
|
116
|
-
)
|
109
|
+
values.each do |method_name, value|
|
110
|
+
::DbMemoize::Value.metal.create! entity_table_name: table_name,
|
111
|
+
entity_id: id,
|
112
|
+
method_name: method_name.to_s,
|
113
|
+
arguments_hash: arguments_hash,
|
114
|
+
value: Helpers.marshal(value)
|
117
115
|
end
|
118
116
|
end
|
119
117
|
end
|
data/lib/db_memoize/value.rb
CHANGED
data/lib/db_memoize/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: db_memoize
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- johannes-kostas goetzinger
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-06-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -53,7 +53,7 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '10.5'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: pg
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
@@ -240,6 +240,7 @@ files:
|
|
240
240
|
- db_memoize.gemspec
|
241
241
|
- lib/db_memoize.rb
|
242
242
|
- lib/db_memoize/helpers.rb
|
243
|
+
- lib/db_memoize/metal.rb
|
243
244
|
- lib/db_memoize/migrations.rb
|
244
245
|
- lib/db_memoize/model.rb
|
245
246
|
- lib/db_memoize/railtie.rb
|
@@ -247,6 +248,7 @@ files:
|
|
247
248
|
- lib/db_memoize/version.rb
|
248
249
|
- lib/tasks/clear.rake
|
249
250
|
- lib/tasks/warmup.rake
|
251
|
+
- log/.keep
|
250
252
|
homepage: https://github.com/mediapeers/db_memoize
|
251
253
|
licenses:
|
252
254
|
- MIT
|