smart_collection 0.2.2 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/smart_collection/cache_manager.rb +28 -16
- data/lib/smart_collection/config.rb +47 -2
- data/lib/smart_collection/instance_methods.rb +19 -0
- data/lib/smart_collection/mixin.rb +59 -65
- data/lib/smart_collection/version.rb +1 -1
- metadata +5 -8
- data/lib/smart_collection/cache_manager/cache_store.rb +0 -42
- data/lib/smart_collection/cache_manager/table.rb +0 -44
- data/lib/smart_collection/scope_builder.rb +0 -46
- data/lib/smart_collection/validator.rb +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7106929dd8e41780fb988dd9aeb82d4b82e115cb
|
4
|
+
data.tar.gz: 689310ec89a9971a06ac74df1eaa8f27a7aef3a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 560c1d306060f3ce682bb362d3c9131541c6e461c577f03d0a800448c6c4b7995204c19429317e26c80a0a3eaa28220e4a6a68622053e2154dac0a85d80d1d39
|
7
|
+
data.tar.gz: cb777ac82a0aad678235a4f67dda300d92e44d25ec80c0c370034760d57b7a11e700ab627e6615c888bdf5141d9446f4c8e2af69c8c9ce1bae9e283ce637ff17
|
@@ -1,34 +1,46 @@
|
|
1
1
|
module SmartCollection
|
2
2
|
class CacheManager
|
3
|
-
|
4
|
-
def self.determine_class config
|
5
|
-
case
|
6
|
-
when config.dig(:cached_by, :table)
|
7
|
-
SmartCollection::CacheManager::Table
|
8
|
-
when config.dig(:cached_by, :cache_store)
|
9
|
-
SmartCollection::CacheManager::CacheStore
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
3
|
def initialize model:, config:
|
14
4
|
@model = model
|
15
5
|
@config = config
|
6
|
+
define_cache_association_for model
|
7
|
+
end
|
8
|
+
|
9
|
+
def define_cache_association_for model
|
10
|
+
config = @config
|
11
|
+
cached_item_model = nil
|
12
|
+
model.class_eval do
|
13
|
+
cached_item_model = Class.new ActiveRecord::Base do
|
14
|
+
self.table_name = config.cache_table_name
|
15
|
+
belongs_to config.item_name, class_name: config.item_class_name, foreign_key: :item_id
|
16
|
+
end
|
17
|
+
const_set("CachedItem", cached_item_model)
|
18
|
+
|
19
|
+
has_many :cached_items, class_name: cached_item_model.name, foreign_key: :collection_id
|
20
|
+
has_many "cached_#{config.items_name}".to_sym, class_name: config.item_class_name, through: :cached_items, source: config.item_name
|
21
|
+
end
|
22
|
+
@cache_model = cached_item_model
|
16
23
|
end
|
17
24
|
|
18
25
|
def update owner
|
19
|
-
|
26
|
+
scopes = @config.scopes_proc.(owner)
|
27
|
+
@cache_model.where(collection_id: owner.id).delete_all
|
28
|
+
#scopes.each do |scope|
|
29
|
+
# sql = scope.select(owner.id, :id).to_sql
|
30
|
+
# @cache_model.connection.execute "INSERT INTO #{@cache_model.table_name} (collection_id, item_id) #{sql}"
|
31
|
+
#end
|
32
|
+
@cache_model.connection.execute "INSERT INTO #{@cache_model.table_name} (collection_id, item_id) #{owner.smart_collection_mixin.uncached_scope(owner).select(owner.id, :id).to_sql}"
|
33
|
+
owner.update_column(:cache_expires_at, Time.now + @config.cache_expires_in_proc.(owner))
|
20
34
|
end
|
21
35
|
|
22
36
|
def read_scope owner
|
23
|
-
|
37
|
+
cache_association = owner.association("cached_#{@config.items_name}")
|
38
|
+
cache_association.scope
|
24
39
|
end
|
25
40
|
|
26
41
|
def cache_exists? owner
|
27
|
-
|
42
|
+
owner.cache_expires_at && owner.cache_expires_at > Time.now
|
28
43
|
end
|
29
44
|
|
30
|
-
def expires_in
|
31
|
-
@config.cache_config[:expires_in] || 1.hour
|
32
|
-
end
|
33
45
|
end
|
34
46
|
end
|
@@ -5,10 +5,11 @@ module SmartCollection
|
|
5
5
|
|
6
6
|
def initialize raw_config
|
7
7
|
@raw_config = raw_config
|
8
|
+
check_config
|
8
9
|
end
|
9
10
|
|
10
11
|
def items_name
|
11
|
-
@raw_config[:
|
12
|
+
@raw_config[:item_association]
|
12
13
|
end
|
13
14
|
|
14
15
|
def item_name
|
@@ -16,7 +17,7 @@ module SmartCollection
|
|
16
17
|
end
|
17
18
|
|
18
19
|
def item_class_name
|
19
|
-
@raw_config[:
|
20
|
+
@raw_config[:item_class_name]
|
20
21
|
end
|
21
22
|
|
22
23
|
def item_class
|
@@ -26,5 +27,49 @@ module SmartCollection
|
|
26
27
|
def cache_config
|
27
28
|
@raw_config[:cached_by]
|
28
29
|
end
|
30
|
+
|
31
|
+
def scopes_proc
|
32
|
+
case @raw_config[:scopes]
|
33
|
+
when Proc
|
34
|
+
@raw_config[:scopes]
|
35
|
+
when Symbol
|
36
|
+
-> (owner) { owner.send(@raw_config[:scopes]) }
|
37
|
+
else
|
38
|
+
raise "scopes option only accepts a Proc / Symbol"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def inverse_association
|
43
|
+
@raw_config[:inverse_association]
|
44
|
+
end
|
45
|
+
|
46
|
+
def cache_table_name
|
47
|
+
if @raw_config[:cache_table] == :default
|
48
|
+
:smart_collection_cached_items
|
49
|
+
else
|
50
|
+
@raw_config[:cache_table]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def cache_expires_in_proc
|
55
|
+
case @raw_config[:cache_expires_in]
|
56
|
+
when Proc
|
57
|
+
@raw_config[:cache_expires_in]
|
58
|
+
when ActiveSupport::Duration
|
59
|
+
-> (owner) { @raw_config[:cache_expires_in] }
|
60
|
+
when Symbol
|
61
|
+
-> (owner) { owner.send(@raw_config[:cache_expires_in]) }
|
62
|
+
else
|
63
|
+
raise "cache_expires_in option only accepts a Proc / ActiveSupport::Duration / Symbol"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def check_config
|
68
|
+
raise "items option must be provided" if items_name.nil?
|
69
|
+
raise "item_class option must be provided" if item_class_name.nil?
|
70
|
+
raise "scopes option must be provided" if @raw_config[:scopes].nil?
|
71
|
+
raise "cache_table option must be provided" if @raw_config[:cache_table].nil?
|
72
|
+
raise "cache_expires_in option must be provided" if @raw_config[:cache_expires_in].nil?
|
73
|
+
end
|
29
74
|
end
|
30
75
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SmartCollection
|
2
|
+
module InstanceMethods
|
3
|
+
def update_cache
|
4
|
+
smart_collection_mixin.config.cache_manager.update self
|
5
|
+
end
|
6
|
+
|
7
|
+
def expire_cache
|
8
|
+
update_column(:cache_expires_at, Time.now - 1)
|
9
|
+
end
|
10
|
+
|
11
|
+
def cache_exists?
|
12
|
+
smart_collection_mixin.config.cache_manager.cache_exists? self
|
13
|
+
end
|
14
|
+
|
15
|
+
def smart_collection_mixin
|
16
|
+
self.class.smart_collection_mixin
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,23 +1,5 @@
|
|
1
1
|
module SmartCollection
|
2
2
|
class Mixin < Module
|
3
|
-
module InstanceMethods
|
4
|
-
def update_cache
|
5
|
-
smart_collection_mixin.config.cache_manager.update self
|
6
|
-
end
|
7
|
-
|
8
|
-
def expire_cache
|
9
|
-
update_column(:cache_expires_at, nil)
|
10
|
-
end
|
11
|
-
|
12
|
-
def cache_exists?
|
13
|
-
smart_collection_mixin.config.cache_manager.cache_exists? self
|
14
|
-
end
|
15
|
-
|
16
|
-
def smart_collection_mixin
|
17
|
-
self.class.smart_collection_mixin
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
3
|
module ClassMethods
|
22
4
|
def smart_collection_mixin
|
23
5
|
@__smart_collection_mixin ||= ancestors.find do |x|
|
@@ -33,27 +15,31 @@ module SmartCollection
|
|
33
15
|
end
|
34
16
|
|
35
17
|
def uncached_scope owner
|
36
|
-
|
18
|
+
scopes = @config.scopes_proc.(owner)
|
19
|
+
raise unless scopes.all?{|x| x.klass == @config.item_class}
|
20
|
+
if scopes.empty?
|
21
|
+
@config.item_class.where('1 = 0')
|
22
|
+
else
|
23
|
+
scopes = scopes.map do |scope|
|
24
|
+
if !scope.select_values.empty?
|
25
|
+
new_scope = scope.dup
|
26
|
+
new_scope.select_values = []
|
27
|
+
new_scope
|
28
|
+
else
|
29
|
+
scope
|
30
|
+
end
|
31
|
+
end
|
32
|
+
@config.item_class.from("(#{scopes.map{|x| "#{x.to_sql}"}.join(' UNION ')}) as #{@config.item_class.table_name}")
|
33
|
+
end
|
37
34
|
end
|
38
35
|
|
39
36
|
def cached_scope owner
|
40
37
|
@config.cache_manager.read_scope owner
|
41
38
|
end
|
42
39
|
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
reflection_options = {smart_collection: config}
|
47
|
-
if config.item_class_name
|
48
|
-
reflection_options[:class_name] = config.item_class_name
|
49
|
-
end
|
50
|
-
|
51
|
-
base.include(InstanceMethods)
|
52
|
-
base.extend(ClassMethods)
|
53
|
-
|
54
|
-
if cache_class = CacheManager.determine_class(@raw_config)
|
55
|
-
config.cache_manager = cache_class.new(model: base, config: config)
|
56
|
-
end
|
40
|
+
def define_association base
|
41
|
+
config = @config
|
42
|
+
config.cache_manager = CacheManager.new(model: base, config: config)
|
57
43
|
|
58
44
|
mixin_options = {
|
59
45
|
name: config.items_name,
|
@@ -61,47 +47,55 @@ module SmartCollection
|
|
61
47
|
if owner.new_record?
|
62
48
|
uncached_scope(owner)
|
63
49
|
else
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
cached_scope(owner)
|
69
|
-
else
|
70
|
-
uncached_scope(owner)
|
50
|
+
cache_manager = config.cache_manager
|
51
|
+
unless cache_manager.cache_exists? owner
|
52
|
+
owner.update_cache
|
71
53
|
end
|
54
|
+
cached_scope(owner)
|
72
55
|
end
|
73
56
|
},
|
74
57
|
type: :collection
|
75
58
|
}
|
76
59
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
}
|
87
|
-
when cache_class == SmartCollection::CacheManager::Table
|
88
|
-
cached_name = "cached_#{config.items_name}".to_sym
|
89
|
-
mixin_options[:preloader] = -> owners {
|
90
|
-
owners.reject(&:cache_exists?).each(&:update_cache)
|
91
|
-
Associationist.preload(owners, cached_items: config.item_name)
|
92
|
-
owners.map do |owner|
|
93
|
-
[owner, owner.cached_items.map{|item| item.send(config.item_name)}]
|
94
|
-
end.to_h
|
95
|
-
}
|
96
|
-
else
|
97
|
-
mixin_options[:preloader] = -> _ {
|
98
|
-
raise RuntimeError, "Turn on cache to enable preloading."
|
99
|
-
}
|
100
|
-
end
|
60
|
+
cached_name = "cached_#{config.items_name}".to_sym
|
61
|
+
mixin_options[:preloader] = -> owners {
|
62
|
+
owners.reject(&:cache_exists?).each(&:update_cache)
|
63
|
+
Associationist.preload(owners, cached_items: config.item_name)
|
64
|
+
owners.map do |owner|
|
65
|
+
[owner, owner.cached_items.map{|item| item.send(config.item_name)}]
|
66
|
+
end.to_h
|
67
|
+
}
|
68
|
+
|
101
69
|
base.include Associationist::Mixin.new(mixin_options)
|
70
|
+
end
|
71
|
+
|
72
|
+
def expired_scope base
|
73
|
+
base.where(base.arel_table[:cache_expires_at].lt(Time.now))
|
74
|
+
end
|
75
|
+
|
76
|
+
def define_inverse_association base
|
77
|
+
return unless @config.inverse_association
|
78
|
+
|
79
|
+
mixin_options = {
|
80
|
+
name: @config.inverse_association,
|
81
|
+
scope: -> owner {
|
82
|
+
expired = base.joins(:cached_items).where(@config.cache_table_name => {item_id: owner.id}).merge(expired_scope base)
|
83
|
+
expired.each(&:update_cache)
|
84
|
+
base.joins(:cached_items).where(@config.cache_table_name => {item_id: owner.id})
|
85
|
+
},
|
86
|
+
type: :collection
|
87
|
+
}
|
88
|
+
@config.item_class.include Associationist::Mixin.new(mixin_options)
|
89
|
+
end
|
102
90
|
|
103
|
-
|
91
|
+
def included base
|
92
|
+
@config = config = SmartCollection::Config.new(@raw_config)
|
93
|
+
|
94
|
+
base.include(InstanceMethods)
|
95
|
+
base.extend(ClassMethods)
|
104
96
|
|
97
|
+
define_association base
|
98
|
+
define_inverse_association base
|
105
99
|
end
|
106
100
|
end
|
107
101
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_collection
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.3'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- CicholGricenchos
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-07-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: associationist
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.1.
|
19
|
+
version: 0.1.3
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.1.
|
26
|
+
version: 0.1.3
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pry
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -89,12 +89,9 @@ extra_rdoc_files: []
|
|
89
89
|
files:
|
90
90
|
- lib/smart_collection.rb
|
91
91
|
- lib/smart_collection/cache_manager.rb
|
92
|
-
- lib/smart_collection/cache_manager/cache_store.rb
|
93
|
-
- lib/smart_collection/cache_manager/table.rb
|
94
92
|
- lib/smart_collection/config.rb
|
93
|
+
- lib/smart_collection/instance_methods.rb
|
95
94
|
- lib/smart_collection/mixin.rb
|
96
|
-
- lib/smart_collection/scope_builder.rb
|
97
|
-
- lib/smart_collection/validator.rb
|
98
95
|
- lib/smart_collection/version.rb
|
99
96
|
homepage: https://github.com/CicholGricenchos/smart_collection
|
100
97
|
licenses:
|
@@ -1,42 +0,0 @@
|
|
1
|
-
module SmartCollection
|
2
|
-
class CacheManager
|
3
|
-
class CacheStore < CacheManager
|
4
|
-
|
5
|
-
def initialize model:, config:
|
6
|
-
super
|
7
|
-
|
8
|
-
@cache_key_proc = @config.cache_config[:cache_key_proc] || -> (owner) { "_smart_collection_#{owner.class.name}_#{owner.id}" }
|
9
|
-
end
|
10
|
-
|
11
|
-
def cache_store
|
12
|
-
@config.cache_config[:cache_store]
|
13
|
-
end
|
14
|
-
|
15
|
-
def cache_key owner
|
16
|
-
@cache_key_proc.(owner)
|
17
|
-
end
|
18
|
-
|
19
|
-
def update owner
|
20
|
-
cache_store.write(cache_key(owner), Marshal.dump(owner.smart_collection_mixin.uncached_scope(owner).pluck(:id)))
|
21
|
-
owner.update(cache_expires_at: Time.now + expires_in)
|
22
|
-
end
|
23
|
-
|
24
|
-
def read_scope owner
|
25
|
-
@config.item_class.where(id: Marshal.load(cache_store.read(cache_key owner)))
|
26
|
-
end
|
27
|
-
|
28
|
-
def read_multi owners
|
29
|
-
cache_keys = owners.map{|owner| cache_key owner}
|
30
|
-
loaded = cache_store.read_multi(*cache_keys)
|
31
|
-
owners.map.with_index do |owner, index|
|
32
|
-
[owner, Marshal.load(loaded[cache_keys[index]])]
|
33
|
-
end.to_h
|
34
|
-
end
|
35
|
-
|
36
|
-
def cache_exists? owner
|
37
|
-
owner.cache_expires_at && owner.cache_expires_at > Time.now && cache_store.exist?(cache_key owner)
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
module SmartCollection
|
2
|
-
class CacheManager
|
3
|
-
class Table < CacheManager
|
4
|
-
|
5
|
-
def initialize model:, config:
|
6
|
-
super
|
7
|
-
|
8
|
-
define_cache_association_for model
|
9
|
-
end
|
10
|
-
|
11
|
-
def define_cache_association_for model
|
12
|
-
config = @config
|
13
|
-
cached_item_model = nil
|
14
|
-
model.class_eval do
|
15
|
-
cached_item_model = Class.new ActiveRecord::Base do
|
16
|
-
self.table_name = 'smart_collection_cached_items'
|
17
|
-
belongs_to config.item_name, class_name: config.item_class_name, foreign_key: :item_id
|
18
|
-
end
|
19
|
-
const_set("CachedItem", cached_item_model)
|
20
|
-
|
21
|
-
has_many :cached_items, class_name: cached_item_model.name, foreign_key: :collection_id
|
22
|
-
has_many "cached_#{config.items_name}".to_sym, class_name: config.item_class_name, through: :cached_items, source: config.item_name
|
23
|
-
end
|
24
|
-
@cache_model = cached_item_model
|
25
|
-
end
|
26
|
-
|
27
|
-
def update owner
|
28
|
-
@cache_model.where(collection_id: owner.id).delete_all
|
29
|
-
@cache_model.connection.execute "INSERT INTO #{@cache_model.table_name} (collection_id, item_id) #{owner.smart_collection_mixin.uncached_scope(owner).select(owner.id, :id).to_sql}"
|
30
|
-
owner.update_column(:cache_expires_at, Time.now + expires_in)
|
31
|
-
end
|
32
|
-
|
33
|
-
def read_scope owner
|
34
|
-
cache_association = owner.association("cached_#{@config.items_name}")
|
35
|
-
cache_association.scope
|
36
|
-
end
|
37
|
-
|
38
|
-
def cache_exists? owner
|
39
|
-
owner.cache_expires_at && owner.cache_expires_at > Time.now
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,46 +0,0 @@
|
|
1
|
-
module SmartCollection
|
2
|
-
class ScopeBuilder
|
3
|
-
def initialize rule, klass
|
4
|
-
@rule = rule
|
5
|
-
@klass = klass
|
6
|
-
@klass_hash = {}
|
7
|
-
end
|
8
|
-
|
9
|
-
def build
|
10
|
-
rule_to_bulk_queries @rule
|
11
|
-
bulk_load
|
12
|
-
rule_to_scope @rule
|
13
|
-
end
|
14
|
-
|
15
|
-
def bulk_load
|
16
|
-
@klass_hash = @klass_hash.map do |klass_name, ids|
|
17
|
-
[klass_name, Object.const_get(klass_name).where(id: ids).map{|x| [x.id, x]}.to_h]
|
18
|
-
end.to_h
|
19
|
-
end
|
20
|
-
|
21
|
-
def rule_to_bulk_queries rule
|
22
|
-
case
|
23
|
-
when arr = (rule['or'] || rule['and'])
|
24
|
-
arr.each{|x| rule_to_bulk_queries x}
|
25
|
-
when assoc = rule['association']
|
26
|
-
ids = @klass_hash[assoc['class_name']] ||= []
|
27
|
-
ids << assoc['id']
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def rule_to_scope rule
|
32
|
-
case
|
33
|
-
when ors = rule['or']
|
34
|
-
ors.map{|x| rule_to_scope x}.inject(:or)
|
35
|
-
when ands = rule['and']
|
36
|
-
ands.map{|x| rule_to_scope x}.inject(:merge)
|
37
|
-
when assoc = rule['association']
|
38
|
-
@klass_hash[assoc['class_name']][assoc['id']].association(assoc['source']).scope
|
39
|
-
when cond = rule['condition']
|
40
|
-
scope = @klass
|
41
|
-
scope = scope.joins(cond['joins'].to_sym) if cond['joins']
|
42
|
-
scope.where(cond['where'])
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
@@ -1,10 +0,0 @@
|
|
1
|
-
module SmartCollection
|
2
|
-
class Validator < ActiveModel::Validator
|
3
|
-
def validate record
|
4
|
-
# try to build scope
|
5
|
-
record.association(record.smart_collection_mixin.config.items_name).scope
|
6
|
-
rescue Exception => e
|
7
|
-
record.errors.add(:failed_to_build_scope, e)
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|