smart_collection 0.2 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e48926b0db15bfc7e6c786278522b7f07df46900
4
- data.tar.gz: 6a1daefd7675a7bd97068d4e1298e5737f0273a0
2
+ SHA256:
3
+ metadata.gz: ab885e83fde2cc95d9864c47930a00c074228be053f02a4c115188fa815be2c0
4
+ data.tar.gz: 83f2cec29972fa10282e8aa35a41577afded2dff176f85c4db627c820996069e
5
5
  SHA512:
6
- metadata.gz: 6d9a7c2649df55b559a0f54517ffbca6a237cad2da28da6a254980fc41fd0eb6bb27db13b5eb7c59251fddfea404f9ad24e06595a364f962264fe147ec315528
7
- data.tar.gz: 782412ba48ce5aa99508992857e94490a19602a8b28d546217725cb77b8232875e6b733b191ff09195f889d32919181f3ce3587c167f0b637eefcb770efdc752
6
+ metadata.gz: 05a2b4e9d08c201646a72db23941074e7d6a41aff1dee36bad1013349759e5ba7b5a38ee4024650e1b14b720ddc3d14fe646e5b8d1f31518733795274f1a56a9
7
+ data.tar.gz: 46afdfe614697e6ae5efebe1f46e7fc8179c0dad073ffc63cc65b7be570ab6a1a7c02e2ce05b4d719881887c6f92ff2a842ba383009770c84bd74ebd42f8dfab
@@ -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
- raise NotImplementedError
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
- raise NotImplementedError
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
- raise NotImplementedError
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[:items]
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[:class_name]
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,71 +15,87 @@ module SmartCollection
33
15
  end
34
16
 
35
17
  def uncached_scope owner
36
- SmartCollection::ScopeBuilder.new(owner.rule, @config.item_class).build
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 included base
44
- @config = config = SmartCollection::Config.new(@raw_config)
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,
60
46
  scope: -> owner {
61
- if cache_manager = config.cache_manager
47
+ if owner.new_record?
48
+ uncached_scope(owner)
49
+ else
50
+ cache_manager = config.cache_manager
62
51
  unless cache_manager.cache_exists? owner
63
52
  owner.update_cache
64
53
  end
65
54
  cached_scope(owner)
66
- else
67
- uncached_scope(owner)
68
55
  end
69
56
  },
70
57
  type: :collection
71
58
  }
72
59
 
73
- case
74
- when cache_class == SmartCollection::CacheManager::CacheStore
75
- mixin_options[:preloader] = -> owners {
76
- owners.reject(&:cache_exists?).each(&:update_cache)
77
- loaded = config.cache_manager.read_multi(owners)
78
- records = config.item_class.where(id: loaded.values.flatten.uniq).map{|x| [x.id, x]}.to_h
79
- loaded.map do |owner, ids|
80
- [owner, ids.map{|x| records[x]}]
81
- end.to_h
82
- }
83
- when cache_class == SmartCollection::CacheManager::Table
84
- cached_name = "cached_#{config.items_name}".to_sym
85
- mixin_options[:preloader] = -> owners {
86
- owners.reject(&:cache_exists?).each(&:update_cache)
87
- Associationist.preload(owners, cached_items: config.item_name)
88
- owners.map do |owner|
89
- [owner, owner.cached_items.map{|item| item.send(config.item_name)}]
90
- end.to_h
91
- }
92
- else
93
- mixin_options[:preloader] = -> _ {
94
- raise RuntimeError, "Turn on cache to enable preloading."
95
- }
96
- 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
+
97
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
98
78
 
99
- base.validates_with SmartCollection::Validator
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
90
+
91
+ def included base
92
+ @config = config = SmartCollection::Config.new(@raw_config)
93
+
94
+ base.include(InstanceMethods)
95
+ base.extend(ClassMethods)
100
96
 
97
+ define_association base
98
+ define_inverse_association base
101
99
  end
102
100
  end
103
101
  end
@@ -1,3 +1,3 @@
1
1
  module SmartCollection
2
- VERSION = '0.2'
2
+ VERSION = '0.3.1'
3
3
  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.2'
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - CicholGricenchos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-26 00:00:00.000000000 Z
11
+ date: 2021-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: associationist
@@ -16,14 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.1.0
19
+ version: '0.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.1.8
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - "~>"
25
28
  - !ruby/object:Gem::Version
26
- version: 0.1.0
29
+ version: '0.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.1.8
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: pry
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -89,12 +95,9 @@ extra_rdoc_files: []
89
95
  files:
90
96
  - lib/smart_collection.rb
91
97
  - lib/smart_collection/cache_manager.rb
92
- - lib/smart_collection/cache_manager/cache_store.rb
93
- - lib/smart_collection/cache_manager/table.rb
94
98
  - lib/smart_collection/config.rb
99
+ - lib/smart_collection/instance_methods.rb
95
100
  - lib/smart_collection/mixin.rb
96
- - lib/smart_collection/scope_builder.rb
97
- - lib/smart_collection/validator.rb
98
101
  - lib/smart_collection/version.rb
99
102
  homepage: https://github.com/CicholGricenchos/smart_collection
100
103
  licenses:
@@ -115,8 +118,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
118
  - !ruby/object:Gem::Version
116
119
  version: '0'
117
120
  requirements: []
118
- rubyforge_project:
119
- rubygems_version: 2.5.2.3
121
+ rubygems_version: 3.2.15
120
122
  signing_key:
121
123
  specification_version: 4
122
124
  summary: collections by rules
@@ -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