active_cash 0.1.0 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +25 -2
- data/lib/active_cash.rb +1 -1
- data/lib/active_cash/adapter.rb +18 -2
- data/lib/active_cash/cache.rb +30 -18
- data/lib/active_cash/errors.rb +3 -0
- data/lib/active_cash/utils.rb +70 -16
- data/lib/active_cash/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ce7a48da4376ed48bcaf90d9b56d270cad0d646
|
4
|
+
data.tar.gz: 689beea4558ef5b39f18d3cf0c1d12585085028d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2fc6f6e25239b5facaafc71341cda2e89a0a3e94b29adff8b3119b5835d09ea28412e9e00e27e2334570e2d1ec05fc6c7bbfa925af969da53c0722579383d835
|
7
|
+
data.tar.gz: 049e2153e2fbd3ed201924af45d0bf85794abcfd01f0458204ba5c273936e26ad4110a4958a7f7dac604cce306052fa60652522004925bc48537c3cef9937193
|
data/README.md
CHANGED
@@ -30,7 +30,7 @@ class Like < ActiveRecord::Base
|
|
30
30
|
|
31
31
|
# type # matched conditions # cache name (default is strategy name)
|
32
32
|
caches :existence, find_by: [:user_id, :video_id]
|
33
|
-
caches :existence, find_by: [:user_id, :video_id, :hidden], name: :hidden
|
33
|
+
caches :existence, find_by: [:user_id, :video_id, :hidden], as: :hidden #also name: :hidden is supported
|
34
34
|
end
|
35
35
|
|
36
36
|
# Check if Like exists for specific :user_id, :video_id.
|
@@ -45,7 +45,11 @@ end
|
|
45
45
|
I am open for better naming conventions :)
|
46
46
|
|
47
47
|
Cache will be created and updated on every object creation, update and deletion
|
48
|
-
using an `after_commit` callback.
|
48
|
+
using an `after_commit` callback. Only a string value is saved (true or false)
|
49
|
+
in Redis (using [redis-objects](https://github.com/nateware/redis-objects) underneath)
|
50
|
+
making it extremely efficient.
|
51
|
+
|
52
|
+
If you want to have a read-driven cache (which could
|
49
53
|
enhance your hit ratio depending on your read/writes ratio) you can provide an
|
50
54
|
empty `update_on` array (by default it includes `[:create, :update, :destroy]`):
|
51
55
|
|
@@ -68,13 +72,32 @@ end
|
|
68
72
|
|
69
73
|
Now cache will be created and updated ONLY IF there is a reference to Redis.
|
70
74
|
|
75
|
+
You can also specify the return value (the symbol must be a method defined in the class instance):
|
76
|
+
``` ruby
|
77
|
+
class Like < ActiveRecord::Base
|
78
|
+
include ActiveCash
|
79
|
+
|
80
|
+
caches :existence, find_by: [:user_id, :video_id, :state], as: :state, returns: :state
|
81
|
+
end
|
82
|
+
|
83
|
+
# Check if Like exists for specific :user_id, :video_id.
|
84
|
+
# returns the state
|
85
|
+
# returns false if state.nil?
|
86
|
+
@like = Like.cached_existence_by(user_id: 1, video_id: 2)
|
87
|
+
```
|
88
|
+
|
89
|
+
Not though that cache will return false if state is nil.
|
90
|
+
|
91
|
+
|
71
92
|
## Todo
|
72
93
|
Only existence strategy is supported at the moment but the code is designed to
|
73
94
|
support other kind of caching strategies as well, like caching the whole object
|
74
95
|
or some parts of it. Also:
|
75
96
|
|
97
|
+
* Refactor a bit :(
|
76
98
|
* We should provide named callbacks
|
77
99
|
* It's super easy to extend for Mongoid
|
100
|
+
* Add memcached adapter
|
78
101
|
|
79
102
|
## Relevant projects
|
80
103
|
There is [identity_cache](https://github.com/Shopify/identity_cache) built by Shopify.
|
data/lib/active_cash.rb
CHANGED
@@ -13,7 +13,7 @@ module ActiveCash
|
|
13
13
|
end
|
14
14
|
module ClassMethods
|
15
15
|
def caches(type, opts = {})
|
16
|
-
name = opts[:name] || type.to_sym
|
16
|
+
name = opts[:name] || opts[:as] || type.to_sym
|
17
17
|
@cache_opts = Utils.build_cache_opts(type, opts, @cache_opts, self.to_s)
|
18
18
|
Utils.set_callbacks(self, @cache_opts[name])
|
19
19
|
Utils.create_methods(self, @cache_opts[name])
|
data/lib/active_cash/adapter.rb
CHANGED
@@ -2,22 +2,38 @@ module ActiveCash::Adapter #fix boolean and make it generic
|
|
2
2
|
extend self
|
3
3
|
|
4
4
|
def get_value(key_name)
|
5
|
-
|
5
|
+
value = Redis::Value.new(key_name).value
|
6
|
+
is_boolean?(value) ? boolean(value) : value
|
6
7
|
end
|
7
8
|
|
8
9
|
def set_value(key_name, value)
|
9
10
|
Redis::Value.new(key_name).value = value
|
10
11
|
|
11
|
-
boolean(value)
|
12
|
+
is_boolean?(value) ? boolean(value) : value
|
12
13
|
end
|
13
14
|
|
14
15
|
def delete_value(key_name)
|
15
16
|
Redis::Value.new(key_name).delete
|
16
17
|
end
|
17
18
|
|
19
|
+
def set_value_with_return(key_name, exists, returns)
|
20
|
+
if exists
|
21
|
+
returns.nil? ? set_value(key_name, true) : set_value(key_name, returns)
|
22
|
+
else
|
23
|
+
set_value(key_name, false)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
18
27
|
def boolean(value)
|
19
28
|
return nil if value.nil?
|
20
29
|
|
21
30
|
[true, 1, '1', 't', 'T', 'true', 'TRUE'].include? value
|
22
31
|
end
|
32
|
+
|
33
|
+
def is_boolean?(value)
|
34
|
+
[
|
35
|
+
true, false, 1, 0, 'true', 'false', '1', '0', 't', 'f', 'TRUE', 'FALSE',
|
36
|
+
'T', 'F'
|
37
|
+
].include? value
|
38
|
+
end
|
23
39
|
end
|
data/lib/active_cash/cache.rb
CHANGED
@@ -1,74 +1,86 @@
|
|
1
1
|
module ActiveCash::Cache
|
2
2
|
extend self
|
3
3
|
|
4
|
-
def exists?(find_by:, method_name:,
|
4
|
+
def exists?(find_by:, method_name:, returns:, klass:)
|
5
5
|
key_name = get_key_name(klass, method_name, find_by)
|
6
6
|
cached_value = adapter.get_value(key_name)
|
7
7
|
return cached_value unless cached_value.nil?
|
8
8
|
|
9
|
-
|
9
|
+
instance = klass.to_s.constantize.find_by(find_by)
|
10
|
+
adapter.set_value_with_return(
|
11
|
+
key_name, !instance.nil?, instance.try(returns)
|
12
|
+
)
|
10
13
|
end
|
11
14
|
|
12
|
-
def instance_exists?(find_by:, method_name:, instance:,
|
15
|
+
def instance_exists?(find_by:, method_name:, instance:, returns:)
|
13
16
|
key_name = get_key_name(instance.class, method_name, find_by)
|
14
17
|
cached_value = adapter.get_value(key_name)
|
15
18
|
return cached_value unless cached_value.nil?
|
16
19
|
|
17
|
-
instance_update(
|
20
|
+
instance_update(
|
21
|
+
find_by: find_by, method_name: method_name, instance: instance,
|
22
|
+
returns: returns
|
23
|
+
)
|
18
24
|
end
|
19
25
|
|
20
|
-
def instance_update(find_by:, method_name:,
|
26
|
+
def instance_update(find_by:, method_name:, returns:, instance:)
|
21
27
|
key_name = get_key_name(instance.class, method_name, find_by)
|
22
28
|
|
23
29
|
find_by.keys.each_with_index do |key, i|
|
24
30
|
if instance.send(key) != find_by.values[i]
|
25
|
-
return adapter.
|
31
|
+
return adapter.set_value_with_return(key_name, false, returns)
|
26
32
|
end
|
27
33
|
end
|
28
34
|
|
29
|
-
adapter.
|
35
|
+
adapter.set_value_with_return(key_name, true, returns)#instance.try(returns))
|
30
36
|
end
|
31
37
|
|
32
|
-
def instance_updated?(find_by:, instance:)
|
38
|
+
def instance_updated?(find_by:, instance:, returns:)
|
39
|
+
changes = OpenStruct.new(instance.previous_changes)
|
40
|
+
return true if changes.send(returns) != nil
|
41
|
+
|
33
42
|
find_by.keys.each_with_index do |key, i|
|
34
|
-
if
|
43
|
+
if changes.send(key) != nil
|
35
44
|
return true
|
36
45
|
end
|
37
46
|
end
|
38
47
|
end
|
39
48
|
|
40
|
-
def old_instance_update(find_by:, method_name:,
|
49
|
+
def old_instance_update(find_by:, method_name:, returns:, instance:)
|
41
50
|
#old values
|
42
51
|
instance_update_if_exists(
|
43
52
|
find_by: Hash[find_by.map {|k, v| ["#{k}_was", v] }],
|
44
53
|
method_name: method_name,
|
45
|
-
|
54
|
+
returns: returns,
|
46
55
|
instance: instance
|
47
56
|
)
|
48
57
|
end
|
49
58
|
|
50
|
-
def instance_update_if_exists(find_by:, method_name:,
|
59
|
+
def instance_update_if_exists(find_by:, method_name:, returns:, instance:)
|
51
60
|
key_name = get_key_name(instance.class, method_name, find_by)
|
52
61
|
|
53
62
|
cached_value = adapter.get_value(key_name)
|
54
63
|
return true if cached_value.nil?
|
55
64
|
|
56
|
-
instance_update(
|
65
|
+
instance_update(
|
66
|
+
find_by: find_by, method_name: method_name, instance: instance,
|
67
|
+
returns: returns
|
68
|
+
)
|
57
69
|
end
|
58
70
|
|
59
|
-
def delete(find_by:, method_name:,
|
71
|
+
def delete(find_by:, method_name:, returns:, klass:)
|
60
72
|
key_name = get_key_name(klass, method_name, find_by)
|
61
73
|
adapter.delete_value(key_name)
|
62
74
|
end
|
63
75
|
|
64
|
-
def set_false(find_by:, method_name:,
|
76
|
+
def set_false(find_by:, method_name:, returns:, klass:)
|
65
77
|
key_name = get_key_name(klass, method_name, find_by)
|
66
|
-
adapter.
|
78
|
+
adapter.set_value_with_return(key_name, false, returns)
|
67
79
|
end
|
68
80
|
|
69
|
-
def set_true(find_by:, method_name:,
|
81
|
+
def set_true(find_by:, method_name:, returns:, klass:)
|
70
82
|
key_name = get_key_name(klass, method_name, find_by)
|
71
|
-
adapter.
|
83
|
+
adapter.set_value_with_return(key_name, true, returns)
|
72
84
|
end
|
73
85
|
|
74
86
|
def get_key_name(klass, method_name, hash_params)
|
data/lib/active_cash/errors.rb
CHANGED
data/lib/active_cash/utils.rb
CHANGED
@@ -5,32 +5,52 @@ module ActiveCash::Utils
|
|
5
5
|
if (opts[:update_on] & [:create]).blank?
|
6
6
|
model.send(:after_commit, on: [:create]) do
|
7
7
|
ActiveCash::Cache.instance_update_if_exists(
|
8
|
-
ActiveCash::Utils.extract_instance_args(opts, self)
|
8
|
+
ActiveCash::Utils.extract_instance_args(opts, self).merge(
|
9
|
+
returns: self.try(opts[:returns])
|
10
|
+
)
|
9
11
|
)
|
10
12
|
end
|
11
13
|
else
|
12
14
|
model.send(:after_commit, on: [:create]) do
|
13
15
|
ActiveCash::Cache.instance_update(
|
14
|
-
ActiveCash::Utils.extract_instance_args(opts, self)
|
16
|
+
ActiveCash::Utils.extract_instance_args(opts, self).merge(
|
17
|
+
returns: self.try(opts[:returns])
|
18
|
+
)
|
15
19
|
)
|
16
20
|
end
|
17
21
|
end
|
18
22
|
|
19
23
|
if (opts[:update_on] & [:update]).blank?
|
20
24
|
model.send(:after_commit, on: [:update]) do
|
21
|
-
if instance_updated?(
|
22
|
-
|
25
|
+
if ActiveCash::Cache.instance_updated?(
|
26
|
+
find_by: opts[:find_by], instance: self, returns: opts[:returns]
|
27
|
+
)
|
28
|
+
ActiveCash::Cache.delete(
|
29
|
+
ActiveCash::Utils.extract_instance_old_args(opts, self).merge(
|
30
|
+
returns: self.try(opts[:returns])
|
31
|
+
)
|
32
|
+
)
|
23
33
|
ActiveCash::Cache.instance_update_if_exists(
|
24
|
-
ActiveCash::Utils.extract_instance_args(opts, self)
|
34
|
+
ActiveCash::Utils.extract_instance_args(opts, self).merge(
|
35
|
+
returns: self.try(opts[:returns])
|
36
|
+
)
|
25
37
|
)
|
26
38
|
end
|
27
39
|
end
|
28
40
|
else
|
29
41
|
model.send(:after_commit, on: [:update]) do
|
30
|
-
if instance_updated?(
|
31
|
-
|
42
|
+
if ActiveCash::Cache.instance_updated?(
|
43
|
+
find_by: opts[:find_by], instance: self, returns: opts[:returns]
|
44
|
+
)
|
45
|
+
ActiveCash::Cache.delete(
|
46
|
+
ActiveCash::Utils.extract_instance_old_args(opts, self).merge(
|
47
|
+
returns: self.try(opts[:returns])
|
48
|
+
)
|
49
|
+
)
|
32
50
|
ActiveCash::Cache.instance_update(
|
33
|
-
ActiveCash::Utils.extract_instance_args(opts, self)
|
51
|
+
ActiveCash::Utils.extract_instance_args(opts, self).merge(
|
52
|
+
returns: self.try(opts[:returns])
|
53
|
+
)
|
34
54
|
)
|
35
55
|
end
|
36
56
|
end
|
@@ -39,13 +59,17 @@ module ActiveCash::Utils
|
|
39
59
|
if (opts[:update_on] & [:destroy]).blank?
|
40
60
|
model.send(:after_commit, on: [:destroy]) do
|
41
61
|
ActiveCash::Cache.delete(
|
42
|
-
ActiveCash::Utils.extract_args(opts, self)
|
62
|
+
ActiveCash::Utils.extract_args(opts, self).merge(
|
63
|
+
returns: self.try(opts[:returns])
|
64
|
+
)
|
43
65
|
)
|
44
66
|
end
|
45
67
|
else
|
46
68
|
model.send(:after_commit, on: [:destroy]) do
|
47
69
|
ActiveCash::Cache.set_false(
|
48
|
-
ActiveCash::Utils.extract_args(opts, self)
|
70
|
+
ActiveCash::Utils.extract_args(opts, self).merge(
|
71
|
+
returns: self.try(opts[:returns])
|
72
|
+
)
|
49
73
|
)
|
50
74
|
end
|
51
75
|
end
|
@@ -53,7 +77,21 @@ module ActiveCash::Utils
|
|
53
77
|
|
54
78
|
def create_methods(model, opts) #raise error when arg not given
|
55
79
|
model.send(:define_singleton_method, "cached_#{opts[:name]}_by") do |args = {}|
|
56
|
-
|
80
|
+
xor_array = ((args.keys | opts[:find_by]) - (args.keys & opts[:find_by]))
|
81
|
+
if xor_array.empty?
|
82
|
+
return ActiveCash::Cache.exists?(
|
83
|
+
find_by: opts[:find_by].inject({}) {|h, arg| h[arg] = args[arg]; h},
|
84
|
+
method_name: opts[:name],
|
85
|
+
klass: opts[:klass],
|
86
|
+
returns: opts[:returns]
|
87
|
+
)
|
88
|
+
else
|
89
|
+
ActiveCash::Utils.raise_find_by_options_mismatch(xor_array, opts[:find_by])
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
model.send(:define_singleton_method, "delete_cached_#{opts[:name]}_by") do |args = {}|
|
94
|
+
return ActiveCash::Cache.delete(
|
57
95
|
find_by: opts[:find_by].inject({}) {|h, arg| h[arg] = args[arg]; h},
|
58
96
|
method_name: opts[:name],
|
59
97
|
klass: opts[:klass]
|
@@ -61,6 +99,15 @@ module ActiveCash::Utils
|
|
61
99
|
end
|
62
100
|
end
|
63
101
|
|
102
|
+
def raise_find_by_options_mismatch(invalid, valid)
|
103
|
+
if (invalid & valid).any?
|
104
|
+
raise(FindByOptionsMismatchError, "Missing find_by options: #{invalid}")
|
105
|
+
else
|
106
|
+
raise(FindByOptionsMismatchError, "Not registerd find_by options: #{invalid}")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
64
111
|
def raise_unknown_cache_type(type)
|
65
112
|
raise(
|
66
113
|
UnknownCacheTypeError,
|
@@ -98,13 +145,20 @@ module ActiveCash::Utils
|
|
98
145
|
end
|
99
146
|
|
100
147
|
#and this
|
101
|
-
def
|
148
|
+
def extract_instance_old_args(opts, instance)
|
149
|
+
changes = OpenStruct.new(instance.previous_changes)
|
150
|
+
|
102
151
|
{
|
103
152
|
find_by: opts[:find_by].inject({}){|h, arg|
|
104
|
-
|
153
|
+
if changes.arg != nil
|
154
|
+
h[arg] = changes.send(arg).first
|
155
|
+
else
|
156
|
+
h[arg] = instance.send(arg)
|
157
|
+
end
|
158
|
+
h
|
105
159
|
},
|
106
160
|
method_name: opts[:name],
|
107
|
-
|
161
|
+
klass: instance.class
|
108
162
|
}
|
109
163
|
end
|
110
164
|
|
@@ -112,13 +166,13 @@ module ActiveCash::Utils
|
|
112
166
|
#add procs/lambdas for better finds
|
113
167
|
raise_unknown_cache_type(type) unless type.to_sym == :existence
|
114
168
|
cache_opts ||= {}
|
115
|
-
name = opts[:name] || type.to_sym
|
169
|
+
name = opts[:name] || opts[:as] || type.to_sym
|
116
170
|
raise_redefined_cache_error(name) unless cache_opts[name].nil?
|
117
171
|
cache_opts[name] = {}
|
118
172
|
cache_opts[name][:name] = name
|
119
173
|
cache_opts[name][:type] = type.to_sym
|
120
174
|
cache_opts[name][:find_by] = opts[:find_by]
|
121
|
-
cache_opts[name][:
|
175
|
+
cache_opts[name][:returns] = opts[:returns] || :nil
|
122
176
|
cache_opts[name][:update_on] = opts[:update_on] || [:create, :update, :destroy]
|
123
177
|
cache_opts[name][:klass] = klass
|
124
178
|
|
data/lib/active_cash/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_cash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Filippos Vasilakis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-objects
|